第二章 数值(下)

911 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

前言

开局一个小彩蛋,JS全靠悟。
来一个很简单的题目,让我们来更快的理解今天要说啥

0.1 + 0.2 = ?

正常来说肯定等于0.3是不是,但是你在控制台上查看结果后可知 0.30000000000000004, wow,amazing! 现在让我们思考一下一下几个问题,然后带着问题一起研究一下为什么?

  1. 在那些情况下会出现精度丢失的情况?
  2. 为什么会出现 精度丢失 的情况?
  3. 该怎么解决这种情况呢? 老司机发车
 ╭╩══╮ ╔══════╗╔══════╗╔═══════╗╔════╗ 
╭  嘟嘟嘟╠╣   都不要跑 ╠╣       ╠╣    ╣
╰⊙══⊙╯╚◎════◎╝╚◎════◎╝╚◎═════◎╝

JS数字精度丢失的一些典型问题

两个简单浮点数相加

// 加法 =====================
0.1 + 0.2 = 0.30000000000000004
0.2 + 0.4 = 0.6000000000000001

// 减法 =====================
0.3 - 0.2 = 0.09999999999999998
 
// 乘法 =====================
0.8 * 3 = 2.4000000000000004

// 除法 =====================
0.3 / 0.1 = 2.9999999999999996

看到这里,由衷的恭喜你,触发了第一个彩蛋。众所周知,JS不是很擅长处理小数,尤其是和金钱息息相关的小数。JS一般使用它可以表示出来的值来代替这些小数。

当你在代码里输入小数点或从一些数据中读取带小数点的数时,基本上就等于给程序贴上了“有误差”的标签。有时候误差小到可以忽略不记,有时候多个数的误差可以相互抵消,但还是有些时候误差会越累计越大。

整数运算

// 加法 =====================
9007199254740992 + 1 = 9007199254740992

// 减法 =====================
9007199254740993 - 1 = 9007199254740991
 
// 乘法 =====================
诶 没找到

// 除法 =====================
100 / 3 = 33.333333333333336

为什么会出现精度丢失的情况呢?

  • JavaScript中是遵循IEEE 754的标准,在程序的内部Number类型实质是一个64位固定长度的浮点数,也就是标准的double双精度浮点数。
  • 计算机的二进制实现和位数限制有些数无法有限表示。就像一些无理数不能有限表示,如 圆周率 3.1415926...,1.3333... 等。JS 遵循 IEEE 754 规范,采用双精度存储(double precision),占用 64 bit。
  • 在转换为二进制的科学记数法的形式时只保留64位有效的数字,此时只能模仿十进制进行四舍五入了,但是二进制只有 0 和 1 两个,于是变为 0 舍 1 入。在这一步出现了错误,那么一步错步步错,那么在计算机存储小数时也就理所应当的出现了误差。这即是计算机中部分浮点数运算时出现误差,这就是丢失精度的根本原因

该怎么解决这种情况呢?

对于整数,前端出现问题的几率可能比较低,毕竟很少有业务需要需要用到超大整数,只要运算结果不超过 Math.pow(2, 53) 就不会丢失精度。

简单方法

  • 对于小数,前端出现问题的几率还是很多的,尤其在一些电商网站涉及到金额等数据。解决方式:把小数放到位整数(乘倍数),再缩小回原来倍数(除倍数)
  • toFixed()

类库

  • Math.js
  • big.js