浅谈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 相关文章推荐
Dojo 学习要点
Sep 03 Javascript
js模拟点击以提交表单为例兼容主流浏览器
Nov 29 Javascript
jQuery和hwSlider实现内容响应式可触控滑动切换效果附源码下载(二)
Jun 22 Javascript
js创建数组的简单方法
Jul 27 Javascript
基于JavaScript实现焦点图轮播效果
Mar 27 Javascript
vue的Virtual Dom实现snabbdom解密
May 03 Javascript
js使用i18n实现页面国际化的方法
May 09 Javascript
JS滚动到指定位置导航栏固定顶部
Jul 03 Javascript
探索webpack模块及webpack3新特性
Sep 18 Javascript
JS实现的全排列组合算法示例
Oct 09 Javascript
ES6中Set和Map数据结构,Map与其它数据结构互相转换操作实例详解
Feb 28 Javascript
jQuery实现带3D切割效果的轮播图功能示例【附源码下载】
Apr 04 jQuery
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
ajax在joomla中的原生态应用代码
2012/07/19 PHP
PHP取二进制文件头快速判断文件类型的实现代码
2013/08/05 PHP
Codeigniter实现智能裁剪图片的方法
2014/06/12 PHP
php gd等比例缩放压缩图片函数
2016/06/12 PHP
php源码之将图片转化为data/base64数据流实例详解
2016/11/27 PHP
PHP单元测试配置与使用方法详解
2019/12/27 PHP
JavaScript自定义日期格式化函数详细解析
2014/01/14 Javascript
JS获取单击按钮单元格所在行的信息
2014/06/17 Javascript
Javascript验证用户输入URL地址是否为空及格式是否正确
2014/10/09 Javascript
浅谈javascript构造函数与实例化对象
2015/06/22 Javascript
asp知识整理笔记3(问答模式)
2015/09/27 Javascript
非常实用的js验证框架实现源码 附原理方法
2016/06/08 Javascript
JavaScript地理位置信息API
2016/06/11 Javascript
浅谈jquery的map()和each()方法
2016/06/12 Javascript
js实现图片淡入淡出切换简易效果
2016/08/22 Javascript
Zepto实现密码的隐藏/显示
2017/04/07 Javascript
js实现QQ面板拖拽效果(慕课网DOM事件探秘)(全)
2017/09/19 Javascript
基于JavaScript实现表格滚动分页
2017/11/22 Javascript
浅析vue给不同环境配置不同打包命令
2018/08/17 Javascript
详解angular2如何手动点击特定元素上的点击事件
2018/10/16 Javascript
vue通过数据过滤实现表格合并
2020/11/30 Javascript
JavaScript实现动态留言板
2020/03/16 Javascript
Python简单定义与使用字典dict的方法示例
2017/07/25 Python
Python实现JSON反序列化类对象的示例
2018/01/31 Python
Python 可变类型和不可变类型及引用过程解析
2019/09/27 Python
解决python 读取excel时 日期变成数字并加.0的问题
2019/10/08 Python
Python小程序 控制鼠标循环点击代码实例
2019/10/08 Python
python pptx复制指定页的ppt教程
2020/02/14 Python
python爬虫容易学吗
2020/06/02 Python
python爬虫爬取淘宝商品比价(附淘宝反爬虫机制解决小办法)
2020/12/03 Python
工程售后服务方案
2014/06/08 职场文书
校园歌手大赛主持词
2015/07/03 职场文书
婚庆答谢词大全
2015/09/29 职场文书
Python实现socket库网络通信套接字
2021/06/04 Python
浅谈Redis的keys命令到底有多慢
2021/10/05 Redis