你不想知道的JavaScript的数字的一切

870 阅读4分钟

可以去YouTube(不存在的网站)上看同名视频了解

你不想知道的JavaScript的数字的一切

为什么小数相加有精度偏差。

分两块理解:

一个数字如何被64位二进制保存。

**两个二进制位数字如何计算。**精度偏差主要发生在这里。

(快两个月没写博客了一直读webpack源码,先水一篇。)

webpack的文章太多了就不写了,JavaScript的数字有是有但都不深。

JavaScript使用双精度浮点64位类型IEEE754标准,Python和Java也是这个标准。

一个数字使用一个64位二进制来存储:

  • 第1位:正负值。

0正数,1负数。

  • 2~11位:指数。

有10位位数,可以表示0~2047之间的数(2^0+2^1+...+2^10)。 因为指数会有负值用来表示小数,所以最终要减去1023。

10000001001=2^10+2^1+2^0=1024+8+1=1033,1033-1023=10,所以最终指数P就是10
  • 12位:隐藏位。

永远为1,但数字0时为0。

  • 13~64位:有效数字。

最终数字由:正负值指数有效数字相乘决定。

指数决定了浮点在有效数字位的位置,指数越大整数就越大,指数越小小数就越大。

指数的默认位置在隐藏位。

Number(0.1).toString(2),可以这样看一个数字的隐藏位

整数

100

100.1

整数与小数公用52位有效位数,浮点前是整数后是小数。整数越大占用位数越多,小数的精度就越低

  • **问题1:**因为数字100.1由多个2^n相加决定,这种计算方式就决定了,部分小数会无限循环,无限接近于这个数,比如0.1。

  • **问题2:**但最大有效位只有52位,且整数部分会占用有效位数。整数越大,小数有效位数就越少,精度就越少。

这就是0.1为什么有精度丢失,而0.5等就没有精度丢失的问题。其中一个原因。

0

64位浮点全都为0时,这个数为0

Infinty

无穷,指数部分10位二进制全部为1时就是Infinty。注意这时有效位数全部为0,包括隐藏位。

这就是隐藏位唯二为0的情况。

NaN

Infinty的情况下,有效位数有任意一位为1就是NaN。其实情况有很多,但是都给你统一到NaN上了,这就是为什么NaN是Number类型。

两个二进制位数字如何计算

  • 分数转为二进制小数,分母除非为2的次幂,不然必是循环小数
  • 十进制分数转换为二进制小数,分母有质因子5,那必是循环小数
  • 将循环小数转换为有限小数:离散误差
  • 过长的尾数舍入:舍入误差
  • 误差大小不会超过最后一位有效位数,单精度是23位,双精度是52位。

对阶

两个数字的阶码可能不同,需要移动至相同阶码才能计算

  • 小阶对大阶,移动小阶阶码。小阶阶码精度一般高于大阶阶码,但双精度最多保存52位。移动小阶能保存更高的精度。
  • 阶码P相同无需移阶。
  • 双精度最多保存52位,但对移出的若干位会保留,之后舍入用。多余位单精度是23位,双精度是52位。

1.1

1.0001100110011001100110011001100110011001100110011010
P=0
你会发现最前面多了一个1,那是隐藏位。

0.9

1.1100110011001100110011001100110011001100110011001101
P=-1

好了0.9+100.1开始对阶
0.9右平移1位,多出部分用0保存
0.11100110011001100110011001100110011001100110011001101
1就是多余数

尾数运算

1.0001100110011001100110011001100110011001100110011010
0.1110011001100110011001100110011001100110011001100110
10.0000000000000000000000000000000000000000000000000000

规格化

转化为1.M的格式
10.0000000000000000000000000000000000000000000000000000
向右移一位,阶码+1
(1.0)P=1

舍入处理

就近舍入,默认

多余数大于等于1000...001(单精度23位,双精度52位),尾数+1

多余数小于等于0111...111(单精度23位,双精度52位),多余数舍去

多余数等于1000...000(单精度23位,双精度52位),尾数为1加1,0则舍去多余数

朝+Infinty

正数 多余数不全为0,尾数+1

负数 无视多余数

朝-Infinty

正数 无视多余数

负数 多余数不全为0,尾数+1

朝0

无视多余数

溢出处理

主要就是Infinty和NaN的情况

好了最后总结精度丢失的原因

  • 整数与小数公用这52位有效位
  • 十进制分数转化为二进制小数时,部分小数无法正确表达会无限循环,无限接近于这个小数,但有效位数只有52位。这里就导致了第一次的精度丢失。
  • 计算时的对阶,会导致部分精度成为多余数导致精度进一步丢失
  • 舍入时的规则,也只是尽量的保证精度