考虑以下代码:

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

为什么会出现这些错误?


当前回答

由于这篇文章对当前的浮点实现进行了一般性的讨论,我想补充一下,有一些项目正在解决它们的问题。

看看https://posithub.org/例如,它展示了一种称为posit(及其前身unum)的数字类型,它承诺以更少的比特提供更好的精度。如果我的理解是正确的,它也解决了问题中的问题。非常有趣的项目,背后的人是数学家约翰·古斯塔夫森博士。整个过程都是开源的,用C/C++、Python、Julia和C#实现了许多实际的实现(https://hastlayer.com/arithmetics).

其他回答

一些统计数据与这个著名的双精度问题有关。

当使用0.1(从0.1到100)的步长将所有值(a+b)相加时,精度误差的概率约为15%。请注意,该错误可能会导致稍大或稍小的值。以下是一些示例:

0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)

当使用0.1(从100到0.1)的步长减去所有值(a-b,其中a>b)时,我们有大约34%的精度误差。以下是一些示例:

0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)

*15%和34%确实是巨大的,所以当精度非常重要时,请始终使用BigDecimal。使用2个十进制数字(步骤0.01),情况会进一步恶化(18%和36%)。

简而言之,这是因为:

浮点数不能以二进制精确表示所有小数

因此,就像10/3不精确地存在于基数10中(它将是3.33……重复出现)一样,1/10也不存在于二进制中。

那又怎么样?如何处理?有什么解决办法吗?

为了提供最佳解决方案,我可以说我发现了以下方法:

parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3

让我解释一下为什么这是最好的解决方案。正如上面提到的其他答案一样,使用现成的Javascript toFixed()函数来解决问题是一个好主意。但很可能你会遇到一些问题。

假设你将两个浮点数相加,如0.2和0.7,这里是:0.2+0.7=0.8999999999999999。

您的预期结果是0.9,这意味着您需要一个精度为1位数的结果。因此,您应该使用(0.2+0.7).tfixed(1)但是不能只给toFixed()一个特定的参数,因为它取决于给定的数字,例如

0.22 + 0.7 = 0.9199999999999999

在本例中,您需要2位精度,因此它应该为Fixed(2),那么,适合每个给定浮点数的参数应该是什么?

你可以说在每种情况下都是10:

(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"

该死你打算怎么处理那些9后不需要的零?现在是将其转换为浮动的时候了,以实现您的愿望:

parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9

既然找到了解决方案,那么最好将其作为如下函数提供:

function floatify(number){
           return parseFloat((number).toFixed(10));
        }
    

让我们自己试试吧:函数floatify(数字){return parseFloat((number).toFixed(10));}函数addUp(){var number1=+$(“#number1”).val();var number2=+$(“#number2”).val();var expectedResult=number1+number2;var expectedResult=浮动(number1+number2);$(“#意外结果”).text(意外结果);$(“#expectedResult”).text(expectedResult);}addUp();输入{宽度:50px;}#预期结果{颜色:绿色;}#未预期结果{颜色:红色;}<script src=“https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js“></script><input id=“number1”value=“0.2”onclick=“addUp()”onkeyup=“addUp()”/>+<input id=“number2”value=“0.7”onclick=“addUp()”onkeyup=“addUp()”/>=<p>预期结果:<span id=“expectedResult”></span></p><p>意外结果:<span id=“expectedResult”></span></p>

您可以这样使用:

var x = 0.2 + 0.7;
floatify(x);  => Result: 0.9

正如W3SCHOOLS所建议的,还有另一种解决方案,您可以通过乘法和除法来解决上述问题:

var x = (0.2 * 10 + 0.1 * 10) / 10;       // x will be 0.3

请记住,(0.2+0.1)*10/10根本不起作用,尽管看起来是一样的!我更喜欢第一种解决方案,因为我可以将其作为一个函数应用,将输入浮点转换为精确的输出浮点。

仅供参考,乘法也存在同样的问题,例如0.09*10返回0.8999999999999999。应用flotify函数作为解决方法:flotify(0.09*10)返回0.9

我的解决方法:

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}

精度是指在加法过程中要保留小数点后的位数。

为了好玩,我按照标准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。

正常的算术是以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) ...