资讯专栏INFORMATION COLUMN

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

hightopo / 2668人阅读

摘要:如题先陈述下问题背景偶尔测测自己写的计算器,随便输入玩嘛,然后发生下面诡异的事情当我从一个输入到十个的时候,过程显示都是正确的,像这样继续输入一个的时候,然后就这个样子了什么原因呢看了下自己的代码,代码重要部分长这样的这里用了一下强制转化为

如题 先陈述下问题背景

偶尔测测自己写的计算器,随便输入玩嘛,然后发生下面诡异的事情:
当我从一个 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...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92951.html

相关文章

  • [ JS 基础 ] JS 浮点数四则运算精度丢失问题 (3)

    摘要:基于这个问题运动基础问题,我想应该也有一部分人没有认真对待过中浮点数的四则运算出现的问题。解决方案引自解决方案为了解决浮点数运算不准确的问题,在运算前我们把参加运算的数先升级的的次方到整数,等运算完后再降级的的次方。 基于这个问题:javascript运动基础问题 ,我想应该也有一部分人没有认真对待过js中浮点数的四则运算出现的问题。 1.问题描述 示例代码: var x ...

    hoohack 评论0 收藏0
  • JS最新基本数据类型:BigInt

    摘要:意外四舍五入会损害程序的可靠性和安全性。下面是一些例子构造函数与其他基本类型一样,可以使用构造函数创建。总结是一种新的数据类型,用于当整数值大于数据类型支持的范围时。 为了保证的可读性,本文采用意译而非直译。 想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你! BigInt数据类型的目的是比Number数据类型支持的范围更大的整数值。在对大整数执行数学运算时,以任意精...

    lwx12525 评论0 收藏0
  • JavaScript填坑史

    摘要:和深入理解在和深入理解这篇博客里笔者曾做过总结,我们知道试单线程的产物,两个函数就是利用了插入代码的方式实现了伪异步,和的原理实际上是一样的。综上所述,其实终归是单线程产物。无论如何异步都不可能突破单线程这个障碍。 说明:  这是笔者平时积累的一些觉得比较有意思或是比较有难度的JavaScript题目理解和心得,会保持长期更新。 1.setTimeout和setInterval深入理解...

    objc94 评论0 收藏0
  • JavaScript 基础知识 - 入门篇(一)

    摘要:如图意义位用来表示符号位位用来表示指数位表示尾数浮点数,比如无限循环无限循环此时只能模仿十进制进行四舍五入了,但是二进制只有和两个,于是变为舍入。这即是计算机中部分浮点数运算时出现误差,丢失精度的根本原因。 showImg(http://ww1.sinaimg.cn/large/9c47d583gy1fmtw1ma9g4j21hc0u0ach.jpg); 前言 最近一直有小伙伴跟我说J...

    sarva 评论0 收藏0
  • JS中如何理解浮点数?

    摘要:本文通过介绍的二进制存储标准来理解浮点数运算精度问题,和理解对象的等属性值是如何取值的,最后介绍了一些常用的浮点数精度运算解决方案。浮点数精度运算解决方案关于浮点数运算精度丢失的问题,不同场景可以有不同的解决方案。 本文由云+社区发表 相信大家在平常的 JavaScript 开发中,都有遇到过浮点数运算精度误差的问题,比如 console.log(0.1+0.2===0.3)// fa...

    bang590 评论0 收藏0

发表评论

0条评论

hightopo

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<