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

gno*_*ice 72 variables floating-point precision matlab

这个问题产生于我在进一步调查这个问题后发现的一些奇怪的事情......

我总是将MATLAB变量理解为默认的双精度.所以,如果我要做一些事情,比如声明一个小数点后20位数的变量:

>> num = 2.71828182845904553488;
>> class(num)  % Display the variable type
ans =
double
Run Code Online (Sandbox Code Playgroud)

我希望忽略最后4位数,因为浮点相对精度大约为10 -16:

>> eps(num)
ans =
    4.440892098500626e-016
Run Code Online (Sandbox Code Playgroud)

如果我尝试在小数点后面显示超过16位的数字(使用fprintf或者sprintf),我得到的结果是:

>> fprintf('%0.20f\n', num)
2.71828182845904550000
>> sprintf('%0.20f', num)
ans =
2.71828182845904550000
Run Code Online (Sandbox Code Playgroud)

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

但是,事情变得奇怪,当我传球num可变精度算术函数符号工具箱,告诉它表示使用的精度21个的位数:

>> vpa(num, 21)
ans =
2.71828182845904553488
Run Code Online (Sandbox Code Playgroud)

什么?!最后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!!!
Run Code Online (Sandbox Code Playgroud)

And*_*nke 65

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

你只是将原来的四位数字退出,因为你选择初始化的文字num恰好是二进制双精度值的精确十进制扩展,因为它是从实际双精度值的扩展输出中复制和粘贴的从另一个问题.它不适用于附近的其他值,正如您在"奖励"附录中所示.

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

这里的差异只是输出格式.我认为正在发生的vpa()是将双精度二进制加倍并将其视为精确值.对于给定的二进制尾数 - 指数值,您可以计算等效于任意多个小数位的十进制等值.如果您在二进制值中具有有限的精度("宽度"),就像使用任何固定大小的数据类型一样,只有那么多的十进制数字是重要的.printf()和Matlab的默认显示通过截断输出或将非有效数字显示为0 vpa()来处理此操作.忽略精度限制并继续计算您请求的小数位数.

那些额外的数字是假的,在某种意义上,如果它们被其他值替换以产生附近的十进制值,它们将全部"舍入"到相同的二进制双值.

这是展示它的一种方式.当存储在双精度中时,这些x的值都是相同的,并且将全部表示为相同的vpa().

x = [
    2.7182818284590455348848081484902650117874145507812500
    2.7182818284590455348848081484902650117874145507819999
    2.7182818284590455348848
    2.71828182845904553488485555555555555555555555555555
    exp(1)
    ]
unique(x)
Run Code Online (Sandbox Code Playgroud)

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

x0 = exp(1)
x1 = x0 + eps(x0)
Run Code Online (Sandbox Code Playgroud)

vpa(x0)并且vpa(x1)应该产生与第16位数相差很多的输出.但是,您不能创建一个double值x,以便vpa(x)生成一个介于vpa(x0)和之间的十进制表示vpa(x1).

(更新:Amro指出您可以使用fprintf('%bx\n', x)以十六进制格式显示基础二进制值的精确表示.您可以使用它来确认文字映射到同一个double.)

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

合理?对不起,啰嗦.

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

  • 您还可以检查双精度变量的十六进制表示.尝试`fprintf('%bx \n',exp(1),2.7182818284590455,2.71828182845904559999999999)`并且它们都将返回相同的64位表示`4005bf0a8b14576a` (4认同)