C++浮点数精度

10

可能是重复问题:
浮点数不精确的例子

double a = 0.3;
std::cout.precision(20);
std::cout << a << std::endl;

结果:0.2999999999999999889

double a, b;
a = 0.3;
b = 0;
for (char i = 1; i <= 50; i++) {
  b = b + a;
};
std::cout.precision(20);
std::cout << b << std::endl;

结果:15.000000000000014211

所以,'a'比应该小。 但是如果我们将'a'取50次 - 结果将比应该大。

为什么会这样? 如何在这种情况下得到正确的结果?


5
去阅读相关内容。浮点数问题需要仔细研究,以免犯下危险的错误。 - Artelius
1
为了获得精确值,请使用整数(或某个大数库)。 const int acc = 100; int tmp, a = 30 / acc, b = 0; for (char i = 1; i <= 50; i++) b = b + a; std::cout << int(b / acc) << "."; tmp = b % acc; if (tmp < 10) std::cout << "0"; std::cout << int(tmp);为了加快速度,可以使用2的幂作为acc,这样*,/和%就可以转换为<<,>>和&。 - Spektre
5个回答

18

为了获得正确的结果,请不要将精度设置大于此数值类型可用的精度:

#include <iostream>
#include <limits>
int main()
{
        double a = 0.3;
        std::cout.precision(std::numeric_limits<double>::digits10);
        std::cout << a << std::endl;
        double b = 0;
        for (char i = 1; i <= 50; i++) {
                  b = b + a;
        };
        std::cout.precision(std::numeric_limits<double>::digits10);
        std::cout << b << std::endl;
}

即使使用这种方法,如果那个循环运行了5000次而不是50次,累积错误仍然会出现--这只是浮点数运算的特性。


5
对于numeric_limits,+1并不足以让它们得到应有的认可。 - Kornel Kisielewicz

15

为什么会这样呢?

因为浮点数是以二进制存储的,其中0.3就像1/3在十进制中一样,是0.01001100110011001...循环的。 当你写下0.3时,实际上得到的是0.299999999999999988897769753748434595763683319091796875(无限二进制表示法四舍五入到53位有效数字)。

请记住,对于浮点数所设计应用程序,无法精确表示0.3并不是问题。浮点数的设计是用于:

  • 物理测量,通常只测量到4个有效数字,绝不超过15个。
  • 类似对数和三角函数这样的超越函数,本身就是近似值。

与其他误差来源相比,这些都比二进制-十进制转换无关紧要。

现在,如果你正在编写金融软件,其中$0.30意味着确切的$0.30,那就不同了。针对这种情况,有专门设计的十进制算术类。

那如何在这种情况下得到正确的结果呢?

将精度限制在15个有效数字通常足以隐藏“噪声”数字。 除非你实际上需要一个精确答案,在大多数情况下,这是最好的方法。


也许你可以在第一个段落的末尾添加:“……因为计算机无法存储无限值,所以十进制小数0.3的二进制表示形式的无限级数在某个位置被截断了。” - josch
如果不想使用十进制算术类,应该始终将货币存储在整数类型中(可能是自己的类),给它以分或“千分之一分”的单位,根据需要选择。这样,您始终可以控制如何处理舍入误差,这些误差只会在除法中出现,并且很容易量化。 - Edward LeBlanc

8

计算机使用二进制而不是十进制来存储浮点数。

许多在十进制看起来很普通的数字,比如0.3,在二进制中没有有限长度的精确表示。
因此,编译器选择最接近具有精确二进制表示的数字,就像你写0.33333代替1⁄3一样。

如果您添加许多浮点数,这些微小的差异会累加,导致意外的结果。


2
我认为对于这个问题的关键答案是,“你不能使用浮点数得到正确的结果。” :) - dash-tom-bang
3
你得到了正确的结果。可以承认,这不是你期望的结果。 - MSalters

1

问题不在于它更大还是更小,而是在于在二进制浮点数中精确存储“0.3”是物理上不可能的。

获得“正确”的结果的方法是不要显示20位小数。


-2

2
十进制和任意精度算术对于 OP 的目的很可能是不必要的。 - dan04

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接