我试图理解MATLAB中基本算术运算的舍入误差,我遇到了以下奇怪的例子。
(0.3)^3 == (0.3)*(0.3)*(0.3)
ans = 0
我想确切知道如何计算左侧。 MATLAB documentation表明对于整数幂,使用“通过平方取幂”算法。
“矩阵功率。如果p是标量,则X ^ p是幂p的X.如果p是整数,则通过重复平方来计算功率。”
所以我假设(0.3)^3
和(0.3)*(0.3)^2
会返回相同的值。但这种情况并非如此。如何解释舍入误差的差异?
答案 0 :(得分:5)
我对MATLAB一无所知,但我在Ruby中尝试过它:
irb> 0.3 ** 3
=> 0.026999999999999996
irb> 0.3 * 0.3 * 0.3
=> 0.027
根据Ruby source code,如果左侧操作数是浮点数,则取幂运算符将右侧操作数转换为浮点数,然后调用标准C函数pow()
。 float
函数的pow()
变体必须实现更复杂的算法来处理非整数指数,这将使用导致舍入错误的操作。也许MATLAB的工作方式类似。
答案 1 :(得分:4)
有趣的是,标量^
似乎是使用pow
实现的,而矩阵^
是使用square-and-multiply实现的。即:
octave:13> format hex
octave:14> 0.3^3
ans = 3f9ba5e353f7ced8
octave:15> 0.3*0.3*0.3
ans = 3f9ba5e353f7ced9
octave:20> [0.3 0;0 0.3]^3
ans =
3f9ba5e353f7ced9 0000000000000000
0000000000000000 3f9ba5e353f7ced9
octave:21> [0.3 0;0 0.3] * [0.3 0;0 0.3] * [0.3 0;0 0.3]
ans =
3f9ba5e353f7ced9 0000000000000000
0000000000000000 3f9ba5e353f7ced9
通过在gdb下运行octave并在pow
中设置断点来确认。
在matlab中也是如此,但我无法真正验证。
答案 2 :(得分:3)
这是一个小测试程序,它遵循Apple Libm-2026
中来自pow()
的系统Source/Intel/xmm_power.c
在这种情况下的作用:
#include <stdio.h>
int main() {
// basically lines 1130-1157 of xmm_power.c, modified a bit to remove
// irrelevant things
double x = .3;
int i = 3;
//calculate ix = f**i
long double ix = 1.0, lx = (long double) x;
//calculate x**i by doing lots of multiplication
int mask = 1;
//for each of the bits set in i, multiply ix by x**(2**bit_position)
while(i != 0)
{
if( i & mask )
{
ix *= lx;
i -= mask;
}
mask += mask;
lx *= lx; // In double this might overflow spuriously, but not in long double
}
printf("%.40f\n", (double) ix);
}
这打印出0.0269999999999999962252417162744677625597
,这与我在Matlab中获得.3 ^ 3
和在Python中获得.3 ** 3
的结果一致(我们知道the latter just calls this code)。相比之下,.3 * .3 * .3
对我来说是0.0269999999999999996946886682280819513835
,如果你只是要求将0.027
打印到那么多的小数位,这就是你得到的同样的事情,因此可能是最接近的两倍。
所以有算法。我们可以准确地找出每个步骤设置的值,但是考虑到不同的算法,它会转到一个非常小的数字并不太令人惊讶。
答案 3 :(得分:3)
感谢@Dougal我发现了这个:
#include <stdio.h>
int main() {
double x = 0.3;
printf("%.40f\n", (x*x*x));
long double y = 0.3;
printf("%.40f\n", (double)(y*y*y));
}
给出:
0.0269999999999999996946886682280819513835
0.0269999999999999962252417162744677625597
这种情况很奇怪,因为使用更多数字的计算会产生最差的结果。这是因为无论如何,初始数字0.3用几位数近似,因此我们从一个相对“大”的错误开始。在这种特殊情况下,发生的事情是少数位的计算给出另一个“大”错误,但符号相反......因此补偿了初始错误。相反,使用更多数字的计算会产生第二个较小的错误,但第一个仍然存在。
答案 4 :(得分:-7)
阅读Goldberg的"What Every Computer Scientist Should Know About Floating-Point Arithmetic"(这是Oracle的转载)。明白了。浮点数是不实数的微积分。对不起,没有TL; DR版本可用。