0.1+0.2 !== 0.3?

栏目: JavaScript · 发布时间: 6年前

内容简介:众所周知,JavaScript在计算某些浮点数的运算时会出现精度的丢失,比如你在控制台输入我们知道,计算机里所有的数据最终都是以二进制保存的,当然数字也一样。所以当计算机计算不同的语言有不同的存储标准,这里我们暂且只讨论JavaScript的存储标准。JavaScript中所用的数字包括整数和小数,都只有一种类型就是

众所周知,JavaScript在计算某些浮点数的运算时会出现精度的丢失,比如你在控制台输入 0.1+0.2 ,得到的结果是 0.30000000000000004 而不是 0.3 ,原因是什么?

世界上有两种人,懂二进制和不懂二进制的人

我们知道,计算机里所有的数据最终都是以二进制保存的,当然数字也一样。所以当计算机计算 0.1+0.2 的时候,实际上计算的是这两个数字在计算机里所存储的二进制,那么 0.1 在JavaScript里存储的二进制到底是多少? 我们先根据十进制转二进制的方法,把 0.1 转化为二进制是: 0.0001100110011001100... (1100循环),然后把 0.2 转化为二进制是: 0.00110011001100... (1100循环)。 我们发现,它们都是无限循环的二进制。显然,计算机当然不会用自己“无限的空间”去存储这些无限循环的二进制数字。那对于这类数据该怎么办?

JavaScript如何存储无限循环的二进制小数?

不同的语言有不同的存储标准,这里我们暂且只讨论JavaScript的存储标准。JavaScript中所用的数字包括整数和小数,都只有一种类型就是 Number ,它的实现遵循IEEE 754标准,使用64位固定长度来表示,也就是标准的double双精度浮点数(相关的还有float 32位单精度),具体的双精度浮点数的存储方式这里不再赘述(可以看后面章节的详细描述),我们只需要知道,在二进制科学表示法中,双精度浮点的小数部分最多只能保留52位(比如 1.xxx...*2^n ,这里 x 最多保留52位),再回过头看 0.1 的二进制表示:

0.00011001100110011001100110011001100110011001100110011001 10011...
复制代码

空格前为第53个有效数字,如何舍去后面的数字,这里遵从“0舍1入”,那么舍去之后实际上就是:

0.00011001100110011001100110011001100110011001100110011010
复制代码

同理我们得到 0.2 的舍去之后实际存储的二进制为:

0.0011001100110011001100110011001100110011001100110011010
复制代码

二者相加:

0.00011001100110011001100110011001100110011001100110011010 +
0.0011001100110011001100110011001100110011001100110011010 =
0.0100110011001100110011001100110011001100110011001100111
复制代码

我们把结果根据公式或者 工具 转为十进制:

0.1+0.2 !== 0.3?

可以看到结果正好为: 0.30000000000000004

注:大多数语言中的小数默认都是遵循 IEEE 754 的 float 浮点数,包括 JavaRuby 、Python,本文中的浮点数问题同样存在。

浮点数是如何保存的

0.1+0.2 !== 0.3?

在计算机中,浮点表示法,分为三大部分:

  • 第一部分用来存储符号位(sign),用来区分正负数,0表示正数
  • 第二部分用来存储指数(exponent)
  • 第三部分用来存储小数(fraction)

双精度浮点数一共占据64位:

  • 符号位(sign)占用1位
  • 指数位(exponent)占用11位
  • 小数位(fraction)占用52位

这里的符号位、指数位、小数位跟二进制是如何联系在一起呢? 我们以 78.735 为例

0.1+0.2 !== 0.3?
最后的的 1.001110011*2^6 我们称为二进制的科学记数法,这个实数由一个整数或定点数(即尾数)乘以某个基数(计算机中通常是2)的整数次幂得到,这就叫 浮点数 。 我们对号入座,先把指数部分 6 转化为二进制是 110

,最终为:

0(sign) 00000000110(exponent) 00111001 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
复制代码

(这是错误的,具体为什么错,继续看下文)

我们再根据双精度规范,来看看上文提到的 0.1 到底是如何存储的,我们已知它的二进制是:

