浅谈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 相关文章推荐
TBCompressor js代码压缩
Jan 05 Javascript
javascript面向对象之二 命名空间
Feb 08 Javascript
JS实现图片平面旋转的方法
Mar 01 Javascript
Vue.js表单控件实践
Oct 27 Javascript
js实现随机点名小功能
Aug 17 Javascript
为vue-router懒加载时下载js的过程中添加loading提示避免无响应问题
Apr 03 Javascript
js实现一个简易计算器
Mar 30 Javascript
vue+element tabs选项卡分页效果
Jun 29 Javascript
vue之延时刷新实例
Nov 14 Javascript
国内常用的js类库大全(CDN公共库)
Jun 24 Javascript
JavaScript获取时区实现过程解析
Sep 24 Javascript
在vs code 中如何创建一个自己的 Vue 模板代码
Nov 10 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
实例(Smarty+FCKeditor新闻系统)
2007/01/02 PHP
PHP CKEditor 上传图片实现代码
2009/11/06 PHP
PHP array_flip() 删除重复数组元素专用函数
2010/05/16 PHP
PHP缓存技术的使用说明
2011/08/06 PHP
PHP json_decode函数详细解析
2014/02/17 PHP
PHP基于curl模拟post提交json数据示例
2018/06/22 PHP
gearman中worker常驻后台,导致MySQL server has gone away的解决方法
2020/02/27 PHP
javascript对JSON数据排序的3个例子
2014/04/12 Javascript
javascript中clone对象详解
2014/12/03 Javascript
Javascript中3个需要注意的运算符
2015/04/02 Javascript
jQuery validate插件实现ajax验证重复的2种方法
2016/01/22 Javascript
javascript表单事件处理方法详解
2016/05/15 Javascript
基于Echarts 3.19 制作常用的图形(非静态)
2016/05/19 Javascript
微信小程序与后台PHP交互的方法实例分析
2018/12/10 Javascript
vue中多路由表头吸顶实现的几种布局方式
2019/04/12 Javascript
vue draggable resizable 实现可拖拽缩放的组件功能
2019/07/15 Javascript
vue 解决数组赋值无法渲染在页面的问题
2019/10/28 Javascript
JS在Array数组中按指定位置删除或添加元素对象方法示例
2019/11/19 Javascript
JS数组方法push()、pop()用法实例分析
2020/01/18 Javascript
jQuery实现的移动端图片缩放功能组件示例
2020/05/01 jQuery
Python格式化css文件的方法
2015/03/10 Python
Django使用httpresponse返回用户头像实例代码
2018/01/26 Python
pandas 对每一列数据进行标准化的方法
2018/06/09 Python
python自动发送测试报告邮件功能的实现
2019/01/22 Python
python爬虫租房信息在地图上显示的方法
2019/05/13 Python
关于tensorflow的几种参数初始化方法小结
2020/01/04 Python
TensorFlow 读取CSV数据的实例
2020/02/05 Python
pandas创建DataFrame的7种方法小结
2020/06/14 Python
六种酷炫Python运行进度条效果的实现代码
2020/07/17 Python
Auchan Direct波兰:欧尚在线杂货店
2016/10/19 全球购物
新加坡领先的在线生活方式和杂货购物网站:EAMART
2019/04/02 全球购物
区域总监的岗位职责
2013/11/21 职场文书
员工年终考核评语
2014/12/31 职场文书
2015年妇产科工作总结
2015/05/18 职场文书
2015年七年级班主任工作总结
2015/05/21 职场文书
一篇文章了解正则表达式的替换技巧
2022/02/24 Javascript