C ++加倍:除以100会导致非常小的错误

时间:2010-08-11 17:55:35

标签: c++ floating-point

我在此代码段中遇到了一组特定值的问题。

double inputs[] = {0, -546543, 99015, 6750, 825, 2725, 70475, 
    50950, 42200, 6750, 26925, 16125, 134350, 10075, 79378};
double result = 0;
for (int i = 0; i < 15; i++) {
    result += inputs[i]/100;
}

我预计result的最终值为0.如果我将除法除以100则为。但是当我将每个值除以100之后再添加到result之前,我最终得到了-6.8212102632969618e-013。

我对浮点运算有很多不了解。我知道它不能保证完全精确。但是这个数据集似乎没有任何异常 - 没有非常大或非常小的值 - 所以我很惊讶计算出错了。

有人可以向我解释一下,并就如何避免这个问题提出任何建议吗?我提出的代码是简化的;在实际的代码中,我不能只是除以100,我不能很容易地将数字作为整数添加,然后将它们分开。

任何建议都将受到赞赏。

5 个答案:

答案 0 :(得分:5)

  

我不能很容易地将数字添加为整数并稍后将它们分开。

为什么不呢?这听起来就像是你问题的解决方案。添加整数并分割一次可能比添加浮点数和分割得快得多。

每次除以100时,你只是累积错误(因为100不是2的幂)。您的所有数字都可以在double中完全表示,但是当您将它们分开时,它们不是 - 因此您的错误。除了修改算法之外,你无法做任何事情。

在你的情况下,由于你除以100,你可以将最终总和四舍五入到最接近的第100位,并得到正确的结果。

答案 1 :(得分:2)

你可能不会考虑的是,double使用base-2作为内部表示。因此,虽然乍一看这些值看起来不是很精确或很小,但使用base-2可能难以表示它们。

以1/3 = 0.333为例。您无法使用base-10中的有限位数精确描述该值(除非将其存储为分数,但让我们将其留在一边)。对于base-2中的某些值也是如此,它们看起来就像10号基数一样好。

0.01就是这样一个例子。要将它存储在base-2中,您需要无限数量的数字,因此1.0 / 100.0不精确(如float或double)。你将基数为10的整数除以100,所以你可能会看到它导致的结果。

某些编程语言提供了基数为10的浮点类型(例如C#中的十进制),用于财务计算。它更适合我们习惯的基础10,但对于计算机来说更贵。当然还有无法表示的数字(1/3,......)。

答案 2 :(得分:1)

阅读The Floating-Point Guide,然后您就会明白:

  

在内部,计算机使用格式   (二进制浮点)不能   准确地代表一个数字   总共0.1,0.2或0.3。

基本上,每个中间值(除以100后)都有舍入误差。最终结果也保留错误并不罕见。

  

十进制数字不能准确   代表一个像1/3的数字,所以你   必须舍入到0.33之类的东西 -   你不要指望0.33 + 0.33 +   0.33加起来,或者 - 你呢?

     

我该怎么做才能避免这个问题?

     

这取决于你正在做什么样的计算。

     
      
  • 如果你真的需要你的结果准确加起来,特别是当你使用钱时:使用特殊的十进制数据类型。
  •   
  • 如果您只是不想看到所有这些额外的小数位:只需将结果格式化为显示的四舍五入的固定位数。
  •   
  • 如果没有可用的十进制数据类型,另一种方法是使用整数,例如:完全以美分进行货币计算。但这是更多的工作,并有一些缺点。
  •   

请注意,第一种情况(需要特定的十进制结果)在大多数情况下都不适用

答案 3 :(得分:1)

通常,在比较来自不同实现的浮点数时,您可以建立某种通过/失败标准来确定相等(或零)。像10 ^ -10表示双倍精度,10 ^ -6表示单精度。例如,如果您将Matlab结果与应用程序代码生成的结果进行比较,并且确认不可避免的精度错误,则需要一些此类基准测试。

答案 4 :(得分:0)

如果必须使用浮点数,您可能需要:

  • 首先分别添加正数和负数,然后减去总和

  • 在计算总和后将其除以100

据我所知,不能保证内存和CPU中浮点寄存器的宽度大小相同(即当你转移到宽度较小的位置时可能会发生一些舍入),并且你的值是存储取决于编译器生成的机器代码。