JavaScript精度丢失
短信控制台-时延分析表格
试一下
精度丢失的典型问题
震惊~!
震惊 X 2~!
---大整数运算
Java和Python
不仅在 JavaScript 中存在这个问题,所有的支持二进制浮点数运算(绝大部分都是 IEEE 754的实现)的系统都存在这个现象。
精度丢失的原因
- Javascript采用了IEEE-754浮点数表示法
- 二进制表示法,可以精确地表示分数,比如1/2,1/8,1/1024
- 我们常用的分数都是十进制分数1/10,1/100等。
- 二进制浮点数不能精确的表示类似0.1这样的简单的数字
在有限的存储空间下,绝大部分的十进制小数都不能用二进制浮点数来精确表示
十进制中,科学计数法的形式是:
二进制中,科学计数法就是:
在有限的存储空间下,十进制小数 0.1 无论如何也不能用这种形式来表示
二进制的「科学计数法」
双精度存储(double precision)
- 1位用来表示符号位(sign)
- 11位用来表示指数(exponent)
- 52位表示尾数(fraction)
比如对于0.5,就可以表示成sign = 0, exponent = -1, fraction = 1
IEEE 754对表示方法做了优化
- fraction必须是1-2之间的一个小数,这样fraction就可以表示53位而不是52位数:
比如原数为0.5, 那么实际上会表示成
- exponent的实际值是exponent-offset,单精度的offset为127
比如-1会表示成126,因为126-offest(127) = -1
IEEE 754对表示方法做了优化
比如0.5的单精度表示为:
0 01111110 00000000000000000000000
其中:
- 0为符号位
- 01111110为指数位,十进制为126, 所以实际的exponent为126 - 127 = -1
- 00000000000000000000000 为fraction,十进制为0
所以0.5f =
这种方法导致很多浮点数不能精确表示
0.1的浮点数表示为:
0 01111011 10011001100110011001101
实际上值为:
0.1 => 0.0001 1001 1001 1001…(无限循环)
0.2 => 0.0011 0011 0011 0011…(无限循环)
双精度浮点数的小数部分最多支持 52 位
0.0100110011001100110011001100110011001100110011001100
(十进制)
0.30000000000000004
0.1 + 0.2 的问题
大整数的精度丢失
尾数位最大是52位,因此 JS 中能精准表示的最大整数是 Math.pow(2, 53),十进制即 9007199254740992。
大于 9007199254740992 的可能会丢失精度:
解决方案
toFixed()的问题
IE6-10
chrome/firefox
toFixed()的问题
四舍五入 or 四舍六入五成双:
各版本浏览器的测试结果
当5后有数时,舍5入1;
当5后无有效数字时,需要分两种情况来讲:
①5前为奇数,舍5入1;
②5前为偶数,舍5不进。
9.655.toFixed(2)的过程
1. f = 2
3. x = 9.655
5. s = ''
8. a. n = 965
原因:
当 n = 965 时 n / 10^f – x = -0.004999999999999005
当 n = 966 时 n / 10^f – x = 0.005000000000000782
965更靠近0
b. m = '965'
c. i. k = 3
iii. a = '9', b = '65'
iv. m = a + '.' + b = '9.65'
9. Return s + m = '9.65'
9.955.toFixed(2)的过程
1. f = 2
3. x = 9.955
5. s = ''
8. a. n = 996
原因:
当 n = 995 时 n / 10^f – x = -0.005000000000000782
当 n = 996 时 n / 10^f – x = 0.005000000000000782
同样靠近0, 选择大的那个
b. m = '996'
c. i. k = 3
iii. a = '9', b = '96'
iv. m = a + '.' + b = '9.96'
9. Return s + m = '9.96'
我就是想要0.1 + 0.2 === 0.3 啊!
Math.formatFloat = function(f, digit) {
var m = Math.pow(10, digit);
return parseInt(f * m, 10) / m;
}
var numA = 0.1;
var numB = 0.2;
alert(Math.formatFloat(numA + numB, 1) === 0.3); //true
把需要计算的数字乘以 10 的 n 次幂,换算成计算机能够精确识别的整数,然后再除以 10 的 n 次幂
相关库
小学奥数相关
无限循环小数 => 分数 ?
纯循环小数
非纯循环小数
无限循环小数 => 分数 ?
重要!
-
不要用JavaScript来计算重要的高精度数据
-
不要用浮点数运算来进行条件判断
-
toFixed() 仅仅用来显示
参考内容
- https://modernweb.com/what-every-javascript-developer-should-know-about-floating-points/
- https://zh.wikipedia.org/wiki/IEEE_754
- http://0.30000000000000004.com/
- https://www.zhihu.com/question/20679634
- http://www.guokr.com/question/486196/
- http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
- http://www.ecma-international.org/ecma-262/5.1/
js-precision-loss
By christyma
js-precision-loss
- 695