0.1+0.2≠0.3?聊聊 js 中的双精度浮点数


0.1+0.2≠0.3?聊聊 js 中的双精度浮点数

前言

我们都遇到过如下计算结果:

0.1+0.2≠0.3?聊聊 js 中的双精度浮点数

为什么会出现如此结果?难道不为 0.3 吗?这涉及到 js 的精度问题。

首先 js 的数字类型采用基于 IEEE 754 标准来实现的(也称为浮点数)。其选用的精度格式是:双精度格式(64 位的二进制数)

这篇就稍稍深入了解下双精度浮点数,以及有关于数 Number 的问题。

IEEE 754 标准

IEEE 二进制浮点数算术标准(IEEE 754),是最广泛使用的浮点数运算标准,为许多 CPU 与浮点运算器所采用。

这个标准定义了表示浮点数的格式(包括负零-0)与反常值(denormal number),一些特殊数值((无穷(Inf)与非数值(NaN)),以及这些数值的“浮点数运算符”;它也指明了四种数值舍入规则和五种例外状况(包括例外发生的时机与处理方式)。

规定了四种表示浮点数值的方式:单精确度(32 位)、双精确度(64 位)、延伸单精确度(43 比特以上,很少使用)与延伸双精确度(79 比特以上,通常以 80 位实现)

64 位的双精度

下图,基本解释清楚 64 位数的组成部分:

0.1+0.2≠0.3?聊聊 js 中的双精度浮点数

64位的双精度

64位的双精度(维基百科)

  • sign bit(S 符号):符号位,表示正负号(0 为负数,1 为正数)
  • exponent(E 指数):表示次方数,在(二进制的)科学计数法中定义 2 的多少次幂
  • mantissa(M 尾数):表示精确度(小数部分,规范中会省略个位数上的 1 )

那么一个双精度值的表达式如下:

0.1+0.2≠0.3?聊聊 js 中的双精度浮点数

我知道会有很多地方看不懂,没事,我下面来具体解释一下。

符号位 sign

符号位很容易理解,表示整个数的正负值。

0.1+0.2≠0.3?聊聊 js 中的双精度浮点数

所以用此位来示意数的正负性。

尾数位 mantissa

尾数也被称为规约形式的浮点数,因为在科学计数法的显示下,分数(fraction 也是 mantissa 那个部分之一)部分最高有效为是 1 (个位数)

0.1+0.2≠0.3?聊聊 js 中的双精度浮点数

最终 mantissa 会以 000001 来示意,会被规范成 1.M 格式 ,其中 1 会被隐藏掉,所以最大是表达 53 位的数(如上图,实际 mantissa 只有 52 位)。

指数位 exponent

科学计数法

我知道各位都是受过义务教育的,不过我真的忘记了,简单回顾下把:

科学记数法是一种记数的方法。把一个数表示成 a 与 10 的 n 次幂相乘的形式(1≤|a|<10,n 为整数),这种记数法叫做科学记数法。


0.1+0.2≠0.3?聊聊 js 中的双精度浮点数

那和科学计数法有什么关系,应该注意到 指数位是 2 的 n 次幂 (为何不是 10 ,因为是二进制)。

如果我们要表达 100(2),则结果如下:

0.1+0.2≠0.3?聊聊 js 中的双精度浮点数



指数偏移值(exponent bias)

我们了解科学计数法是可以表示大于 1 ,或者小于 1 的数(小数),即:通过正负指数的值来标识显示。

由于指数位的 11 位不包括符号位,那么为了达到这样正负的效果,就引入了 指数的偏移值

为什么要引入这个概念?我想了很久,以下这个例子或许会给你启发:

指数如果是 1023 和 1024,到底哪个值谁大?

首先 11 位的指数位对应的二进制最大和最小结果值为:00000000000(0) ,11111111111(2047,2^(12-1)-1),即指数的取值范围为:[0,2047]

并且我们知道指数具有

正负值 (来控制小数点左右移位),那么我们按照二进制中负数的规则(取反,补位),那么指数值为 [0,1023] 区间内为正数,[1024,2047] 内为负数(二进制中负数最高位为 1 )。

0.1+0.2≠0.3?聊聊 js 中的双精度浮点数

另外,根据 IEEE 规范, 0 和 2047 两个最值需要做特殊用途,所以这里移除,所以整个规范的指数取值范围是:

[1,2046]

回到这个问题,1023 和 1024 到底谁大,按照上面区间的划分,明显是 1023 > 1024 (正数大于负数,但机器不那么想)。

崩溃!就我个人理解起来就很困难,更不谈实际运算了(当你看到一个大于 1023 的值,还需要引入符号位,补码之类的计算方式)。

所以引入偏移值 bias(bias = 1023),使得整个运算简单,好理解。

那么 [-1022+bias,1023+bias] 等同于 [1,2046],这样抛去了符号位的影响, 最终:1023 就变成了 0 ,1024 变成了 1 ,明显 1 的指数值更大。

至于为何是 1023 ,我给的建议是 2046/2 (虽然这样理解是不对的,我认为数学功底真的对编程用处很大,虽然全还给老师了),另外 32 位精度浮点数的指数偏移量是 127 。


标准(规格)和非标准(规格)

整个指数位的值分为三种情况:

0.1+0.2≠0.3?聊聊 js 中的双精度浮点数

解惑些问题

整数范围

当尾数为标准模式时:1.M ,尾数位供 52 位,加上隐藏位 1 ,整个精度会是 53 位。

那么整数的取值范围是 [-2^53-1,2^53-1]

0.1+0.2≠0.3?聊聊 js 中的双精度浮点数

最小精度

在尾数的 52 位中,使得有一个最小的位定义(1.00000~ 中间 51 个 0~00001),即 2^-52 。

0.1+0.2≠0.3?聊聊 js 中的双精度浮点数

0.1+0.2 等于什么?

按照最小精度,即打满 52 位,那么像 0.1 和 0.2 最终无限循环后的:

0.1+0.2≠0.3?聊聊 js 中的双精度浮点数

最后

对于这个 IEEE 754 规范,我理解的还不是很透彻,不过对于 js 精度上的问题也算是有个初步的解答。

如果有不对之处望各位留言指正。


分享到:


相關文章: