考虑以下代码:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
为什么会出现这些错误?
考虑以下代码:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
为什么会出现这些错误?
当前回答
不,不破,但大多数小数必须近似
总结
浮点运算是精确的,不幸的是,它与我们通常的以10为基数的数字表示法不太匹配,所以我们经常给它的输入与我们写的略有不同。
即使是像0.01、0.02、0.03、0.04…0.24这样的简单数字也不能精确地表示为二进制分数。如果你数到0.01、.02、.03…,直到你数到0.25,你才能得到以2为底的第一个分数。如果你尝试使用FP,那么你的0.01会稍微有点偏差,所以要将其中的25个相加到一个精确的0.25,就需要一长串的因果关系,包括保护位和舍入。很难预测,所以我们举手说“FP不准确”,但事实并非如此。
我们不断地给FP硬件一些在基数10中看似简单但在基数2中却是重复的分数。
这是怎么发生的?
当我们用十进制书写时,每个分数(特别是每个终止的小数)都是形式的有理数
a/(2n x 5m)
在二进制中,我们只得到2n项,即:
a/2n
所以在十进制中,我们不能表示1/3。因为基数10包括2作为素因子,所以我们可以写成二进制分数的每个数字也可以写成基数10的分数。然而,我们写为10进制分数的任何东西都很难用二进制表示。在0.01、0.02、0.03…0.99的范围内,只有三个数字可以用我们的FP格式表示:0.25、0.50和0.75,因为它们是1/4、1/2和3/4,所有的数字都只使用2n项。
在base10中,我们不能表示1/3。但在二进制中,我们不能做1/10或1/3。
因此,虽然每一个二进制分数都可以用十进制来表示,但反过来却不正确。事实上,大多数小数在二进制中重复。
处理它
开发人员通常被要求进行<epsilon比较,更好的建议可能是舍入为整数值(在C库中:round()和round f(),即保持FP格式),然后进行比较。舍入到特定的小数部分长度可以解决大多数输出问题。
此外,在实数运算问题(FP是在早期昂贵的计算机上为之发明的问题)上,宇宙的物理常数和所有其他测量值只为相对较少的有效数字所知,因此整个问题空间无论如何都是“不精确的”。FP“精度”在这种应用中不是问题。
当人们尝试使用FP进行计数时,整个问题就真的出现了。它确实可以做到这一点,但前提是你坚持使用整数值,这会破坏使用它的意义。这就是为什么我们拥有所有这些小数软件库的原因。
我喜欢克里斯的披萨回答,因为它描述了实际问题,而不仅仅是关于“不准确”的通常手写。如果FP只是“不准确”,我们可以修复它,而且几十年前就已经做到了。我们没有这样做的原因是因为FP格式紧凑快速,是处理大量数字的最佳方式。此外,这也是太空时代和军备竞赛以及早期使用小型内存系统解决速度非常慢的计算机的大问题的尝试所留下的遗产。(有时,单个磁芯用于1位存储,但这是另一回事。)
结论
如果您只是在银行数豆子,那么首先使用十进制字符串表示的软件解决方案工作得非常好。但你不能这样做量子色动力学或空气动力学。
其他回答
这些奇怪的数字之所以出现,是因为计算机使用二进制(以2为基数)数字系统进行计算,而我们使用十进制(以10为基数)。
大多数分数不能用二进制或十进制或两者精确表示。结果-四舍五入(但精确)的数字结果。
可以在数字计算机中实现的浮点数学必须使用实数的近似值及其运算。(标准版文件长达50多页,并有一个委员会处理其勘误表和进一步完善。)
这种近似是不同类型的近似的混合,每一种都可以被忽略或仔细考虑,因为其偏离精确性的特定方式。它还涉及到许多硬件和软件层面的明确例外情况,大多数人都会走过来假装没有注意到。
如果您需要无限精度(例如,使用数字π,而不是其许多较短的替代项之一),您应该编写或使用符号数学程序。
但是,如果您同意浮点数学有时在值和逻辑上是模糊的,错误可能会很快累积,并且您可以编写需求和测试来考虑这一点,那么您的代码可以经常通过FPU中的内容。
从Python 3.5开始,您可以使用math.isclose()函数来测试近似相等性:
>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> 0.1 + 0.2 == 0.3
False
简而言之,这是因为:
浮点数不能以二进制精确表示所有小数
因此,就像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
一些统计数据与这个著名的双精度问题有关。
当使用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%)。