咚咚技术团队
  • 首页
  • 文章
    • 前端
      • 0.1 + 0.2 精度丢失深究
      • IOS H5 视频无法播放
      • H5 播放 amr 音频文件
      • IOS 10.x 版本在 Taro 中的兼容性问题
      • 百度 UEditor 引发的 cross-iframe 问题解决方案
      • 访问 www.banchengyun.com 时发生了什么
      • decodeURIComponent 与特殊符号
      • 前端埋点
    • 后端
      • Swoole 相关
        • MAC 本地环境执行 GuzzleHttp 时导致 Swoole 进程异常退出
      • Hyperf 相关
        • 在 phpstorm 中调试 hyperf 代码
        • Hyperf 1.x Proxy 缓存失效问题
      • K8s 相关
        • 搭建 k8s 集群
        • 使用 docker-compose 快速搭建 Hyperf + Redis 开发环境
        • Kubernetes Autoscaler
      • 其它
        • 幂等性和原子性
    • 测试
    • 效能提升
      • 优秀开发者的第一步:始于需求分析
      • 优秀开发者的第二步:如何阅读他人的代码
  • 活动
  • 课堂
  • 知识库
    • 公共
      • 什么是流程型组织
      • 半城云集成产品开发流程
      • 阿⾥云 Codeup 代码平台使⽤ & 迁移指南
      • git 使用规范
      • 关于第三方与服务号授权的问题
      • 收不到消息的排查方法
      • 系统安全
      • 前端编码规范
      • 后端编码规范
      • 测试规范
    • 前端
      • 规范
        • 前端编码规范
        • 咚咚技术栈
        • code-review 规范
        • git 工作流
        • Tapd 文档
      • 复盘经验
        • 2021.01 效能、规范、技术债讨论会
      • Code Review
        • SCRM 2020-07
    • 后端
      • 复盘经验
        • SCRM 2020 年 8 月
      • Code Review
        • SCRM 2020-07
    • 测试
      • 复盘经验
        • SCRM 2020 年 8 月
  • 项目文档
    • 前端
    • 后端:小程序
    • 后端:企业微信
  • 接口文档
  • 兴趣小组
    • golang 小组
    • 增长小组
    • 前端小组
  • 书单推荐
  • 生产环境 分析会
    • NO.2022.01
  • 生产环境 可用性
  • 团队活动
    • OpenTalk
      • NO.2021.Q3
      • NO.2020.Q2
    • WalkTogether
  • 关于我们
  • GitBook 使用说明
由 GitBook 提供支持
在本页
  • JavaScript 运算符种类
  • 0.1 + 0.2 不等于 0.3 的原因
  • 那么我们如何解决呢?

这有帮助吗?

  1. 文章
  2. 前端

0.1 + 0.2 精度丢失深究

JavaScript 运算符分为许多种,以下就是我们的

JavaScript 运算符种类

算数运算符:+加,-减,*乘,/除,%取余,-(一元取反,也可以说是负),++自加,--自减。

等同全同运算符:==、===、!==、!===

比较运算符:>、<、>=、<=

字符串运算符:>,<,<=,>=,=,+

逻辑运算符:&&、 ||、 !

赋值运算符:=、+=、*=、-=、/=

位运算符:&(与运算)、|(或运算)、^(异或运算)、~(非运算)、>>(带符号的右位移)、>>>(无符号的(用 0 补足的)右位移)、<< (左位移)

今天我们要说的是 JavaScript 中一个比较奇怪的现象,我们直接看下面例子。

console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.3); // 0.4
console.log(0.2 + 0.3); // 0.5
console.log(0.15 + 0.15); // 0.3

我们发现一个非常奇怪的现象,0.1 + 0.2 != 0.3,而 0.15 + 0.15 却等于了 0.3,这是为什么呢? 这个问题我非常喜欢拿来当做面试题对前端面试者的考察。

0.1 + 0.2 不等于 0.3 的原因

首先让我们了解一下 JavaScript 中的 Number 类型。

在 JavaScript 中,整数和浮点数都属于 Number 数据类型,所有的数字都是以 64 位浮点数形式储存,也就是双精度浮点数,即便是整数也是如此。

十进制小数转换为二进制小数,用 2 乘 10 进制小数,可以得到积,将积的整数部分取出,再用 2 乘余下的小数部分,又得到一个积, 再讲积的整数部分取出,如此进行,知道积中的小数部分为零,此时 0 或 1 为二进制的最后一位。或者达到所要求的精度为止。

