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

发布于 2024-10-03 13:35:02 字数 2015 浏览 1 评论 0原文

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

我默认情况下,始终将 MATLAB 变量理解为双精度。因此,如果我要声明一个小数点后 20 位数字的变量:

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

我希望忽略最后 4 位数字,因为 浮点相对精度约为 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 传递给 可变精度算术函数 ">符号工具箱,告诉它使用 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!!!

This question arose out of something strange that I noticed after investigating this question further...

I always understood MATLAB variables to be double-precision by default. So, if I were to do something like declare a variable with 20 digits after the decimal point:

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

I would expect the last 4 digits to be ignored, since the floating-point relative accuracy is on the order of 10-16:

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

If I try to display the number with more than 16 digits after the decimal point (using either fprintf or sprintf), I get what I expect to see:

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

In other words, digits 17 through 20 are all 0.

But things get weird when I pass num to the variable precision arithmetic function in the Symbolic Toolbox, telling it to represent the number using 21 digits of precision:

>> vpa(num, 21)
ans =
2.71828182845904553488

WHAT?! Those last 4 digits have reappeared! Shouldn't they have been lost when the original number I entered was stored as a double-precision variable num? Since num is a double-precision variable when it is passed to vpa, how did vpa know what they were?

My best guess as to what is happening is that MATLAB internally represents num with more precision than a double since I initialized it to a number with more digits past the decimal point than a double-precision variable could handle. Is this really what is happening, or is something else going on?


BONUS: And here's an additional source of confusion if you don't already have a migraine from the above...

>> 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!!!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

尘曦 2024-10-10 13:35:02

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

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

更准确地说,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()。)

They're doubles. vpa() is simply choosing to display non-significant digits beyond the floating point relative accuracy, where printf() and disp() truncate or zero them out.

You're only getting your original four digits back out because the literal you chose to initialize num with just happens to be the exact decimal expansion of a binary double value, because it was copy and pasted from the output of the expansion of an actual double value from the other question. It won't work for other nearby values, as you show in your "BONUS" addendum.

More precisely, all numeric literals in Matlab produce values of type double. They get converted to the binary double value that is nearest to the decimal value they represent. In effect, digits in a literal beyond the limit of precision of the double type are silently dropped. When you copy and paste the output of vpa to create a new variable, as the other question's poster did with the e = ... statement, you're initializing a value from a literal, instead of dealing directly with the result of a previous expression.

The differences here are just in output formatting. I think what's going on is that vpa() is taking that double precision binary double and treating it as an exact value. For a given binary mantissa-exponent value, you can calculate the decimal equivalent to arbitrarily many decimal places. If you have a limited precision ("width") in the binary value, as you do with any fixed-size data type, only so many of those decimal digits are significant. printf() and Matlab's default display handle this by truncating the output or displaying non-significant digits as 0. vpa() is ignoring the limits of precision and continuing to calculate as many decimal places as you request.

Those additional digits are bogus, in the sense that if they were replaced by other values to produce a nearby decimal value, they would all get "rounded" to the same binary double value.

Here's a way to show it. These values of x are all the same when stored in doubles, and will all be represented the same by vpa().

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

Here's another way of demonstrating it. Here are two doubles that are very close to each other.

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

vpa(x0) and vpa(x1) should produce outputs that differ a lot past the 16th digit. However, you shouldn't be able to create a double value x such that vpa(x) produces a decimal representation that falls between vpa(x0) and vpa(x1).

(UPDATE: Amro points out that you can use fprintf('%bx\n', x) to display an exact representation of the underlying binary value in hex format. You can use this to confirm the literals map to the same double.)

I suspect vpa() behaves this way because it treats its inputs as exact values, and polymorphically supports other Matlab types from the Symbolic Toolbox that have more precision than doubles. Those values will need to be initialized by means other than numeric literals, which is why sym() takes a string as an input and vpa(exp(1)) differs from vpa(sym('exp(1)')).

Make sense? Sorry for the long-windedness.

(Note I don't have the Symbolic Toolbox so I can't test vpa() myself.)

守护在此方 2024-10-10 13:35:02

第一:

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

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

请参阅此链接了解更多信息

在 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 使用保护数字来保持精度

digits 函数的值指定使用的有效数字的最小数量。在内部,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 位

first :

it appears that sprintf and fprintf have different behavior on different versions of MATLAB
for example in MATLAB 2018 a

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

second :

Floating-Point Numbers

MATLAB® represents floating-point numbers in either double-precision or single-precision format. The default is double precision, but you can make any number single precision with a simple conversion function.

Double-Precision Floating Point

MATLAB constructs the double-precision (or double) data type according to IEEE® Standard 754 for double precision. Any value stored as a double requires 64 bits, formatted as shown in the table below:

Bits : 63
Usage : Sign (0 = positive, 1 = negative)

Bits : 62 to 52
Usage : Exponent, biased by 1023

Bits : 51 to 0
Usage : Fraction f of the number 1.f

refer to this link for more info

Between 252=4,503,599,627,370,496 and 253=9,007,199,254,740,992 the representable numbers are exactly the integers. For the next range, from 253 to 254, everything is multiplied by 2, so the representable numbers are the even ones, etc. Conversely, for the previous range from 2^51 to 2^52, the spacing is 0.5, etc.

The spacing as a fraction of the numbers in the range from 2^n to 2^n+1 is 2^n−52. The maximum relative rounding error when rounding a number to the nearest representable one (the machine epsilon) is therefore 2^−53.

so in your case where n=1 (2^1 <= num <= 2^2) the spacing is 2^-51 ,

i think it is safe to assume that sprintf and sprintf algorithms for showing numbers are tricky and MATLAB Double type is based on IEEE standard,


about VPA:

vpa Uses Guard Digits to Maintain Precision

The value of the digits function specifies the minimum number of significant digits used. Internally, vpa can use more digits than digits specifies. These additional digits are called guard digits because they guard against round-off errors in subsequent calculations.

Numerically approximate 1/3 using four significant digits.

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

Approximate the result a using 20 digits. The result shows that the toolbox internally used more than four digits when computing a. The last digits in the result are incorrect because of the round-off error.

vpa(a, 20)
ans =
0.33333333333303016843

the problem you may encounter is because of spacing , gaurd digits algorithm and round off problem ,

for example using matlab 2018 a :

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

but:

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

because the number is between 2^3 and 2^4 so the spacing is 2^-49 (= 1.77 e-15)
so the number is valid till 15th decimal place
and

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

because the number is between 2^6 and 2^7 so the spacing is 2^-46 (= 1.42 e-14)
so the number is valid till 14th decimal place

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文