Please enable Javascript to view the contents

从头学习js-16-浮点数精度

 ·  ☕ 3 分钟

这个系列是我读冴羽老师博客的感悟,
加入了个人的解读和练习题的解答

关于浮点数精度导致 js 的计算错误问题,我之前在Js 大数相加有提到过,那这篇文章将再次深入研究导致错误的原因。

ECMAScript 中的 Number 类型使用 IEEE754 标准来表示整数和浮点数值。
IEEE754 全称为 IEEE 二进制浮点数算数标准,这个标准定义了表示浮点数的格式等内容

在 IEEE754 中,规定了四种表示浮点数值的方式:单精确度(32 位)、双精确度(64 位)、延伸单精确度与延伸双精确度。
ECMAScript 采用的是双精确度,用 64 位字节储存一个浮点数


浮点数转二进制

1020 十进制:
1020 = 1 _ 10^3 + 0 _ 10^2 + 2 _ 10^1 + 0 _ 10^0

1020 二进制:
1020 = 1 _ 2^9 + 1 _ 2^8 + 1 _ 2^7 + 1 _ 2^6 + 1 _ 2^5 + 1 _ 2^4 + 1 _ 2^3 + 1 _ 2^2 + 0 _ 2^1 + 0 _ 2^0

我之前一直好奇用二进制表示小数是什么样的,因为计算器和网上的在线转二进制都不支持小数

但细想一下一定还是类似的,比如:
0.75 = a _ 2^-1 + b _ 2^-2 + c _ 2^-3 + d _ 2^-4 + …
在二进制里 abcd 只能是 0 或 1

要继续算出上面的 abcd 是 0 还是 1,我们在等号两边同时乘以 2

1 + 0.5 = a _ 2^0 + b _ 2^-1 + c _ 2^-2 + d _ 2^-3…

a * 2^0 = a, 由于 a 只能是 1 或 0,这里 a 应该是组成 1.5 里的最大值,所以 a 为 1

剩下的的是:

0.5 = b _ 2^-1 + c _ 2^-2 + d * 2^-3…

再次两边都乘以 2

1 + 0 = b _ 2^0 + c _ 2^-2 + d * 2^-3…

同上得出 b 也是 1,同时等号左边已经为 0 了,所以 0.75 的二进制就是 0.11

然而不是所有的数都像 0.75 这么好算,我们来算下 0.1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
0.1 = a * 2^-1 + b * 2^-2 + c * 2^-3 + d * 2^-4 + ...

0 + 0.2 = a * 2^0 + b * 2^-1 + c * 2^-2 + ...   (a = 0)
0 + 0.4 = b * 2^0 + c * 2^-1 + d * 2^-2 + ...   (b = 0)
0 + 0.8 = c * 2^0 + d * 2^-1 + e * 2^-2 + ...   (c = 0)
1 + 0.6 = d * 2^0 + e * 2^-1 + f * 2^-2 + ...   (d = 1)
1 + 0.2 = e * 2^0 + f * 2^-1 + g * 2^-2 + ...   (e = 1)
0 + 0.4 = f * 2^0 + g * 2^-1 + h * 2^-2 + ...   (f = 0)
0 + 0.8 = g * 2^0 + h * 2^-1 + i * 2^-2 + ...   (g = 0)
1 + 0.6 = h * 2^0 + i * 2^-1 + j * 2^-2 + ...   (h = 1)
....

然后你就会发现,这个计算在不停的循环,所以 0.1 用二进制表示就是 0.00011001100110011……


IEEE754 中的储存规则

IEEE754 的储存规则其实就是科学计数法:

正负 _ 进制的指数 _ 有一位个位的小数(x.x)

sign Exponent Mantissa
1 Bit 11 Bits 52 Bits

我们知道-1020 十进制的科学计数法是 -1 _ 10^3 _ 1.02

那在计算机里我们只关注二进制,
0.1 的二进制是 0.00011001100110011……
科学计数法是 1 _ 2^-4 _ 1.1001100110011……

二进制的第三部分的个位一定是 1,在储存的时候能省位置就省,这个 1 就不存储了,只存小数点后的数字

由于第二部分的 Exponent 是可以为负数的(小数),11 位可以储存的范围是 0 ~ 2046,
但是要分正负,所有储存的范围是-1023 ~ 1023,
但是存负数很麻烦,所以用了一个办法:把 0 当作 1023,把 1023 当作 2046,所有数都加上 1023 即 0 ~ 2048,
当要用的时候再减去 1023(把 1023 记做 bias)

在这个标准下:
我们会用 1 位存储 S,0 表示正数,1 表示负数。
用 11 位存储 E + bias,对于 11 位来说,bias 的值是 2^(11-1) - 1,也就是 1023。
用 52 位存储 Fraction。

举个例子,就拿 0.1 来看,对应二进制是 1 _ 1.1001100110011…… _ 2^-4,
Sign 是 0,
E + bias 是 -4 + 1023 = 1019,1019 用二进制表示是 1111111011,
Fraction 是 1001100110011……

0.1 对应 64 个字节位的完整表示就是:
0 01111111011 1001100110011001100110011001100110011001100110011010

同理, 0.2 表示的完整表示是:
0 01111111100 1001100110011001100110011001100110011001100110011010

所以当 0.1 存下来的时候,就已经发生了精度丢失,当我们用浮点数进行运算的时候,使用的其实是精度丢失后的数

在运算时也会发生精度丢失,储存和运算都有可能发生超出位数精度丢失。

分享

Llane00
作者
Llane00
Web Developer