下面看两个例子:

0.3 = (0.0 1001 1001...)B
0.3 * 2 = 0.6======取出整数部分0
0.6 * 2 = 1.2======取出整数部分1
0.2 * 2 = 0.4======取出整数部分0
0.4 * 2 = 0.8======取出整数部分0
0.8 * 2 = 1.6======取出整数部分1
0.6 * 2 = 1.2======取出整数部分1
0.2 * 2 = 0.4======取出整数部分0
0.4 * 2 = 0.8======取出整数部分0
0.8 * 2 = 1.6======取出整数部分1

0.2 = 0.00110011....
0.2 * 2 = 0.4======取出整数部分0
0.4 * 2 = 0.8======取出整数部分0
0.8 * 2 = 1.6======取出整数部分1
0.6 * 2 = 1.2======取出整数部分1
0.2 * 2 = 0.4======取出整数部分0
0.4 * 2 = 0.8======取出整数部分0
0.8 * 2 = 1.6======取出整数部分1
0.6 * 2 = 1.2======取出整数部分1

 // 0.1 转化为二进制
0.0 0011 0011 0011 0011...(0011无限循环)

由于尾数只有 52 位,所以对于 0.1 和 0.2 转换后的二进制如下:

// S是符号位,在第0位, P是指数位,用e表示,是第1位到第11位。尾数是储存小数部分(即有效数字),第12到63位,用f表示。
e = -4; m =1.1001100110011001100110011001100110011001100110011010 (52位)
e = -3; m =1.1001100110011001100110011001100110011001100110011010 (52位)

像十进制数有4舍5入的规则一样,二进制也存在类似的规则,简单的说,如果 1.101
要保留一位小数,可能的值是 1.1 和 1.2,那么先看 1.101 和 1.1 或者 1.2 哪个值更
接近,毫无疑问是 1.1,于是答案是 1.1。那么如果要保留两位小数呢?很显然要么
是 1.10 要么是 1.11,而且又一样近,这时就要看这两个数哪个是偶数(末位是偶
数),保留偶数为答案。综上,如果第 52 bit 和 53 bit 都是 1,那么是要进位的。
这也导致了误差的产生。

我们看下这两个二进制相加

  e = -4; m = 1.1001100110011001100110011001100110011001100110011010 (52位)
+ e = -3; m = 1.1001100110011001100110011001100110011001100110011010 (52位)
---------------------------------------------------------------------------
相加时如果指数不一致,需要对齐,一般情况下是向右移,因为最右边的即使溢出了,损失的精度远远小于左边溢出。
  e = -3; m = 0.1100110011001100110011001100110011001100110011001101
+ e = -3; m = 1.1001100110011001100110011001100110011001100110011010
---------------------------------------------------------------------------
  e = -3; m = 10.0110011001100110011001100110011001100110011001100111
---------------------------------------------------------------------------
  e = -2; m = 1.0011001100110011001100110011001100110011001100110100(52位)
---------------------------------------------------------------------------
= 0.010011001100110011001100110011001100110011001100110100
= 0.30000000000000004(十进制)

我们可以看到,当十进制小数的二进制表示的有限数字超过 52 位时,在 JavaScript 里是不能精确存储的,这时候就存在舍入误差(Round-off error)。

那么我们如何解决呢?

  • 开源的库:bigInt

  • Number.toFixed 保留指定长度的小数,会四舍五入,不够准确,但可以解决 0.1+0.2 的问题。

  • 各自乘以 10 的 N 次方后,再处于 10 的 N 次方。 N > 1。

  • Number.EPSILON

function numbersequal(a, b) {
  return Math.abs(a - b) < Number.EPSILON;
}
var a = 0.1 + 0.2,
  b = 0.3;
console.log(numbersequal(a, b)); //true

由浮点数精度衍生出来的还有 JAVA 的 long 类型,当超出一定范围后会精度丢失。 我们通过在请求接口的时候,对数据进行一次处理

String.prototype.getLong = function () {
  const text = this.replace(/([^\\]":)(\d*)(,|})/g, '$1"$2"$3');
  return JSON.parse(text);
};

参考文章及发现的有趣 coder:

上一页前端下一页IOS H5 视频无法播放

最后更新于3年前

这有帮助吗?

float

0.30000000000000004.com/
js 双精度浮点数