考虑以下代码:

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

为什么会出现这些错误?


当前回答

想象一下,以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,这取决于我们为指数和尾数分配了多少位。这会影响计算的精度位数。

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

其他回答

我的解决方法:

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

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

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

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

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

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

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

这里的大多数答案都用非常枯燥的技术术语来解决这个问题。我想用正常人能够理解的方式来解决这个问题。

想象一下,你正试图把披萨切成薄片。你有一个机器人披萨切割机,可以将披萨切成两半。它可以将整个披萨减半,也可以将现有的披萨减半,但无论如何,减半总是准确的。

那台披萨切割机动作非常精细,如果你从一整块披萨开始,然后将其减半,然后继续每次将最小的披萨片减半,你可以在披萨片太小甚至无法实现高精度功能之前,将其减半53次。此时,您不能再将非常薄的切片减半,但必须按原样包含或排除它。

现在,你如何将所有的切片以这样一种方式分割,使其达到披萨的十分之一(0.1)或五分之一(0.2)?真的想一想,试着解决它。如果你手边有一个神话般的精密披萨切割机,你甚至可以尝试使用真正的披萨


当然,大多数有经验的程序员都知道真正的答案,那就是,无论你切得多细,都无法用这些切片拼凑出十分之一或五分之一的披萨。你可以做一个非常好的近似值,如果你把0.1的近似值和0.2的近似值相加,你会得到非常好的0.3的近似值。

对于双精度数字(允许您将披萨减半53次的精度),小于或大于0.1的数字分别为0.09999999999999999167332731531132594682276248931884765625和0.1000000000000000055511151231257827021181583404541015625。后者比前者更接近0.1,因此,如果输入值为0.1,数字解析器将倾向于后者。

(这两个数字之间的区别是“最小切片”,我们必须决定是否包含,这会引入向上的偏差,或者排除,这会带来向下的偏差。最小切片的技术术语是ulp。)

在0.2的情况下,数字都是相同的,只是放大了2倍。同样,我们赞成略高于0.2的值。

注意,在这两种情况下,0.1和0.2的近似值都有轻微的向上偏差。如果我们加上足够多的这些偏差,它们会将数字推离我们想要的越来越远,事实上,在0.1+0.2的情况下,偏差足够高,从而导致的数字不再是最接近0.3的数字。

特别是,0.1+0.2实际上是0.1000000000000000055511151231257827021181583404541015625+0.0200000000000000011102230246251565404236316680908203125=0.30000000000000000444089209850062616169452667236328125,而最接近0.3的数字实际上是0.29999999999988897769753748434595763683319091796875。


另外,一些编程语言还提供了披萨切割机,可以将披萨切成十分之一。虽然这种披萨切刀并不常见,但如果你有机会切到一个,那么你应该在切到十分之一或五分之一的披萨片非常重要的时候使用它。

(最初发布在Quora上。)

简而言之,这是因为:

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

因此,就像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

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