浅谈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 相关文章推荐
客户端脚本中常常出现的一些问题和调试技巧
Jan 09 Javascript
为Yahoo! UI Extensions Grid增加内置的可编辑器
Mar 10 Javascript
jQuery EasyUI API 中文文档 - ProgressBar 进度条
Sep 29 Javascript
jquery中的mouseleave和mouseout的区别 模仿下拉框效果
Feb 07 Javascript
jQuery针对input的class属性写了多个值情况下的选择方法
Jun 03 Javascript
JS输出空格的简单实现方法
Sep 08 Javascript
js仿QQ邮箱收件人选择与搜索功能
Feb 10 Javascript
JS实现的简单表单验证功能示例
Oct 13 Javascript
React Native 图片查看组件的方法
Mar 01 Javascript
Angular value与ngValue区别详解
Nov 27 Javascript
vue中使用v-for时为什么不能用index作为key
Apr 04 Javascript
详解Vue3使用axios的配置教程
Apr 29 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求两个目录的相对路径示例(php获取相对路径)
2014/03/27 PHP
通过MSXML2自动获取QQ个人头像及在线情况(给初学者)
2007/01/22 Javascript
javascript引用对象的方法代码
2007/08/13 Javascript
javascript 常用功能总结
2012/03/18 Javascript
js快速排序的实现代码
2013/12/08 Javascript
将HTML的左右尖括号等转义成实体形式的两种实现方式
2014/05/04 Javascript
不使用ajax实现无刷新提交表单
2014/12/21 Javascript
Javascript中函数名.length属性用法分析(对比arguments.length)
2016/09/16 Javascript
微信小程序 Page()函数详解
2016/10/17 Javascript
原生JS实现简单放大镜效果
2017/02/08 Javascript
解析jquery easyui tree异步加载子节点问题
2017/03/08 Javascript
常用的几个JQuery代码片段
2017/03/13 Javascript
JavaScript实现简单的四则运算计算器完整实例
2017/04/28 Javascript
vue2.0+ 从插件开发到npm发布的示例代码
2018/04/28 Javascript
angularjs结合html5实现拖拽功能
2018/06/25 Javascript
JavaScript多态与封装实例分析
2018/07/27 Javascript
vue项目中使用vue-i18n报错的解决方法
2019/01/13 Javascript
Vue表单之v-model绑定下拉列表功能
2019/05/14 Javascript
解决vue-cli项目开发运行时内存暴涨卡死电脑问题
2019/10/29 Javascript
JavaScript回调函数callback用法解析
2020/01/14 Javascript
详解搭建一个vue-cli的移动端H5开发模板
2020/01/17 Javascript
Paypal支付不完全指北
2020/06/04 Javascript
快速排序的算法思想及Python版快速排序的实现示例
2016/07/02 Python
Python常用的json标准库
2019/02/19 Python
基于python生成器封装的协程类
2019/03/20 Python
python字符串Intern机制详解
2019/07/01 Python
django列表筛选功能的实现代码
2020/03/27 Python
Python搭建Keras CNN模型破解网站验证码的实现
2020/04/07 Python
python对接ihuyi实现短信验证码发送
2020/05/10 Python
python中@property的作用和getter setter的解释
2020/12/22 Python
JYSK加拿大:购买家具、床垫、家居装饰等
2020/02/14 全球购物
2014年党建工作汇报材料
2014/10/27 职场文书
违纪检讨书
2015/01/27 职场文书
小组口号霸气押韵
2015/12/24 职场文书
关于React Native 无法链接模拟器的问题
2021/06/21 Javascript
java泛型通配符详解
2021/07/25 Java/Android