当我对浮点数进行数学运算时,我从来不知道JavaScript到底发生了什么。我一直都很害怕使用小数,直到我尽可能地避开它们。但是,如果我知道在IEEE 754标准中幕后发生了什么,那么我将能够预测会发生什么;具有可预测性,我会更自信,更不可怕。
有人可以给我一个简单的解释(简单解释整数的二进制表示关于IEEE 754标准如何工作以及它如何产生这种副作用:0.1 + 0.2 != 0.3
?
非常感谢! :)
答案 0 :(得分:10)
0.1的小数分数不能在基数2中干净地表达
假设我们想在base-2中表示小数0.1。我们知道它等于1/10。在base-2中1除以10的结果是0.000110011001100...
,其具有重复的小数序列。
因此,在十进制形式中,实际上很容易干净地表示像0.1这样的数字,在base-2中你无法准确地表达基于10的有理数。您只能通过使用能够存储的位数来近似它。
简单地说,我们只有足够的存储空间来重现该数字的第一个,例如8个有效二进制数字。存储的数字为11001100(指数为11)。这转换回基数为2的0.000110011,十进制为0.099609375,而不是0.1。这是将0.1转换为理论浮点变量时会发生的错误量,该变量将基值存储为8位(不包括符号位)。
浮点变量如何存储值
IEEE 754的标准规定了一种用二进制编码实数的方法,带有符号和二进制指数。指数应用于二进制域,这意味着在转换为二进制之前不会移位小数点,之后就会这样做。
有不同大小的IEEE浮点数,每个浮点数都指定了基数使用了多少二进制数字以及指数的数量。
当你看到0.1 + 0.2 != 0.3
时,这是因为你实际上并没有在0.1或0.2上执行计算,而是在浮点二进制的这些数字的近似值上只有一定的精度。在将结果转换回十进制时,由于此错误,结果将不会精确为0.3。另外,结果甚至不等于0.3的二进制近似值。实际的错误量取决于浮点值的大小,因此使用了多少精度位。
四舍五入有时会有所帮助,但在这种情况下并非如此
在某些情况下,由于转换为二进制的精度损失而导致的计算错误将足够小,以便在从二进制转换回来的过程中从舍入值中消失,因此您甚至不会注意到任何差异 - 它将会看起来很有效。
IEEE浮点具有关于如何进行舍入的具体规则。
然而,对于0.1 + 0.2对0.3,舍入不会抵消错误。 添加0.1和0.2的二进制近似值的结果将与0.3的二进制近似值不同。
答案 1 :(得分:4)
如果您天真地将1/3转换为0.333(或任何有限数量的3),这与1/3 + 1/3 + 1/3!= 1的原因相同。 0.333 + 0.333 + 0.333 = 0.999,而非1。
在基数9(例如)中,1/3可以精确地表示为0.3 9 ,并且0.3 9 + 0.3 9 + 0.3 9 = 1.0 9 。 可以在基数9中精确表示的一些数字不能在基数10中精确表示,并且必须四舍五入到可以的数字。
类似地,某些数字不能在基数2中精确表示,但可以在基数10中表示,例如0.2。
0.2 10 是0.0011001100110011 ... 2
如果将其四舍五入为0.0011 2 ,则:
0.0011 2 + 0.0011 2 + 0.0011 2 + 0.0011 2 + 0.0011 2 < / sub> = 0.1111 2 ,而不是1.0000 2 。
(0.1111 2 是15/16)
由于计算机(至少是我们使用的计算机)以二进制方式进行算术运算,这会影响它们。
请注意,随着我们使用更多数字,结果的准确性会增加。 (0.33333333 10 + 0.33333333 10 + 0.33333333 10 = 0.99999999 10 ,这比0.999更接近正确答案<子> 10 子>)
由于这个原因,四舍五入的误差通常非常小。 double
存储大约15个十进制数字,因此相对误差大约为10 -15 (更确切地说,2 -52 )。
因为错误很小,除非通常不会产生影响:
==
或!=
)。比较非整数数字是否相等绝对是你应该避免的,但你可以在计算和其他比较(<
或>
)中使用它们而不会出现问题(除非你的程序要求很高)精度)。
答案 2 :(得分:2)
在javascript中我总是这样做(Math.abs(.1 + .2-.3)&lt; .000001)
我一直都这么认为...... .25披萨+ .25披萨!= .5披萨(当你切它时你会丢掉披萨)lol
答案 3 :(得分:1)
如果你想使用花车有信心,请记住它们至少有15位有效数字,这对于常见任务来说几乎总是足够的。
日常工作所需的有效位数因人而异,例如:工程师可能只使用3,经济学家可能会使用5,科学家可能会使用更多(或更少)。首先计算出你想要的有效位数(例如,你想要看到2,345,876,234美元还是2.3亿美元)。如果它低于5位有效数字,你可以安全地使用至少7位有效数字运算,并将结果四舍五入到最后所需的有效位数。
e.g。如果您只需要3位有效数字:
(0.1 + 0.2).toFixed(3) // 0.300
如果你总是使用至少两个比你需要更多的有效数字,那么最后四舍五入到所需数字,你就不会被JavaScript数字引入的微小错误所困扰。
答案 4 :(得分:0)
以下链接对此进行了解释 Floating Point Representation Error