JavaScript浮点数的精度问题深究

JavaScript浮点数的精度问题

javascript浮点数的精度问题是老生常谈了,但往往只知道0.1 + 0.2 != 0.3,究其原因,只能说出javascript精度问题,知其然而不知其所以然。
比如我,知道小数运算会有精度问题,但不知道整数运算同样会出现精度问题

82234342434234235339 + 23338435345455243948
// 结果
105572777779689490000

究其原因,网上看了些文章,但好多细节略过了,让我这种没有预备知识的看的一脸懵逼。比如十进制0.1转换成二进制咋转的?干,没说

javascript中的Number

在javascript中,数值类型都是Number,无论是整数还是浮点数。Number采用的是IEEE 754标准的64 位双精度浮点数。往往解释js精度问题基本上就只能说道这了,但我还是不明白为什么呀?

IEEE 754标准的64 位双精度浮点数

首先有一个概念需要明确,64位双精度的意思是:因为它是64位的,所以它是双精度。如果是32位,则是单精度的。(类似于Java中的float和double)

js采用的这种浮点数,一个数字由64位组成,你可以理解成:
00000011101...0001011001这么一串

那我们数字得有正负吧?这个这种浮点数它用第0位来表示正负。0表示正数,1表示负数

接下来第1位~第11位用来表示指数第12位~第63位表示有效数字。如下图

img

小数的精度问题

我们先来看0.1+0.2的运算过程是怎样的。

  • 0.1和0.2转为二进制表示
  • 将两个二进制数相加
  • 将结果转为十进制

关于十进制小数如何转成二进制可以看这篇文章,很简单的。
当你把0.1转为二进制的时候你会发现,这货居然无穷的。(实际输出可以直接在控制台尝试:(0.1).toString(2))

但是我们刚刚说了,js采用的这种浮点数表示法,有效数字只有52位,也就是0.1转成二进制以后最终只能保留小数点后52位,如下:

0.1 -> 0.0001100110011001100110011001100110011001100110011001

0.2 -> 0.0011001100110011001100110011001100110011001100110011

只要你试过,你就会开心的发现,0.1~0.9,除了0.5以外,都是无穷的,nice。

如果你不想自己计算两个二进制数相加,可以用我写的twoBigIntSum方法将两个位数相加

那么0.1 + 0.2最终得到的就是

0.0100110011001100110011001100110011001100110011001100

把它转为十进制是0.30000000000000004,开不开心,意不意外。

整数精度问题

最让我诧异的是整数居然也会有精度问题,看两个例子:

19571992547450991 === 19571992547450990 // true
输入:82234342434234235339 -> 输出:82234342434234240000

道理和小数的精度问题是一样的,有效数只有52位,也就是整数的安全范围是-2^53 - 1~2^53 - 1(52位都为1),超出这个范围的整数精度就会出现问题。javascript中也提供了Number.MAX_SAFE_INTEGER接口,返回在安全范围内的最大整数。

是不是很刺激?

回到我们开头的问题:

82234342434234235339 + 23338435345455243948
// 结果
105572777779689490000

这是为什么呢?

就是因为这两个数超出了安全范围

82234342434234235339 --实际上等于--> 82234342434234240000
23338435345455243948 --实际上等于--> 23338435345455243000
将两数相加又出现精度问题,所以最后加和得到
105572777779689490000

推荐阅读更多精彩内容