浅谈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 相关文章推荐
用js重建星际争霸
Dec 22 Javascript
Javascript实例教程(19) 使用HoTMetal(1)
Dec 23 Javascript
JSQL 基于客户端的成绩统计实现方法
May 05 Javascript
iphone safari不支持position fixed的解决方法
May 04 Javascript
Javascript 加载和执行-性能提高篇
Dec 28 Javascript
基于javascript的Form表单验证
Dec 29 Javascript
bootstrap PrintThis打印插件使用详解
Feb 20 Javascript
web3.js增加eth.getRawTransactionByHash(txhash)方法步骤
Mar 15 Javascript
vue最简单的前后端交互示例详解
Oct 11 Javascript
微信小程序 授权登录详解(附完整源码)
Aug 23 Javascript
javascript实现前端成语点击验证优化
Jun 24 Javascript
浅谈vue2的$refs在vue3组合式API中的替代方法
Apr 18 Vue.js
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 cache类代码(php数据缓存类)
2010/04/15 PHP
PHP高自定义性安全验证码代码
2011/11/27 PHP
php中存储用户ID和密码到mysql数据库的方法
2013/02/06 PHP
dhtmlxTree目录树增加右键菜单以及拖拽排序的实现方法
2013/04/26 PHP
PHP中的魔术方法总结和使用实例
2015/05/11 PHP
php解压缩zip和rar压缩包文件的方法
2019/07/10 PHP
tp5.0框架隐藏index.php入口文件及模块和控制器的方法分析
2020/02/11 PHP
gearman中worker常驻后台,导致MySQL server has gone away的解决方法
2020/02/27 PHP
Yii 框架使用Forms操作详解
2020/05/18 PHP
sina的lightbox效果。
2007/01/09 Javascript
javascript 屏蔽鼠标键盘的几段代码
2008/01/02 Javascript
JavaScript获取GridView中用户点击控件的行号,列号
2009/04/14 Javascript
Ext grid 添加右击菜单
2009/11/26 Javascript
Jquery AutoComplete自动完成 的使用方法实例
2010/03/19 Javascript
js时间日期和毫秒的相互转换
2013/02/22 Javascript
extjs 3.31 TreeGrid实现静态页面加载json到TreeGrid里面
2013/04/02 Javascript
JS+flash实现chrome和ie浏览器下同时可以复制粘贴
2013/09/22 Javascript
JS常用字符串处理方法应用总结
2014/05/22 Javascript
js实现文本框中输入文字页面中div层同步获取文本框内容的方法
2015/03/03 Javascript
jQuery实现获取绑定自定义事件元素的方法
2015/12/02 Javascript
谈谈AngularJs中的隐藏和显示
2015/12/09 Javascript
基于canvas实现的钟摆效果完整实例
2016/01/26 Javascript
JavaScript学习笔记整理_setTimeout的应用
2016/09/19 Javascript
JS中数组重排序方法
2016/11/11 Javascript
微信小程序实战之轮播图(3)
2017/04/17 Javascript
小程序显示弹窗时禁止下层的内容滚动实现方法
2019/03/20 Javascript
nodejs检测因特网是否断开的解决方案
2019/04/17 NodeJs
JavaScript实现拖拽和缩放效果
2020/08/24 Javascript
vue 动态创建组件的两种方法
2020/12/31 Vue.js
python实现将元祖转换成数组的方法
2015/05/04 Python
用 Python 连接 MySQL 的几种方式详解
2018/04/04 Python
HTML5 canvas绘制的玫瑰花效果
2014/05/29 HTML / CSS
英国的领先快速时尚零售商:In The Style
2019/03/25 全球购物
社区活动邀请函范文
2014/01/29 职场文书
《月光启蒙》教学反思
2014/03/01 职场文书
学校艾滋病宣传活动总结
2015/05/09 职场文书