在 MATLAB 中,默认情况下变量真的是双精度的吗?

Posted

技术标签:

【中文标题】在 MATLAB 中,默认情况下变量真的是双精度的吗?【英文标题】:In MATLAB, are variables REALLY double-precision by default? 【发布时间】:2011-05-12 17:54:07 【问题描述】:

这个问题源于我在进一步调查this question 后注意到的一些奇怪的事情......

我一直认为 MATLAB 变量默认为 double-precision。因此,如果我要执行类似声明小数点后 20 位的变量之类的操作:

>> num = 2.71828182845904553488;
>> class(num)  % Display the variable type
ans =
double

我希望最后 4 位数字被忽略,因为 floating-point relative accuracy 大约为 10-16

>> eps(num)
ans =
    4.440892098500626e-016

如果我尝试显示小数点后超过 16 位的数字(使用 fprintfsprintf),我会得到预期的结果:

>> fprintf('%0.20f\n', num)
2.71828182845904550000
>> sprintf('%0.20f', num)
ans =
2.71828182845904550000

换句话说,数字 17 到 20 都是 0。

但是当我将num 传递给Symbolic Toolbox 中的variable precision arithmetic function 时,事情变得很奇怪,告诉它使用21 位精度来表示数字:

>> vpa(num, 21)
ans =
2.71828182845904553488

什么?!最后 4 位数字又出现了!当我输入的原始数字存储为双精度变量num 时,它们不应该丢失吗?由于num 传递给vpa 时是双精度变量,那么vpa 怎么知道它们是什么?

我对正在发生的事情的最佳猜测是,MATLAB 在内部表示 num 的精度比双精度更高,因为我将它初始化为一个数字,其小数点后的位数比双精度变量可以处理的多。这真的是正在发生的事情,还是发生了其他事情?


奖励:如果您还没有上述偏头痛,这里还有一个令人困惑的来源......

>> num = 2.71828182845904553488;  % Declare with 20 digits past the decimal
>> num = 2.718281828459045531;    % Re-declare with 18 digits past the decimal
>> vpa(num, 21)
ans =
2.71828182845904553488  % It's the original 20-digit number!!!

【问题讨论】:

【参考方案1】:

他们是双打。 vpa() 只是选择显示超出浮点相对精度的非有效数字,其中 printf()disp() 将它们截断或归零。

你只得到你原来的四位数,因为你选择初始化num的文字恰好是二进制双精度值的精确十进制扩展,因为它是从输出的复制和粘贴的从另一个问题扩展实际的双精度值。正如您在“BONUS”附录中显示的那样,它不适用于附近的其他值。

更准确地说,Matlab 中的所有数字文字都会产生 double 类型的值。它们被转换为最接近它们所代表的十进制值的二进制双精度值。实际上,文字中超出 double 类型精度限制的数字会被静默删除。当您复制并粘贴 vpa 的输出以创建新变量时,就像另一个问题的海报对 e = ... 语句所做的那样,您是从文字初始化一个值,而不是直接处理前一个的结果表达。

这里的区别只是输出格式。我认为正在发生的事情是vpa() 正在采用双精度二进制双精度并将其视为精确值。对于给定的二进制尾数指数值,您可以计算出相当于任意多个小数位的小数。如果二进制值的精度(“宽度”)有限,就像使用任何固定大小的数据类型一样,那么这些十进制数字中只有这么多是有效的。 printf() 和 Matlab 的默认显示通过截断输出或将非有效数字显示为 0 来处理此问题。vpa() 忽略了精度限制,并继续根据您的要求计算尽可能多的小数位。

这些额外的数字是虚假的,如果它们被其他值替换以产生附近的十进制值,它们都会被“四舍五入”到相同的二进制双精度值。

这是一种展示方式。 x 的这些值在存储为双精度时都是相同的,并且都将由vpa() 表示。

x = [
    2.7182818284590455348848081484902650117874145507812500
    2.7182818284590455348848081484902650117874145507819999
    2.7182818284590455348848
    2.71828182845904553488485555555555555555555555555555
    exp(1)
    ]
unique(x)

这是展示它的另一种方式。这是两个非常接近的双打。

x0 = exp(1)
x1 = x0 + eps(x0)

vpa(x0)vpa(x1) 产生的输出在第 16 位之后应该会有很大的不同。但是,您不应该能够创建一个双精度值 x 以使 vpa(x) 生成介于 vpa(x0)vpa(x1) 之间的十进制表示。

(更新:Amro 指出您可以使用 fprintf('%bx\n', x) 以十六进制格式显示底层二进制值的精确表示。您可以使用它来确认文字映射到相同的双精度。)

我怀疑vpa() 之所以这样做是因为它将其输入视为精确值,并且多态支持符号工具箱中的其他 Matlab 类型,这些类型的精度高于双精度。这些值需要通过数字文字以外的方式进行初始化,这就是为什么sym() 将字符串作为输入并且vpa(exp(1)) 不同于vpa(sym('exp(1)'))

