浅谈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 相关文章推荐
JQuery中如何传递参数如click(),change()等具体实现
Apr 28 Javascript
js改变img标签的src属性在IE下没反应的解决方法
Jul 23 Javascript
JQuery分别取得每行最后一列和最后一行的示例代码
Aug 18 Javascript
JavaScript中的eval()函数详解
Aug 22 Javascript
JavaScript获取Url里的参数
Dec 18 Javascript
浅谈下拉菜单中的Option对象
May 10 Javascript
jQuery实现移动端手机商城购物车功能
Sep 24 Javascript
PHP获取当前页面完整URL的方法
Dec 02 Javascript
利用js判断手机是否安装某个app的多种方案
Feb 13 Javascript
JS基于正则表达式的替换操作(replace)用法示例
Apr 28 Javascript
解决Vue.js父组件$on无法监听子组件$emit触发事件的问题
Sep 12 Javascript
详解如何使用React Hooks请求数据并渲染
Oct 18 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
历史证明,懒惰才是推动科学发展技术进步的动力
2021/03/02 无线电
PHP set_time_limit(0)长连接的实现分析
2010/03/02 PHP
PHP中替换换行符的几种方法小结
2012/10/15 PHP
PHP网站开发中常用的8个小技巧
2015/02/13 PHP
php生成静态html页面的方法(2种方法)
2015/09/14 PHP
laravel实现分页样式替换示例代码(增加首、尾页)
2017/09/22 PHP
捕获浏览器关闭、刷新事件不同情况下的处理方法
2013/06/02 Javascript
Javascript中call的两种用法实例
2013/12/13 Javascript
JSF中confirm弹出框的用法示例介绍
2014/01/07 Javascript
js代码实现的加入收藏效果并兼容主流浏览器
2014/06/23 Javascript
基于Bootstrap3表格插件和分页插件实例详解
2016/05/17 Javascript
js 转义字符及URI编码详解
2017/02/28 Javascript
JavaScript 字符串数字左补位,右补位,取固定长度,截位扩展函数代码
2017/03/25 Javascript
JS排序之选择排序详解
2017/04/08 Javascript
nodejs连接mysql数据库简单封装示例-mysql模块
2017/04/10 NodeJs
bootstrap3使用bootstrap datetimepicker日期插件
2017/05/24 Javascript
详解vue+webpack+express中间件接口使用
2018/07/17 Javascript
vue环形进度条组件实例应用
2018/10/10 Javascript
vue配置接口域名方法总结
2019/05/12 Javascript
JavaScript直接调用函数与call调用的区别实例分析
2020/05/22 Javascript
vant 时间选择器--开始时间和结束时间实例
2020/11/04 Javascript
jQuery实现增删改查
2020/12/22 jQuery
python3实现短网址和数字相互转换的方法
2015/04/28 Python
Python爬取数据保存为Json格式的代码示例
2019/04/09 Python
python3.8与pyinstaller冲突问题的快速解决方法
2020/01/16 Python
Python如何实现邮件功能
2020/05/27 Python
keras的backend 设置 tensorflow,theano操作
2020/06/30 Python
python集合的新增元素方法整理
2020/12/07 Python
利用python爬取有道词典的方法
2020/12/08 Python
新西兰第一的行李箱网站:luggage.co.nz
2019/07/22 全球购物
阿里巴巴美国:Alibaba美国
2019/11/24 全球购物
专项法律服务方案
2014/06/11 职场文书
大学生学雷锋活动总结
2014/06/26 职场文书
2014年反腐倡廉工作总结
2014/12/05 职场文书
2014年环境整治工作总结
2014/12/10 职场文书
JavaScript如何优化逻辑判断代码详解
2021/06/08 Javascript