浅谈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 相关文章推荐
javascript 中that的含义示例介绍
May 14 Javascript
JavaScript数据结构和算法之图和图算法
Feb 11 Javascript
js+html5绘制图片到canvas的方法
Jun 05 Javascript
jQuery Ajax 加载数据时异步显示加载动画
Aug 01 Javascript
jQuery实现的简单百分比进度条效果示例
Aug 01 Javascript
js 简易版滚动条实例(适用于移动端H5开发)
Jun 26 Javascript
Node.js 使用流实现读写同步边读边写功能
Sep 11 Javascript
微信小程序switch开关选择器使用详解
Jan 31 Javascript
vue里input根据value改变背景色的实例
Sep 29 Javascript
Vue.directive 实现元素scroll逻辑复用
Nov 29 Javascript
jquery实现商品sku多属性选择功能(商品详情页)
Dec 20 jQuery
微信小程序中data-key属性之数据传输(经验总结)
Aug 22 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
二次元帅气男生排行榜,只想悄悄收藏系列
2020/03/04 日漫
一个不易被发现的PHP后门代码解析
2014/07/05 PHP
硬盘浏览程序,保存成网页格式便可使用
2006/12/03 Javascript
javascript SocialHistory 检查访问者是否访问过某站点
2008/08/02 Javascript
jquery 框架使用教程 AJAX篇
2009/10/11 Javascript
jquery实现的元素的left增加N像素 鼠标移开会慢慢的移动到原来的位置
2010/03/21 Javascript
IE本地存储userdata的一个bug说明
2010/07/01 Javascript
nodejs下打包模块archiver详解
2014/12/03 NodeJs
Jquery实现弹性滑块滑动选择数值插件
2015/08/08 Javascript
javascript中的深复制详解及实例分析
2016/12/29 Javascript
JQuery form表单提交前验证单选框是否选中、删除记录时验证经验总结(整理)
2017/06/09 jQuery
nodejs后台集成ueditor富文本编辑器的实例
2017/07/11 NodeJs
jQuery简单实现对数组去重及排序操作实例
2017/10/31 jQuery
JS实现的合并多个数组去重算法示例
2018/04/11 Javascript
vue-cli3.0 特性解读
2018/04/22 Javascript
Python中is与==判断的区别
2017/03/28 Python
python机器学习理论与实战(一)K近邻法
2021/01/28 Python
Django中使用Celery的教程详解
2018/08/24 Python
python获取时间及时间格式转换问题实例代码详解
2018/12/06 Python
Django 响应数据response的返回源码详解
2019/08/06 Python
利用python计算时间差(返回天数)
2019/09/07 Python
pytorch-神经网络拟合曲线实例
2020/01/15 Python
Python生成随机验证码代码实例解析
2020/06/09 Python
Python3爬虫里关于代理的设置总结
2020/07/30 Python
css3实现信纸/同学录效果的示例代码
2018/12/11 HTML / CSS
Waterford加拿大官方网站:世界著名的水晶杯品牌
2016/11/01 全球购物
德国高性价比网上药店:medpex
2017/07/09 全球购物
阿根廷票务网站:StubHub阿根廷
2018/04/13 全球购物
比利时的在线灯具店:Lampen24.be
2019/07/01 全球购物
保洁主管岗位职责
2013/11/20 职场文书
广告语设计及教案
2014/03/21 职场文书
社区班子个人对照检查材料思想汇报
2014/10/07 职场文书
2014年监理工作总结范文
2014/11/17 职场文书
中学教师个人总结
2015/02/10 职场文书
2019通用版导游词范本!
2019/08/07 职场文书
Java SSM配置文件案例详解
2021/08/30 Java/Android