有意义吗?抱歉啰嗦了。

(请注意,我没有符号工具箱,所以我无法自己测试vpa()。)

【讨论】:

啊哈!所以我只是碰巧使用二进制值的精确十进制扩展作为我的测试数字。现在一切都说得通了!不知道我是怎么错过的,不过……也许是因为我女儿出牙时睡眠不足,让我整晚都睡不着。 ;) @Mikhail:不,虽然有一些真正的 MathWorks 人在 SO 周围闲逛,比如 Loren 和 MatlabDoug。我刚刚花时间构建了使用 Matlab 的外部接口支持集成 C、Java 和 COM 的 Matlab 开发平台。熟悉数据类型和 Matlab 内部结构的好方法。 您还可以检查双精度变量的十六进制表示。试试fprintf('%bx\n', exp(1), 2.7182818284590455, 2.71828182845904559999999999),它们都会返回相同的64位表示4005bf0a8b14576a @Amro:太好了!我不知道 %bx。这也证实了 eps() 给出了一位差异。 fprintf('%bx\n', exp(1), exp(1)+eps(exp(1)))(至少对于这个值)。在我的回答中加入这一点。 @AndrewJanke:很抱歉复活这个旧线程,但似乎 VPA(或者确切地说是由 VPA 调用的 SYM)比我们想象的还要棘手。默认情况下,SYM 尝试将浮点数转换为“有理”形式,以补偿中间评估中涉及的舍入误差...更多关于这个的讨论here【参考方案2】:

第一:

似乎 sprintf 和 fprintf 在不同版本的 MATLAB 上具有不同的行为 例如在 MATLAB 2018 a

num=2.7182818284590666666666;    
sprintf('%0.70f', num)
ans =
'2.7182818284590668511668809514958411455154418945312500000000000000000000'

秒:

浮点数

MATLAB® 以双精度或单精度格式表示浮点数。默认为双精度,但您可以通过简单的转换函数将任意数字设为单精度。

双精度浮点

MATLAB 根据 IEEE® 标准 754 为双精度构造双精度(或双精度)数据类型。任何存储为双精度值的值都需要 64 位,格式如下表所示:

位:63 用法:符号(0 = 正,1 = 负)

位:62 到 52 用法:指数,偏差 1023

位:51 到 0 用法:数字 1.f 的分数 f

refer to this link for more info

在 252=4,503,599,627,370,496 和 253=9,007,199,254,740,992 之间,可表示的数字恰好是整数。对于下一个范围,从 253 到 254,所有内容都乘以 2,因此可表示的数字是偶数等。相反,对于上一个范围从 2^51 到 2^52,间距为 0.5,等等。

作为从 2^n 到 2^n+1 范围内的数字的一部分,间距是 2^n−52。因此,将数字舍入到最接近的可表示值(机器 epsilon)时的最大相对舍入误差为 2^-53。

所以在你的情况下 n=1 (2^1

我认为可以安全地假设用于显示数字的 sprintf 和 sprintf 算法很棘手,并且 MATLAB Double 类型基于 IEEE 标准,


关于VPA:

vpa 使用保护数字来保持精度

digits 函数的值指定使用的有效数字的最小数量。在内部,vpa 可以使用比数字指定更多的数字。这些额外的数字被称为保护数字,因为它们可以防止后续计算中的舍入错误。

使用四位有效数字近似为 1/3。

a = vpa(1/3, 4)
a =
0.3333

使用 20 位近似结果 a。结果表明,工具箱内部在计算 a 时使用了多于四位数。由于舍入误差,结果中的最后一位数字不正确。

vpa(a, 20)
ans =
0.33333333333303016843

你可能遇到的问题是因为间距、高德数字算法和四舍五入问题,

例如使用matlab 2018 a:

 sprintf('%0.28f', 8.0)
 ans =
 '8.0000000000000000000000000000'

但是:

sprintf('%0.28f', 8.1)
ans =
'8.0999999999999996447286321199'

因为数字在 2^3 和 2^4 之间所以间距是 2^-49 (= 1.77 e-15) 所以该数字有效到小数点后 15 位 和

sprintf('%0.28f', 64.1)
ans =
'64.0999999999999943156581139192'

因为数字在 2^6 和 2^7 之间所以间距是 2^-46 (= 1.42 e-14) 所以这个数字一直有效到小数点后 14 位

【讨论】:

以上是关于在 MATLAB 中,默认情况下变量真的是双精度的吗?的主要内容,如果未能解决你的问题,请参考以下文章

MATLAB-数据类型

如何在Objective-C中检查输入是双精度还是字符串?

如何在保证仿真精度的情况下提高仿真速度

Matlab编程Matlab高效编程技巧

默认情况下停靠 Matlab 图形

C ++ 11中“auto var = condition?1:1.0”的类型是啥?它是双精度还是整数?