由parseInt 引起的问题---想到浮点运算精度丢失---看透js number 的 encoded

如题 先陈述下问题背景


偶尔测测本身写的计算器,随便输入玩嘛,而后发生下面诡异的事情:
当我从一个 1 输入到十个 1 的时候,过程显示都是正确的,像这样:
图片描述html

继续输入一个 1 的时候,而后就这个样子了:
图片描述git

什么缘由呢?
看了下本身的代码,代码重要部分长这样的:
图片描述github

这里用了一下 parseInt 强制转化为整数类型 (研究了以后,才懊悔,这个方法缺陷太多,研究的不深刻就容易写垃圾麻烦代码--)segmentfault

摸索问题怎么产生的


出现问题,只能一点一点扒,拆分红小块找问题。这个过程,若是有耐心的话,仍是能够学到不少,提高不少,是一件很愉快的事情,工做并非作的越多越好,而是带着思想作的越深越好。
废话结束浏览器


首先尝试一下 console parseInt
尝试刚刚数字, 从十个 1 上开始加
图片描述ide

着实搞不懂这是怎么回事唉测试

parseInt 深刻理解 (字符串,小数,整数之间的转换)


除了我试出来的这个问题,网上还看到这样的奇葩 ParseInt(0.0000008, 10)编码

因此了解一下 parseInt 究竟在转换过程当中作了啥是很是有必要的es5

parseInt 在将字符串或者小数(咱们眼中的而已,其实他都一视同仁)转换为整数时,作了那几步呢?spa

  1. 取出参数
  2. 将传入的第一个参数转为字符串 调用了String()方法
  3. 根据第二个参数,再对字符串进行int转换

String()方法会让原来的参数改变,好比无数个1
图片描述
再好比连接中提到的 0.0000008
图片描述

可见String()将其科学计数法再转为字符串,形成只取到了 8

通俗理解一下,第一个参数是字符串还好,若是不是字符串就形成麻烦了,非字符串的数值在调用 String 进行转换时会出问题的,这就是 parseInt 弊端

