考虑以下代码:

0.1 + 0.2 == 0.3  ->  false
0.1 + 0.2         ->  0.30000000000000004

为什么会出现这些错误?


当前回答

正常的算术是以10为基数的,所以小数表示十分、百分等。当你试图用二进制2为基数的算术表示浮点数时,你要处理的是半、四、八等。

在硬件中,浮点存储为整数尾数和指数。尾数表示有效数字。指数类似于科学记数法,但它使用的基数是2而不是10。例如,64.0将用尾数1和指数6表示。0.125将用尾数1和指数-3表示。

浮点小数必须加上2的负幂

0.1b = 0.5d
0.01b = 0.25d
0.001b = 0.125d
0.0001b = 0.0625d
0.00001b = 0.03125d

等等

在处理浮点运算时,通常使用误差增量而不是相等运算符。而不是

if(a==b) ...

你会使用

delta = 0.0001; // or some arbitrarily small amount
if(a - b > -delta && a - b < delta) ...

其他回答

你试过胶带解决方案了吗?

尝试确定错误发生的时间,并用简短的if语句修复它们,这并不漂亮,但对于某些问题,这是唯一的解决方案,这就是其中之一。

 if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
                    else { return n * 0.1 + 0.000000000000001 ;}    

我在c#的一个科学模拟项目中也遇到过同样的问题,我可以告诉你,如果你忽视蝴蝶效应,它会变成一条大胖龙,咬你一口**

想象一下,以10为基数,例如8位数的精度工作。您检查是否

1/3 + 2 / 3 == 1

并了解到这返回错误。为什么?好吧,作为真实的数字

1/3=0.333….和2/3=0.666。。。。

在小数点后八位截断,我们得到

0.33333333 + 0.66666666 = 0.99999999

当然,这与1.00000000正好相差0.00000001。


具有固定位数的二进制数的情况完全类似。作为实数,我们有

1/10=0.0001100110011001100…(底座2)

and

1/5=0.00111001100110011001…(底座2)

如果我们把这些截成七位

0.0001100 + 0.0011001 = 0.0100101

而另一方面,

3/10=0.010011001100110011…(基数2)

被截断为七位的值为0.0100110,两者相差0.0000001。


确切的情况稍显微妙,因为这些数字通常以科学符号存储。因此,例如,我们可以将其存储为1.10011*2^-4,而不是将1/10存储为0.0001100,这取决于我们为指数和尾数分配了多少位。这会影响计算的精度位数。

结果是,由于这些舍入错误,您根本不想在浮点数上使用==。相反,您可以检查它们的差值的绝对值是否小于某个固定的小数字。

已经发布了很多好的答案,但我想再补充一个。

并非所有数字都可以通过浮点数/双精度表示例如,在IEEE754浮点标准中,数字“0.2”将以单精度表示为“0.200000003”。

用于在引擎盖下存储实数的模型将浮点数表示为

即使您可以轻松键入0.2,FLT_RADIX和DBL_RADIX都是2;对于使用“IEEE二进制浮点运算标准(ISO/IEC Std 754-1985)”的带有FPU的计算机,不是10。

所以准确地表示这些数字有点困难。即使在没有任何中间计算的情况下显式指定此变量。

从Python 3.5开始,您可以使用math.isclose()函数来测试近似相等性:

>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> 0.1 + 0.2 == 0.3
False

为了好玩,我按照标准C99的定义玩了浮点数的表示,并编写了下面的代码。

代码以3个独立的组打印浮点的二进制表示

SIGN EXPONENT FRACTION

然后,它打印一个和,当以足够的精度求和时,它将显示硬件中真正存在的值。

因此,当你写float x=999…时,编译器会将该数字转换为函数xx打印的位表示,这样函数yy打印的和就等于给定的数字。

事实上,这个总数只是一个近似值。对于数字999999999,编译器将在浮点的位表示中插入数字1000000000

代码之后,我附加了一个控制台会话,在该会话中,我计算硬件中真正存在的两个常量(减去PI和999999999)的项和,并由编译器插入其中。

#include <stdio.h>
#include <limits.h>

void
xx(float *x)
{
    unsigned char i = sizeof(*x)*CHAR_BIT-1;
    do {
        switch (i) {
        case 31:
             printf("sign:");
             break;
        case 30:
             printf("exponent:");
             break;
        case 23:
             printf("fraction:");
             break;

        }
        char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;
        printf("%d ", b);
    } while (i--);
    printf("\n");
}

void
yy(float a)
{
    int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
    int fraction = ((1<<23)-1)&(*(int*)&a);
    int exponent = (255&((*(int*)&a)>>23))-127;

    printf(sign?"positive" " ( 1+":"negative" " ( 1+");
    unsigned int i = 1<<22;
    unsigned int j = 1;
    do {
        char b=(fraction&i)!=0;
        b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);
    } while (j++, i>>=1);

    printf("*2^%d", exponent);
    printf("\n");
}

void
main()
{
    float x=-3.14;
    float y=999999999;
    printf("%lu\n", sizeof(x));
    xx(&x);
    xx(&y);
    yy(x);
    yy(y);
}

这里是一个控制台会话,我在其中计算硬件中存在的浮点值的实际值。我使用bc打印主程序输出的项的总和。可以将该和插入python-repl或类似的内容中。

-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../terra1/stub
@ ./a.out
sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1
sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0
negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1
positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
-- .../terra1/stub
@ bc
scale=15
( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
999999999.999999446351872

就是这样。999999999的值实际上是

999999999.999999446351872

您也可以通过bc检查-3.14也受到干扰。不要忘记在bc中设置比例因子。

显示的金额是硬件内部的金额。通过计算它获得的值取决于设置的比例。我确实将比例因子设置为15。数学上,以无限的精度,它似乎是1000000000。