为什么0.1加0.2不等于0.3

js小数精度

0.1+0.2不等于0.3?

https://www.javascriptc.com/books/nodejs-roadmap/javascript/floating-point-number-0.1-0.2.html

https://www.cnblogs.com/zhangziqiu/archive/2011/03/30/computercode.html

https://babbage.cs.qc.cuny.edu/IEEE-754.old/Decimal.html

存储方式

JavaScript使用Number类型表示数字(整数和浮点数),遵循 IEEE 754 标准通过双精度64位(8字节)来表示一个数字

64位 = 1位符号 + 11位指数 + 52位数字

  • 符号位: 0表示正数,1表示负数
  • 指数位: 科学计数法, e的n次方带上偏移, 实际指数位 = 指数位的的值 + 1023, 再转成 11 位二进制
  • 尾数位: 1.xx…xx, 第一位是1, 默认不写, 后面的xx有52位, 其中第 53 及之后的位会决定是否进 1
    • 第 53 位是 0, 后面的全部舍去
    • 第 53 位是 1
      • 如果第 54 位及后面任意一位是 1, 向上进 1
      • 如果第 54 位是 0
        • 如果 52 位是 0, 则舍去
        • 如果 52 位是 1, 则进 1

十/二进制转换

整数

B = kn * 2^n + … + k3 * 2^3 + k2 * 2^2 + k1 * 2^1 + k0 * 2^0

除2取余, 直到商为0, 倒序写

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
以10为例

10/2 = 5 ... 0
5/2 = 2 ... 1
2/2 = 1 ... 0
1/2 = 0 ... 1

10 = 2^3 + 2^1

10 = 1010

符号位 正数, 为0
偏移后的指数位 用科学计数法表示, 1.01*10^3, 相当于小数点左移3位, 还需要带上偏移, 偏移量为 2^(11-1)-1=1023, 1023+3=1026=2^10+2^1, 二进制表示为 10 000 000 010
(偏移量: 2^11 可以表示2048种状态, 可以表示[-1023 ~ 1024] 范围的指数, 使 指数 + 偏移量 = 一个非负整数, 就不用担心如何表示负数)
尾数位 1.01 隐藏高位1和小数点,  为 01, 后面补0到52位

在内存中存储:
0  10 000 000 010  0100000000000000000000000000000000000000000000000000

小数部分

B = k1 * 2^-1 + k2 * 2^-2 + … + kn * 2^-n

小数: 乘2取整, 直到不存在小数为止, 正序写

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
以0.1为例

0.1 * 2 = 0.2  取 0

0.2 * 2 = 0.4  取 0
0.4 * 2 = 0.8  取 0
0.8 * 2 = 1.6  取 1
0.6 * 2 = 1.2  取 1

0.2 * 2 = 0.4  取 0
0.4 * 2 = 0.8  取 0
0.8 * 2 = 1.6  取 1
0.6 * 2 = 1.2  取 1
...  

0.1 = 0.0 0011 0011 (0011无限循环)

符号位: 0
指数位: 1.1 0011(循环) * 2^-4, -4+1023=1019=1111111011(二进制)
尾数位: 1 0011 0011 (有循环, 截取到52位, 第53位进1舍0,精度丢失)

在内存中存储:
0 01111111011 1001100110011001100110011001100110011001100110011010

浮点数运算

  • 对阶(阶数不同也会精度丢失)
  • 求和
  • 规格化

对阶

浮点数加减首先要判断两数的指数位是否相同(小数点位置是否对齐),若两数指数位不同,需要对阶保证指数位相同。

对阶时遵守小阶向大阶看齐原则,尾数向右移位,每移动一位,指数位加 1 直到指数位相同,即完成对阶。

尾数右移 1 位之后最高位空出来, 补全方法:

  • 逻辑右移:最高位永远补 0
  • 算术右移:不改变最高位值,是 1 补 1,是 0 补 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
S  E            M
0  01111111011  1001100110011001100110011001100110011001100110011010 // 0.1
0  01111111100  1001100110011001100110011001100110011001100110011010 // 0.2

// 0.1 移动之前
0  01111111011  1001100110011001100110011001100110011001100110011010 

// 0.1 右移 1 位之后尾数最高位空出一位,(0 舍 1 入,此处舍去末尾 0)
0  01111111100   100110011001100110011001100110011001100110011001101(0) 

// 0.1 前面还有隐藏掉最高位1, 右移 1 位完成
0  01111111100  1100110011001100110011001100110011001100110011001101

尾数求和

1
2
3
  0.1100110011001100110011001100110011001100110011001101
+ 1.1001100110011001100110011001100110011001100110011010
 10.0110011001100110011001100110011001100110011001100111

规格化和舍入

产生进位,阶码需要 + 1

1
2
3
  S  E
  0  01111111100 + 1
= 0  01111111101

尾部进位 2 位,去除最高位默认的 1, 最低位进行舍入操作: 最低有效位上加 1,若为 0 则直接舍去,若为 1 继续加 1

1
2
3
4
  100110011001100110011001100110011001100110011001100111 // + 1
=  00110011001100110011001100110011001100110011001101000 // 去除最高位默认的 1
=  00110011001100110011001100110011001100110011001101000 // 最后一位 0 舍去
=  0011001100110011001100110011001100110011001100110100  // 尾数最后结果

最终存储

1
0  01111111101 0011001100110011001100110011001100110011001100110100

转成十进制

1
0.30000000000000004

原码, 反码, 补码

  • 一个数在计算机中的二进制表示形式, 叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0, 负数为1

比如,十进制中的数 +3 ,计算机字长为8位,转换成二进制就是00000011。如果是 -3 ,就是 10000011

  • 带符号位的机器数对应的真正数值称为机器数的真值
1
2
0000 0001的真值 = +000 0001 = +1
1000 0001的真值 = –000 0001 = –1
  • 正数的原码=反码=补码
1
[+1] = [00000001]原 = [00000001]反 = [00000001]补
  • 负数的符号位一值不变, 反码 = 原码除符号位全部取反, 补码=符号位不变反码+1
1
[-1] = [10000001]原 = [11111110]反 = [11111111]补
  • 为什么要有反码? 解决原码做减法的问题
  • 为什么要有补码? 解决了0的符号以及两个编码的问题

js number安全范围

1
2
Number.MAX_SAFE_INTEGER = 2^53 - 1  (52个1加上省略的, 共53个1)
Number.MIN_SAFE_INTEGER = -(2^53 - 1)

Number.MAX_SAFE_INTEGER 的计算:

  1. 符号位: 正数为 0
  2. 尾数位: 1.111… 111 (52 个 1 加上小数点前面隐藏的 1, 共 53)
  3. 指数位: 52 (小数点左移 52 位) + 1023 = 1075
  4. 对应的二进制为
1
0  100 0011 10011  111...111 (52个1)

总结

二制制转换, 对阶, 舍入 过程中都可能导致精度丢失

如何解决

  1. 将数字转成整数
  2. 用第三方库
    1. Math.js
    2. big.js
    3. decimal. js
build with Hugo, theme Stack, visits 0