当我对浮点数进行数学运算时,我永远不明白 JavaScript 到底发生了什么。我一直非常害怕使用小数,以至于我尽可能避免使用小数。但是,如果我知道 IEEE 754 标准的幕后情况,那么我就能预测会发生什么;有了可预测性,我会更加自信,减少恐惧。
有人可以给我一个简单的解释(就像解释整数的二进制表示一样简单,比如IEEE 754 标准如何工作以及它如何产生这种副作用:0.1 + 0.2 != 0.3
?
非常感谢!:)
当我对浮点数进行数学运算时,我永远不明白 JavaScript 到底发生了什么。我一直非常害怕使用小数,以至于我尽可能避免使用小数。但是,如果我知道 IEEE 754 标准的幕后情况,那么我就能预测会发生什么;有了可预测性,我会更加自信,减少恐惧。
有人可以给我一个简单的解释(就像解释整数的二进制表示一样简单,比如IEEE 754 标准如何工作以及它如何产生这种副作用:0.1 + 0.2 != 0.3
?
非常感谢!:)
像 0.1 这样的小数不能以 2 为基数清晰地表达
假设我们要以 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/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 = 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更接近正确答案)
因此,四舍五入的误差通常非常小。Adouble
存储大约 15 个十进制数字,因此相对误差约为 10 -15(更准确地说,是 2 -52)。
因为误差很小,它通常不会产生影响,除非:
==
or !=
)。比较非整数的相等性绝对是您应该避免的事情,但是您可以在计算和其他比较(<
或>
)中毫无问题地使用它们(同样,除非您的程序需要非常高的准确性)。
in javascript i always do something like (Math.abs(.1+.2-.3)<.000001)
i always think like this... .25 pizza + .25 pizza != .5 pizza (you lose pizza when you cut it) lol
如果您想对使用浮点数有信心,请记住它们至少有 15 个有效数字,这对于常见任务几乎总是足够的。
日常工作所需的有效位数各不相同,例如工程师可能只使用 3,经济学家可能使用 5,科学家可能使用更多(或更少)。所以首先计算出你想要的有效位数(例如,你想看到 2,345,876,234 美元还是 23 亿美元 OK)。如果它少于 5 个有效数字,您可以安全地用至少 7 个有效数字进行算术运算,并在最后将结果四舍五入到所需的有效数字数。
例如,如果您只需要 3 个有效数字:
(0.1 + 0.2).toFixed(3) // 0.300
如果你总是使用至少比你需要的多两个有效数字,然后在末尾四舍五入到所需的数字,你就不会被 JavaScript 数字引入的微小错误所困扰。