浅谈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之事件绑定
Jul 08 Javascript
javascript实现依次输入input自动定焦
Dec 23 Javascript
详解JavaScript的流程控制语句
Nov 30 Javascript
几种二级联动案例(jQuery\Array\Ajax php)
Aug 13 Javascript
微信小程序 使用腾讯地图SDK详解及实现步骤
Feb 28 Javascript
JQuery实现图片轮播效果
May 08 jQuery
js replace替换字符串同时替换多个方法
Nov 27 Javascript
Next.js项目实战踩坑指南(笔记)
Nov 29 Javascript
使用puppeteer爬取网站并抓出404无效链接
Dec 20 Javascript
vue计算属性get和set用法示例
Feb 08 Javascript
jQuery选择器之基本过滤选择器用法实例分析
Feb 19 jQuery
javascript删除数组元素的七个方法示例
Sep 09 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
PHP编码规范的深入探讨
2013/06/06 PHP
php while循环得到循环次数
2013/10/26 PHP
一个显示效果非常不错的PHP错误、异常处理类
2014/03/21 PHP
ThinkPHP模板中数组循环实例
2014/10/30 PHP
laravel容器延迟加载以及auth扩展详解
2015/03/02 PHP
CI框架使用composer安装的依赖包步骤与方法分析
2016/11/21 PHP
php从身份证获取性别和出生年月
2017/02/09 PHP
yii2.0整合阿里云oss上传单个文件的示例
2017/09/19 PHP
js类 from qq
2006/11/13 Javascript
判断iframe里的页面是否加载完成
2014/06/06 Javascript
IE8中动态创建script标签onload无效的解决方法
2014/12/22 Javascript
JavaScript实现16进制颜色值转RGB的方法
2015/02/09 Javascript
详解nodejs与javascript中的aes加密
2016/05/22 NodeJs
jquery选择器中的空格与大于号>、加号+与波浪号~的区别介绍
2016/06/24 Javascript
浅谈js中test()函数在正则中的使用
2016/08/19 Javascript
JavaScript 冒泡排序和选择排序的实现代码
2016/09/03 Javascript
JS触摸屏网页版仿app弹窗型滚动列表选择器/日期选择器
2016/10/30 Javascript
浅谈webpack组织模块的原理
2018/03/10 Javascript
对vue事件的延迟执行实例讲解
2018/08/28 Javascript
vue基于viewer实现的图片查看器功能
2019/04/12 Javascript
在vue中使用Echarts画曲线图的示例
2020/10/03 Javascript
python实现发送邮件功能
2017/07/22 Python
多个应用共存的Django配置方法
2018/05/30 Python
python自动化生成IOS的图标
2018/11/13 Python
Python登录系统界面实现详解
2019/06/25 Python
Django+uni-app实现数据通信中的请求跨域的示例代码
2019/10/12 Python
Python计算不规则图形面积算法实现解析
2019/11/22 Python
Python 使用 prettytable 库打印表格美化输出功能
2019/12/26 Python
基于python3实现倒叙字符串
2020/02/18 Python
浅谈django 重载str 方法
2020/05/19 Python
Selenium环境变量配置(火狐浏览器)及验证实现
2020/12/07 Python
英国最大的电子零件及配件零售商:Partmaster
2017/04/24 全球购物
仓库主管的岗位职责
2013/12/04 职场文书
特色蛋糕店创业计划书
2014/01/28 职场文书
2015年团队工作总结范文
2015/05/04 职场文书
承兑汇票延期证明
2015/06/23 职场文书