为何二进制浮点数不准确?IEEE 754 二进制浮点数舍入规则

    我被代码海扁署名-非商业-禁演绎
    阅读 4:36·字数 1384·发布 
    Bilibili 空间
    关注 960

    本节使用的“不准确”一词可能会被解释为“精度丢失”,如果从二进制的角度来讲,一些无限小数的数位的确丢失了,但从开发人员的角度看,数字更像是失去了准确性。

    二进制浮点数不准确

    二进制浮点数存在不准确的情况,尤其在计算的结果中,你可能会看到意想不到的数字。让我们来看一个经典的案例,在 JavaScript 中计算0.10.2的和,输出结果并不是理所当然的0.3,而是0.30000000000000004

    floating.js
    console.log(`0.1 + 0.2 = ${parseFloat('0.1') + parseFloat('0.2')}`)
    0.1 + 0.2 = 0.30000000000000004

    上面的示例说明,浮点数0.10.20.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?!

    当然,你也可以把不准确的原因,归于人们不习惯使用由01组成的二进制数字,一旦没有了十进制和二进制之间的转换,也就没有了不准确的问题。

    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,否则同样直接丢弃,这将保证无论是正数还是负数都会更接近负无穷。

    源码

    floating.js·codebeatme/programming-reference·GitHub