如题 先陈述下问题背景
偶尔测测自己写的计算器,随便输入玩嘛,然后发生下面诡异的事情:
当我从一个 1 输入到十个 1 的时候,过程显示都是正确的,像这样:
继续输入一个 1 的时候,然后就这个样子了:
什么原因呢?
看了下自己的代码,代码重要部分长这样的:
这里用了一下 parseInt 强制转化为整数类型 (研究了之后,才懊悔,这个方法缺陷太多,研究的不深入就容易写垃圾麻烦代码--)
摸索问题怎么产生的
出现问题,只能一点一点扒,拆分成小块找问题。这个过程,如果有耐心的话,还是可以学到很多,提升很多,是一件很愉快的事情,工作并不是做的越多越好,而是带着思想做的越深越好。
废话结束
首先尝试一下 console parseInt
尝试刚刚数字, 从十个 1 上开始加
着实搞不懂这是怎么回事唉
parseInt 深入理解 (字符串,小数,整数之间的转换)
除了我试出来的这个问题,网上还看到这样的奇葩 ParseInt(0.0000008, 10)
所以了解一下 parseInt 究竟在转换过程中做了啥是非常有必要的
parseInt 在将字符串或者小数(我们眼中的而已,其实他都一视同仁)转换为整数时,做了那几步呢?
- 取出参数
- 将传入的第一个参数转为字符串 调用了String()方法
- 根据第二个参数,再对字符串进行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 种数值
这些数中包括 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
现在思考为什么是 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
思考方式基本类似,看这个过程中有哪些步骤,在哪步会丢失精度
首先这是一个表达式,要先将表达式的 左右对象 装进计算机
- 转换为二进制
- 用二进制科学计数法表示
- 表示成 IEEE 754 形式
第一步和第三步都有可能丢失精度
回到最开始的问题,怎么解决呢?
- 替换 parseInt 为 Math.round
- 若想用 大于 2^53 的数进行计算的话,思考下导入什么 bitInterger 的库用一用吧
多看几个例子:
以上属于精度丢失问题,间隔计算
科学计数法存储展示问题
参考
http://2ality.com/2012/04/num...
https://en.wikipedia.org/wiki...
http://es5.github.io/#x8
http://blog.csdn.net/JustJava...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。