浮点不精确的结果,小数分数不是太小 - 有没有办法克服?

时间:2018-04-15 16:12:51

标签: c matlab precision long-double

我有一段代码重复地减少从1开始的变量。经过两次迭代后,结果是不精确的,这是一个惊喜。

有效过程可以通过以下两行显示:

>> (1 - 0.1) - 0.9
0
>> (1 - 0.05 - 0.05) - 0.9
-1.110223024625157e-16

第二种情况的结果在Matlab和Octave中都不为零。

同样,以下C代码显示了问题:

#include <stdio.h>

void main () {
  double x1, x2;
  x1=1-0.05-0.05;
  x2=1-0.1;

  printf("x1 exact:%d, x2 exact:%d\n", x1==0.9, x2==0.9);
}

在Intel Xeon(R)CPU E5-2680上使用gcc版本4.7.0编译,结果是

x1 exact:0, x2 exact:1

表明第一次计算是不精确的,而第二次计算是精确的。

我可以通过使用long double(后缀&#34; L&#34;对于所有文字,以及声明为long double的变量)在C中克服此问题。这让我有点困惑:我认为不精确的结果可以解释为0.05在基数2中没有精确的表示;但是使用long double并不能改变这个事实......那么为什么结果会有所不同呢?

我真正想要的是在Matlab中克服它的方法。有什么想法吗?

有趣的是,在这两种情况下,同样计算的MS Excel结果都是准确的。

2 个答案:

答案 0 :(得分:2)

如您所知,0.10.9等十进制数字在二进制文件中没有精确的表示形式。所以如果你做这样的事情:

float f = 0.1;
if(f * 9 == 0.9)
    printf("exact\n");
else
    printf("inexact\n");

,或者如果您在原始问题中编写代码,它可能会打印&#34;确切&#34;,它可能会打印&#34;不精确&#34;,这取决于...各种各样的事情。在我看来,不值得花太多时间试图找出原因或原因。如果它打印出来&#34;不精确&#34;,好吧,我们知道为什么,它一开始并不准确。如果它打印&#34;确切&#34;,我们得到了#34;幸运&#34; - 某些不确定的地方相互取消了某些东西 - 或者其他东西 - 但我们不能依赖它,所以它不是很有趣。

由于我们不能指望它,我们必须编写在打印时执行适当舍入的代码,或者使用&#34;足够接近&#34;平等比较。一旦我们编写了该代码,它就能正常工作,无论具体的比较是否具有任何机会,似乎都有效。

答案 1 :(得分:1)

  

...结果是......表明第一次计算是不精确的,而第二次计算是精确的。

此推断不正确。 1 - .05 - .05 == .9为false且1 - .1 == .9为真的事实表明,计算1 - .05 - .05中出现的舍入错误不会产生等于.9的结果,而是舍入错误计算1 - .1中出现的结果会产生等于.9的结果。由于在源代码中使用.9的浮点结果本身不是.9,1 - .1等于.9这一事实并不意味着1 - .1是准确的。它只是意味着它与.9有相同的舍入误差。

在表达式1 - .1 == .9中,有三个舍入错误:

  • .1从源代码中的十进制数字转换为二进制浮点值时,结果不是.1,而是0.1000000000000000055511151231257827021181583404541015625。
  • .9从源代码中的十进制数字转换为二进制浮点值时,结果为0.90000000000000002220446049250313080847263336181640625。
  • 当前一个数字0.1000000000000000055511151231257827021181583404541015625从1中减去时,我们可以看到,因为它大于.1,结果应该低于.9,类似于.8999 ....但结果是0.90000000000000002220446049250313080847263336181640625。

恰好,舍入错误在双方都产生了相同的结果,因此对等式的测试评估为真。

在计算1 - .05 - .05时,舍入错误为:

    源代码中的
  • .05转换为0.05000000000000000277555756156289135105907917022705078125。
  • 1 - .05的结果为0.9499999999999999555910790149937383830547332763671875。
  • 1 - .05 - .05的结果为0.899999999999999911182158029987476766109466552734375`。

这里发生的事情是,在减去1 - .05时,碰巧附近的可表示值略小于.95。因此,当减去.05时,我们看到的略大于.05,我们从略低于.95的东西中减去略大于.05的东西。这种错误组合产生的数学结果足够.9,它更接近下一个较低的可表示值0.89999999999999911182158029987476766109466552734375,而不是0.90000000000000002220446049250313080847263336181640625。

当您使用long double进行类似的计算时,舍入误差恰好不同。在以这些方式检查浮点计算时,long double 不会优于double计算结果恰好落在您获得的相同结果上的频率理想的数学。在您的示例中可能已经发生了您选择的数字,但使用其他数字会产生有效的随机结果,就好像每个计算都有一个随机错误向上或向下一步。如果这些错误恰好在两个不同的计算中具有相同的净效应,那么这两个结果是相等的。如果这些错误恰好具有不同的净效应,则两个结果不相等。