浅谈JavaScript中小数和大整数的精度丢失


Posted in Javascript onMay 31, 2016

先来看两个问题:

0.1 + 0.2 == 0.3; // false
9999999999999999 == 10000000000000000; // true

第一个问题是小数的精度问题,在业界不少博客里已有讨论。第二个问题,去年公司有个系统的数据库在做数据订正时,发现有部分数据重复的诡异现象。本文将从规范出发,对上面的问题做个小结。

最大整数

JavaScript 中的数字是用 IEEE 754 双精度 64 位浮点数 来存储的,其格式为:

s x m x 2^e

s 是符号位,表示正负。 m 是尾数,有 52 bits. e 是指数,有 11 bits. 在 ECMAScript 规范 里有给出 e 的范围为 [-1074, 971]. 这样,很容易推导出 JavaScript 能表示的最大整数为:

1 x (2^53 - 1) x 2^971 = 1.7976931348623157e+308

这个值正是 Number.MAX_VALUE

同理可推导出 Number.MIN_VALUE 的值为:

1 x 1 x 2^(-1074) = 5e-324

注意 MIN_VALUE 表示最接近 0 的正数,而不是最小的数。最小的数是 -Number.MAX_VALUE

小数的精度丢失

JavaScript 的数字都是双精度浮点数,在计算机里用二进制存储。当有效位数超过 52 位时,会存在精度丢失。比如:

十进制 0.1 的二进制为 0.0 0011 0011 0011 … (循环 0011)
十进制 0.2 的二进制为 0.0011 0011 0011 … (循环 0011)
0.1 + 0.2 相加可表示为:
   e = -4; m = 1.10011001100...1100(52 位)
 + e = -3; m = 1.10011001100...1100(52 位)
---------------------------------------------
   e = -3; m = 0.11001100110...0110
 + e = -3; m = 1.10011001100...1100
---------------------------------------------
   e = -3; m = 10.01100110011...001
---------------------------------------------
 = 0.01001100110011...001
 = 0.30000000000000004(十进制)

根据上面的演算,还可以得出一个结论:当十进制小数的二进制表示的有限数字不超过 52 位时,在 JavaScript 里是可以精确存储的。比如:

0.05 + 0.005 == 0.055 // true

进一步的规律,比如:

0.05 + 0.2 == 0.25 // true
0.05 + 0.9 == 0.95 // false

需要考虑 IEEE 754 的 Rounding modes, 有兴趣的可进一步研究。

大整数的精度丢失

这个问题鲜有人提及。首先得弄清楚问题是什么:

1. JavaScript 能存储的最大整数是什么?

该问题前面已回答,是 Number.MAX_VALUE, 非常大的一个数。

2. JavaScript 能存储的且不丢失精度的最大整数是什么?

根据 s x m x 2^e, 符号位取正,52 位尾数全填充 1, 指数 e 取最大值 971, 显然,答案依旧是 Number.MAX_VALUE.

我们的问题究竟是什么呢?回到起始代码:

9999999999999999 == 10000000000000000; // true

很明显,16 个 9 还远远小于 308 个 10. 这个问题与 MAX_VALUE 没什么关系,还得归属到尾数 m 只有 52 位上来。

可以用代码来描述:

var x = 1; // 为了减少运算量,初始值可以设大一点,比如 Math.pow(2, 53) - 10
while(x != x + 1) x++;
// x = 9007199254740992 即 2^53

也就是说,当 x 小于等于 2^53 时,可以确保 x 的精度不会丢失。当 x 大于 2^53 时,x 的精度有可能会丢失。比如:

x 为 2^53 + 1 时,其二进制表示为:
10000000000...001 (中间共有 52 个 0)
用双精度浮点数存储时:
e = 1; m = 10000..00(共 52 个 0,其中 1 是 hidden bit)
显然,这和 2^53 的存储是一样的。

按照上面的思路可以推出,对于 2^53 + 2, 其二进制为 100000…0010(中间 51 个 0),也是可以精确存储的。

规律:当 x 大于 2^53 且二进制有效位数大于 53 位时,就会存在精度丢失。这和小数的精度丢失本质上是一样的。

hidden bit 可参考:A tutorial about Java double type.

小结