(感兴趣的 能够再深刻 String 这个方法作了哪些事情吧。这里根据es5标准写了一篇String 内部处理逻辑标准

继续尝试 parseInt ,百试不厌,就会发现,当增长到必定大的数时候,会发现不变了,临界值以下:
图片描述

不只仅尝试了 parseInt 同时尝试了 + 单元运算符,结果同样, 这应该跟 paseInt 没多大关系了吧,应该是由于编码存储之类的问题吧

因此啊,即便没有最大值问题, parseInt 仍是少用,能够Math.round等就不会出现这个问题,为啥呢,能够深刻一波

是怎么联系上 encoded 问题的呢?

上面例子测试,发现,当 parseInt 的第一个参数传入的是数字而且愈来愈大时,值就停留在 9007199254740992。固然排除特殊的科学计数法(这会致使 js 在表示 number 时隐藏部分数字)后变换只获得1或者8或者其余等等状况,为何停留在这个数字呢?

查看 ES5 标准, 发现 JS 对 number encoded 的处理很独特,总结以下:

Number类型统一按浮点数处理,64位存储,整数是按最大53位来算最大最小数的

Number value
primitive value corresponding to a double-precision 64-bit binary format IEEE 754 value.

查阅 IEEE 754

图片描述

图片显示双精度 64 位浮点数的存储格式为:

s * m * 2^e

s 是符号位,表示正负,由 1 bit

m 是小数位,由 52 bits

e 是指数位, 由 11 bits

64位表示双精度浮点数,能够表示 2^64 - 2^53 + 3 种数值

clipboard.png

这些数中包括 number 所包括的各类类型(NaN , infinity, 浮点数),这些数值是怎么在 64bits 中存储的呢?能够看这一篇JS双精度64位 Number

其中正常的浮点数又包含了不丢失精度的可能会丢失精度的,不丢失精度的数通俗理解就是加 1 不会算错,因此 Number.MAX_SAFE_INTEGER 的值为 9007199254740991 由于 9007199254740991 + 1 不会算错,若是用 9007199254740992 +1 就会算错了,以下图
图片描述

因此称之为 max_safe_interger

其实 number 能表示的最大整数是 2^53,为何呢?

为何是 2^53 而不是 2^52?

根据 IEEE754 制定的标准,应该只有 52 bits 用来表示小数位的,最大也应该是 2^51 - 1 呀!

先普及 52 bits 表示的二进制转换为十进制为何最大是 2^51 - 1,有助于小白理解 2^53 而不至于混淆。

52 bits

当 52 位上都是 1 的时候,转换为十进制获得的数最大
根据二进制转十进制的方法,将 52 个 1……1 转换为十进制,方程为:
2^0 + 2^1 + 2^2 + …… + 2^51
观察得知这是一个
首位为 2
公比为 2
等比数列求合 a1 * (1 -q^n)/1-q (q != 1)
           na1 (q == 1)

因此 52 bits 能表示的最大十进制数为 2^51 - 1,同理 53 bits 能表示的最大十进制数为 2^52 - 1

等比数列求合公式推导连接

clipboard.png

如今思考为何是 2^53 呢?

Why 53 bits? You have 53 bits available for the magnitude (excluding the sign), but the fraction comprises only 52 bits. How is that possible? As you have seen above, the exponent provides the 53rd bit: It shifts the fraction, so that all 53 bit numbers except the zero can be represented and it has a special value to represent the zero (in conjunction with a fraction of 0).

这段话的意思就是,在表示最大数的时候,存储指数位的 11 bits 分给小数位 1 bit,就变成了 53 bits,就算这样也应该最大是 2^53 - 1 啊,为何不是 2^53 - 1呢?

为何是 2^53 而不是 2^53 - 1呢?

Why is the highest integer not 2^53−1? Normally, x bit mean that the lowest number is 0 and the highest number is 2x−1. For example, the highest 8 bit number is 255. In JavaScript, the highest fraction is indeed used for the number 2^53−1, but 2^53 can be represented, thanks to the help of the exponent – it is simply a fraction f = 0 and an exponent p = 53

这段话意思是说,最高的小数位对于 2^53 - 1是有必要的并且已经有的,可是 2^53 也是能够转化过来的。什么意思呢,对于二进制来讲,小数点前保留一位,规格化后始终是 1.*,节省了 1 bit,这个 1 并不须要保存。

发现超过 2^53 的十进制数也是能够表示的,那么是怎么存储的的,仍是指数位提供了帮助,这也就是为何能够在 2^53 上以 2 的倍数变化,能够加 2,加 4 ……,可是加 1 不行,不能精确显示。一样比 2^53 大且比 2^54 小的数在浏览器中显示,是其 2 的倍数才能正确显示,不然不能

,x can be represented in the range 2^53 ≤ x < 2^54. In row (p=54), that spacing increases to multiples of four, in the range 2^54 ≤ x < 2^55 …… and so on

因此 max_safe_interger 是 2^53 - 1

以前常见到 js unicode utf-16 引发的一些问题,此次碰见的是 js encoded 问题
js string 存储是 utf-16 encoding form
js number 存储是 IEEE 754 双精度浮点数 64 bits 标准

JS 浮点运算丢失精度问题

在计算 0.1 + 0.1 出错的问题上,精度是怎么丢失的呢,这个问题和 parseInt 思考方式基本相似,看这个过程当中有哪些步骤,在哪步会丢失精度

首先这是一个表达式,要先将表达式的 左右对象 装进计算机

  1. 转换为二进制
  2. 用二进制科学计数法表示
  3. 表示成 IEEE 754 形式

第一步和第三步都有可能丢失精度

回到最开始的问题,怎么解决呢?

  1. 替换 parseInt 为 Math.round
  2. 若想用 大于 2^53 的数进行计算的话,思考下导入什么 bitInterger 的库用一用吧

多看几个例子:

clipboard.png
以上属于精度丢失问题,间隔计算

科学计数法存储展现问题

clipboard.png

参考
http://2ality.com/2012/04/num...
https://en.wikipedia.org/wiki...
http://es5.github.io/#x8
http://blog.csdn.net/JustJava...

相关文章
相关标签/搜索