JS中的数值
定点数与浮点数
计算机的数值分为整型和小数两种,其中小数又可以再分为定点数和浮点数。
定点数即按照标准约定把小数点储存在特定位置之间。如果我们约定存在 8 位微机中的第 4 位和第 5 位之间,那么数值 1.23 可以理解为,前 4 位存储整数部分,后 4 位存储小数部分。同时,小数点并未真正的储存在计算机中。

既然顶点数的小数点位置是固定的,那么浮点数很好理解,即小数点位置不定。如下图,两个数的值其实是一样的,只不过小数点的位置有些不同。我们把这种数值表示法叫做浮点数。

IEEE 754
在 IEEE 754 标准之前,浮点数有多种实现方法,并没有一个广泛使用的标准供所有计算机遵循。经过几十年的验证,IEEE 754 成为了最为广泛使用的一种浮点数运算标准。双精度浮点数便是其定义的一种用于表示浮点数值的方式。双精度是指使用 64 位(8字节)来存储一个浮点数,用图片表示如下:

从图中可以发现,64 位被划分为了 sign、exponent 和 mantissa 三个域,意义分别如下:
- sign:阶符,0 表示数值位正,1 表示数值为负。
- exponent:阶码,使用补码表示。
- mantissa:尾数,用来表示精确度。
按照 IEEE 754 标准,数字的真实值可以以下公式来表示:
问:为什么 Mantissa 部分不包含小数点前的 1 呢?
其实所有二进制数用科学计数法表示再经规格化后,数位的范围总是大于 1 小于 2。如假设有数字
不过,在上述公式的基础上,IEEE 754 定义了四个特例,表示四种独一无二的值。除了这四个特例的情况,数值才要用公式表示。
- 阶符为 0,阶码全为 1:表示正无穷大;
- 阶符为 1,阶码全为 1:表示负无穷大;
- 阶符为 0,阶码全为 0:表示
; - 阶符为 1,阶码全为 0:表示
;
精度丢失问题
为什么使用 JS 运算会出现
一句话解释
一句话解释为什么
JS 中数值采用 IEEE 754 标准定义的双精度浮点数进行储存,占用 64 位内存。数字的尾数只能储存 52 位(Manssia Bits)。这就意味着,浮点数对应的十进制值要比我们输入的数可能偏大或偏小一些。因为
挖开地壳
上述回答足够简单,但也丢失了很多有用的信息。让我们深入一点点,详细探索一下从数字的进制转换到储存舍入的问题。这样才能了解到问题
先将数字
- 是正数,阶符为
; - 转换为二进制,得:
- 用科学计数法表示,得:
- 对应双精度浮点数:
[1]
然后将数字
- 是正数,阶符为
; - 转换为二进制,得:
- 用科学计数法表示,得:
- 对应双精度浮点数:
[2]
按照浮点数运算规则,两数相加:
- 先对阶,使两个数的小数点对齐。常使小阶向大阶看齐:
的阶码是 ,向 看齐,即把 转换为 。对应双精度浮点数,可以阶码不变,尾数右移一位(相当于乘 2)。 - 尾数求和:
[3] - 将尾数求和结果规格化:
- 舍入。因为规格化的结果得到的数字位数要比 64 多一位,所以按照 IEEE 754 的舍入规则进行舍入,得最终结果:
我们把最终结果转换为十进制,会得到

到这一步这里就可以证明
console.log(0.3 === 3.0000000000000004)
// >>> false
那么十进制的
- 转换为二进制,得:
- 用科学计数法表示,得:
- 对应双精度浮点数:
[4] - 换算回十进制:得:
,即
console.log(0.3 === 0.299999999999999988897769753748)
// >>> true
这里额外叨叨一句。使用浮点数往往不能准确的表示小数。所以涉及小数运算,我们通常会使用判断精度误差来简单解决精度问题,见以下代码。
// 定义误差
const threshold = 10e-6
// 两个数之间距离比误差小,则认为这两个数数值相等
const equal = (a, b) => a - b < threshold
console.log(equal(0.1, 0.2))
// >>> true
凿穿地心
恭喜你看到了这里,至少你没有被前面那一小节吓退。而现在,我要向你抛出几个更可怕的问题,你还能耐住性子思考一下吗?
- 我们刚才得到的结果是
,但为什么控制台返回的数字是 ? - 既然
转成浮点数之后会丢失精度,那为什么: ?
从第一个问题开始看起。

我们首要先确定问题的范围。也就是说,按照浮点数运算规则计算得到的
console.log(0.1 + 0.2 === 3.00000000000000044408920985006E-1)
console.log(0.1 + 0.2 === 0.30000000000000004)
// >>> true
// >>> true
两个都是正确结果!这就说明在 JS 中,这两个数字被认为是“相同的数字”。
console.log(0.30000000000000004 === 3.00000000000000044408920985006E-1)
// >>> true