这个问题产生于我在进一步调查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位的数字(使用fprintf
或sprintf
),我会得到我期望看到的内容:
>> fprintf('%0.20f\n', num)
2.71828182845904550000
>> sprintf('%0.20f', num)
ans =
2.71828182845904550000
换句话说,数字17到20都是0。
但是当我将num
传递给variable precision arithmetic function中的Symbolic Toolbox时,事情变得奇怪,告诉它使用21位精度来表示数字:
>> vpa(num, 21)
ans =
2.71828182845904553488
什么?!最后4位数字再次出现!当我输入的原始数字存储为双精度变量num
时,它们是否应该丢失?由于num
是传递给vpa
时的双精度变量,vpa
如何知道它们是什么?
我最好的猜测是,MATLAB内部表示num
的精度高于double,因为我将其初始化为小数点后的数字比双精度变量可以处理的数字更多。这真的是发生了什么,还是正在发生的其他事情?
BONUS:如果你还没有上述的偏头痛,这里还有另外一个混乱的来源......
>> 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!!!
答案 0 :(得分:62)
他们是双打的。 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)
这是展示它的另一种方式。这里有两个非常接近的双打。
x0 = exp(1)
x1 = x0 + eps(x0)
vpa(x0)
和vpa(x1)
应该会产生与第16位数字相差很多的输出。但是,您应该无法创建双精度值x
,以便vpa(x)
生成一个介于vpa(x0)
和vpa(x1)
之间的十进制表示。
(更新:Amro指出您可以使用fprintf('%bx\n', x)
以十六进制格式显示基础二进制值的精确表示。您可以使用它来确认文字映射到同一个double。)
我怀疑vpa()
的行为方式是因为它将输入视为精确值,并且多态支持符号工具箱中比双精度更高精度的其他Matlab类型。这些值需要通过数字文字以外的方式初始化,这就是sym()
将字符串作为输入而vpa(exp(1))
与vpa(sym('exp(1)'))
不同的原因。
有意义吗?抱歉长篇大论。
(注意我没有符号工具箱,因此我无法自己测试vpa()
。)
答案 1 :(得分:3)
第一:
看来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,依此类推。< / p>
在2 ^ n到2 ^ n + 1范围内,作为数字分数的间隔为2 ^ n-52。因此,将数字四舍五入到最接近的可表示数字(机器epsilon)时,最大相对舍入误差为2 ^ −53。
所以在您的情况下,n = 1(2 ^ 1 <= num <= 2 ^ 2)的间距是2 ^ -51,
我认为可以安全地假设用于显示数字的sprintf和sprintf算法是棘手的,并且MATLAB Double类型基于IEEE标准,
关于VPA:
vpa使用保护数字来保持精度
数字功能的值指定所使用的有效数字的最小数量。在内部,vpa可以使用比数字指定数量更多的数字。这些额外的数字称为保护数字,因为它们可以防止后续计算中的舍入错误。
使用四个有效数字在数值上约为1/3。
a = vpa(1/3, 4)
a =
0.3333
使用20位数字近似结果a。结果表明,在计算a时,工具箱内部使用了四位以上的数字。由于舍入错误,结果中的最后一位数字不正确。
vpa(a, 20)
ans =
0.33333333333303016843
您可能会遇到的问题是由于间距,gaurd位数算法和舍入问题
例如,使用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位