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