为何二进制浮点数不准确?IEEE 754 二进制浮点数舍入规则
本节使用的“不准确”一词可能会被解释为“精度丢失”,如果从二进制的角度来讲,一些无限小数的数位的确丢失了,但从开发人员的角度看,数字更像是失去了准确性。
二进制浮点数不准确
二进制浮点数存在不准确的情况,尤其在计算的结果中,你可能会看到意想不到的数字。让我们来看一个经典的案例,在 JavaScript 中计算0.1
与0.2
的和,输出结果并不是理所当然的0.3
,而是0.30000000000000004
。
console.log(`0.1 + 0.2 = ${parseFloat('0.1') + parseFloat('0.2')}`)
0.1 + 0.2 = 0.30000000000000004
上面的示例说明,浮点数0.1
,0.2
,0.3
,至少有一个是不准确的,当然,事实上他们每一个都无法准确。
二进制浮点数不准确的原因
二进制浮点数之所以会不准确,是因为我们习惯使用十进制,而十进制与二进制的数字并不是一一对应的,大部分十进制小数没有对应的二进制小数。
如果十进制数字只有整数部分,那么没有问题,你总能找到一个相同的二进制数字,只需要不断的加1
或减1
。
可一旦拥有小数部分,情况就变得完全不同了。如果把小数理解为对物体的划分,那么0.3
相当于把物体分为 10 份然后取走其中的 3 份,这样的事情在二进制中是无法做到的,因为二进制的小数只能将物体分为 2 份,每一份都对应了十进制的0.5
,虽然可以将 2 份划分为 4 份,甚至更进一步的划分下去,但最终你会发现,二进制没有等价于十进制0.3
的小数,他只能无限的接近0.3
,这也就是二进制浮点数不准确的原因。
最低位为 5 的十进制小数拥有对应的二进制小数
大部分十进制小数都找不到对应的二进制小数,除了以 5 结尾的十进制小数以外,比如十进制的0.5
对应了二进制0.1
。这可以被解释为,取出 10 份中的 5 份,与取出 2 份中的 1 份是等价的。
都怪人们不使用 0 和 1?!
当然,你也可以把不准确的原因,归于人们不习惯使用由0
和1
组成的二进制数字,一旦没有了十进制和二进制之间的转换,也就没有了不准确的问题。
IEEE 754 二进制浮点数舍入规则
由于存储空间有限,对于从十进制转换而来的二进制无限小数,IEEE 754 标准规定需要对其进行舍入,以便能够放入存储空间内。
IEEE 754 二进制浮点数就近舍入规则
IEEE 754 定义了四种舍入方式,默认采用就近舍入的方式,他类似于十进制的四舍六入五取偶。当被舍弃部分的最高位为0
时不需要进1
,这相当于四舍。当被舍弃部分的最高位为1
并且后面不全为0
时需要进1
,这相当于六入。当被舍弃部分的最高位为1
且后面全为0
时,则根据未舍弃部分的最低位来决定是否进1
,最低位为1
,需要进1
,最低位为0
,不需要进1
,这相当于五取偶。
假设,二进制小数0.000011000
被舍弃的部分是末尾的1000
,那么需要进1
,因为未舍弃部分的最低位为1
,舍入结果将是0.00010
。
IEEE 754 二进制浮点数向零舍入,向正无穷舍入,向负无穷舍入规则
除了就近舍入规则,IEEE 754 还定义了向零舍入,向正无穷舍入,向负无穷舍入,他们可用于计算范围边界。
向零舍入会直接丢弃需要舍弃的部分,这样无论是正数还是负数都会更接近于0
。
向正无穷舍入会直接丢弃负数需要舍弃的部分,如果正数被丢弃的部分含有1
则进1
,否则同样直接丢弃,这将保证无论是正数还是负数都会更接近正无穷。
向负无穷舍入会直接丢弃正数需要舍弃的部分,如果负数被丢弃的部分含有1
则进1
,否则同样直接丢弃,这将保证无论是正数还是负数都会更接近负无穷。