小数和大整数的精度丢失,并不仅仅在 JavaScript 中存在。严格来说,使用了IEEE 754 浮点数格式来存储浮点类型的任何编程语言(C/C++/C#/Java 等等)都存在精度丢失问题。在 C#、Java 中,提供了 Decimal、BigDecimal 封装类来进行相应的处理,才避开了精度丢失。

注:ECMAScript 规范中,已有  decimal proposal,但目前尚未被正式采纳。

最后考考大家:

Number.MAX_VALUE + 1 == Numer.MAX_VALUE;
Number.MAX_VALUE + 2 == Numer.MAX_VALUE;
...
Number.MAX_VALUE + x == Numer.MAX_VALUE;
Number.MAX_VALUE + x + 1 == Infinity;
...
Number.MAX_VALUE + Number.MAX_VALUE == Infinity;
// 问题:
// 1. x 的值是什么?
// 2. Infinity - Number.MAX_VALUE == x + 1; 是 true 还是 false ?

以上这篇浅谈JavaScript中小数和大整数的精度丢失就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
利用404错误页面实现UrlRewrite的实现代码
Aug 20 Javascript
ExtJS 入门
Oct 29 Javascript
JavaScript中的16进制字符(改进)
Nov 21 Javascript
JavaScript获取表格(table)当前行的值、删除行、增加行
Jul 03 Javascript
Node.js读写文件之批量替换图片的实现方法
Sep 07 Javascript
详解webpack es6 to es5支持配置
May 04 Javascript
JS处理数据四舍五入(tofixed与round的区别详解)
Oct 26 Javascript
vuex 使用文档小结篇
Jan 11 Javascript
解决使用Vue.js显示数据的时,页面闪现原始代码的问题
Feb 11 Javascript
Nuxt.js实现校验访问浏览器类型的中间件
Aug 24 Javascript
解决vue attr取不到属性值的问题
Sep 18 Javascript
vue通过过滤器实现数据格式化
Jul 20 Javascript
javascript小数精度丢失的完美解决方法
May 31 #Javascript
javascript解决小数的加减乘除精度丢失的方案
May 31 #Javascript
javascript基本语法
May 31 #Javascript
jquery 无限极下拉菜单的简单实例(精简浓缩版)
May 31 #Javascript
通用无限极下拉菜单的实现代码
May 31 #Javascript
JavaScript+Java实现HTML页面转为PDF文件保存的方法
May 30 #Javascript
详解JavaScript中双等号引起的隐性类型转换
May 30 #Javascript
You might like
PHP实现利用MySQL保存session的方法
2014/08/23 PHP
PHP简单实现HTTP和HTTPS跨域共享session解决办法
2015/05/27 PHP
微信 getAccessToken方法详解及实例
2016/11/23 PHP
解决php扩展安装不生效问题
2019/10/25 PHP
jQuery .tmpl(), .template()学习资料小结
2011/07/18 Javascript
用Mootools获得操作索引的两种方法分享
2011/12/12 Javascript
js实现checkbox全选和反选示例
2014/05/01 Javascript
Javascript字符串浏览器兼容问题分析
2014/12/01 Javascript
浅析JavaScript事件和方法
2015/02/28 Javascript
探究Javascript模板引擎mustache.js使用方法
2016/01/26 Javascript
Node.js中使用jQuery的做法
2016/08/17 Javascript
AngularJs ng-repeat 嵌套如何获取外层$index
2016/09/21 Javascript
jQuery实现背景滑动菜单
2016/12/02 Javascript
js实现华丽的九九乘法表效果
2017/03/29 Javascript
分分钟学会vue中vuex的应用(入门教程)
2017/09/14 Javascript
js实时监控文本框输入字数的实例代码
2018/01/18 Javascript
百度小程序之间的页面通信过程详解
2019/07/18 Javascript
js实现多张图片每隔一秒切换一张图片
2019/07/29 Javascript
vue-cli在 history模式下的配置详解
2019/11/26 Javascript
微信小程序 scroll-view的使用案例代码详解
2020/06/11 Javascript
压缩包密码破解示例分享(类似典破解)
2014/01/17 Python
Python内置的HTTP协议服务器SimpleHTTPServer使用指南
2016/03/30 Python
Python断言assert的用法代码解析
2018/02/03 Python
Pycharm在创建py文件时,自动添加文件头注释的实例
2018/05/07 Python
解决python super()调用多重继承函数的问题
2019/06/26 Python
python文件路径操作方法总结
2020/12/21 Python
基于css3实现漂亮便签样式
2013/03/18 HTML / CSS
html5 横向滑动导航栏的方法示例
2020/05/08 HTML / CSS
Travelstart沙特阿拉伯:廉价航班、豪华酒店和实惠的汽车租赁优惠
2019/04/06 全球购物
建筑公司员工自我鉴定
2014/04/08 职场文书
中小学校园安全广播稿
2014/09/29 职场文书
清洁工个人工作总结
2015/03/05 职场文书
八月迷情观后感
2015/06/11 职场文书
学习社交礼仪心得体会
2016/01/22 职场文书
搞笑Gif:这么白这么长的腿像极了一楼的女朋友
2022/03/21 杂记
python实现手机推送 代码也就10行左右
2022/04/12 Python