众所周知的是,计算机中对十进制数字的运算是先把十进制数字转换为二进制再进行运算的(至于为什么,呃,如果我没记错的话,大致是因为早期计算机只支持二进制?正如之前看到的一句话,二进制是世界的本源)。对于二进制浮点数的转换而言,还牵扯到了一个IEEE754(二进制浮点数算术标准)。
十进制小数转化为二进制小数的具体做法是,将十进制小数乘以 2,得到积后将取出整数部分,再将剩余的小数部分继续乘以 2,反复循环上述过程(乘二取整),直至小数部分为零或达到所要求的精度为止。举个例子,0.5 转换为二进制是 0.1,刚刚好可以既快且准地表示出来。而 0.1 转换为二进制后为 0.0001100110011…,是一个无限循环的二进制小数。又由于位数的限制( JS 使用的是 64 位双精度浮点数编码,最多只有 52 位有效数字,见下图),所以从第 53 位后的位数会被舍掉(如果是 1 就向前一位进 1,如果是 0 就舍弃),造成了浮点数精确度损失。因此一部分二进制小数是不等于原来的十进制小数的!
至于解决办法,最好的自然是避免对浮点数进行比较操作,不然的话也可以界定浮点数的精确度,使用 toFixed()
保留小数点前几位,或者直接借用第三方库(刚才看了下有好几个这类库,虽然我没用过)。
这里还有一个问题,0.1 + 0.1
是等于 0.2 的,很奇怪,这时候就没有精度损失了?Segmentfault 上有人说是:
两个有舍入误差的值在求和时,相互抵消了,但这种“负负得正,相互抵消”不一定是可靠的,当这两个数字是用不同长度数位来表示的浮点数时,舍入误差可能不会相互抵消。
说得也不是很确切就是了,但也没有看到更多的答案了。
就先粗略地做这些笔记吧,以后再有更多的认识再补充。