0.00011001100110011001100110011001100110011001100110011001 10011...
复制代码

转化为科学表示法就是:

1.1001100110011001100110011001100110011001100110011001*2^-2
复制代码

也就是说 0.1 的:

0
1001100110011001100110011001100110011001100110011001
-2

到这里我就懵逼了, -2 怎么转为二进制呢,虽然双精度浮点规范规定了一个符号位,但是这个符号位表示的是整个数据的正负,而非指数的正负,难道还要保留一位专门存储指数的正负吗?答案是否定的,为了减少不必要的麻烦,IEEE规定了一个偏移量,这个偏移量是干嘛用的呢,就是对于指数部分,每次都加这个偏移量进行保存,这样即使指数是负数,那么加上这个偏移量也变为正数啦。为了使所有的负指数加上这个偏移量都能够变为正数,这个偏移量的设置也是有规律的。 以double双精度为例,我们知道它的指数部分是二进制的11位,那么能够表示的数据范围就是 0~2047 ,IEEE规定一个大概在中间数的位置 1023 为双精度的偏移量。

  1. 当指数位不全是0也不全是1时(规格化的数值),IEEE规定,阶码计算公式为 e-Bias 。 此时e最小值是1,则 1-1023= -1022 ,e最大值是 2046 ,则 2046-1023=1023 ,可以看到,这种情况下取值范围是 -1022~1013
  2. 当指数位全部是0的时候(非规格化的数值),IEEE规定,阶码的计算公式为 1-Bias ,即 1-1023= -1022
  3. 当指数位全部是1的时候(特殊值),IEEE规定这个浮点数可用来表示3个特殊值,分别是正无穷,负无穷, NaN(not a number) 。 具体的,小数位不为0的时候表示NaN;小数位为0时,当符号位s=0时表示正无穷,s=1时候表示负无穷。

这个时候我们再看 78.735 的指数部分如何存储,需要 6+1023 就是 1029 ,转化为二进制就是: 10000000101 ,所以 78.735 正确存储方式为:

0 10000000101 00111001 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
复制代码

同理,你是否也知道 0.1 的双精度的浮点存储形式了呢?

浮点数值的范围

如果你认真读到了这里,想必你应该能推算出JavaScript的所能表示的数值范围了吧。 e的最大值是1023。 1.111..(52位)..11*2^1023 转为普通二进制就是:

1 111..(52位)..11 000..(1023-52就是971位)..00
复制代码

把二进制转为十进制就是:

0.1+0.2 !== 0.3?
我们会发现这个值和 Number.MAX_VALUE 的值一致,都是 1.7976931348623157e+308 。 但实际上这个值还不算最大,比如我们在此数值基础上继续加一些数,发现并没有返回 Infinity

0.1+0.2 !== 0.3?
所以 Number.MAX_VALUEInfinity 之间还存在一些数,根据IEEE规范我们可以得知,正无穷当且是指数部分全为1(指数部分的最大值 Math.pow(2,11)-1-1023 == 1024

),小数部分为0的时候,就是:

1.000...*2^1024
复制代码

所以 Math.pow(2,1024) 就是正无穷,那么其实JavaScript所能存储的最大数字是 Math.pow(2,1024)-1 。 但是 Number.MAX_VALUEMath.pow(2,1024) 之间的数据我们无法正常表示出来,精度会丢失。 同理也可推算最小数。

JavaScript的安全最大整数

所谓安全范围,就是我们在这个范围内计算不会出现精度的丢失。 根据双精度的定义,可以得知,最大的安全整数:

1.11..(52位)*2^52
复制代码

转为十进制就是 Math.pow(2,53)-1 ,即 9007199254740991

在JavaScript中,有 Number.MAX_SAFE_INTEGER 来表示最大安全整数

0.1+0.2 !== 0.3?
我们发现和我们自己推算出来的值是一样的。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Lean Startup

The Lean Startup

Eric Ries / Crown Business / 2011-9-13 / USD 26.00

更多中文介绍:http://huing.com Most startups fail. But many of those failures are preventable. The Lean Startup is a new approach being adopted across the globe, chan ging the way companies are built and ......一起来看看 《The Lean Startup》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具