JS 的数值
双精度浮点数
JS 用双精度浮点数 double 储存 Number
浮点数是一种对于实数的近似值的表示法
double 储存规则
正负 + 指数 + 精度
十进制 78.375
二进制1001 110.011
- 小数部分由十进制转为二进制,不一定可以转的很规整,例如十进制
0.1
被专为二进制0.0001100110011001100
(1100无限循环)。因此需要舍去或进位,从而形成误差 1001 110.011
科学记数法为1.001 110 011 * 2^6
。其中前者一定以1开头,所以可以只记录小数部分;同理后者底数一定是2,只记录指数部分即可。再加上正负数,就是 double 的三个部分了
精度部分
精度部分有52位,因此把指数放大,最大可以精确储存 2^53
的整数值(记得前一段被忽略的开头的1也算一位)Math.pow(2,53) === 9007199254740992
是 JS 可以储存的最大整数。再往大计算,就不会精确
指数部分
指数部分有11位,最大值为2^11-1 = 2047。由于指数有正有负,但有符号计算比无符号计算麻烦,因此存为无符号整数,其中分出一半表示负数部分,因此指数部分的值范围是 2^-1023 到 2^1024
。
::TODO:: 一个猜测:double 能表示的最大值 为 1 * 1.111111111(52位)* 2^1024
但是 JS 里 Math.pow(2,1024)
就返回了 Infinity
,可以理解为 Number 能表示的最大值其实小于 double 真正能表示的最大值?
数值的表示法
字面量和科学计数法两种
字面量
JS 有四种进制来表示字面量:二进制
、八进制
、十进制
和十六进制
- 十进制:没有前导0的数值
- 二进制:有前缀
0b
、0B
的数值 b = Binary - 八进制:有前缀
0o
、0O
的数值 o = Octal- 前导0也可以表示八进制
010
表示十进制的8
- 这个表示法容易造成混乱,已被 ES5 的严格模式、ES6 废弃。但为了兼容,浏览器依然支持这种表示
- 十六进制:有前缀
0x
、0X
的数值 x = Hex?
科学记数法
以下两种场景下会表示为科学计数法
- 小数点前有超过(大于等于)21位数字
1234567890123456789012 => 1.2345678901234568e+21
::TODO::此时整数超过了 Number 可精确表示的最大整数,可以看到e前边的小数精度不够,没有完整储存原本的数字 - 小数点后的0大于等于6个
0.0000001 => 1e-7
通过小写e
或者大写E
后边加一个整数,用来表示指数部分。
注意被转为科学计数法后,toString 方法也会输出带e的字符串
1234567890123456789012.toString() => '1.2345678901234568e+21'
一句话概括0.1+0.2!=0.3
JS 中 Number 全部用双精度浮点数 double 储存,在十进制小数部分转为二进制小数时,可能出现除不尽的情况,小数部分无限长(应该是无限循环了),限于double储存结构,只能储存有限位数,因此存下来的会是有误差的值。运算时也是二机制数字之间运算,因此结果是不精确的。
解决方案
分场景讨论这个问题
运算类
一个经典场景,元转分问题0.58 * 100 === 57.99999999999999
一般场景下分就是精度极限了,不会再往后有小数,因此可以 Math.round
四舍五入,拿到整数分Math.round(0.58 * 100) === 58
上边是一种把小数转为整数的方案。更通用一些,涉及运算都需要转为整数。
可能遇到科学计数法和最大数问题