10

  初学JavaScript,在进行小数(浮点数)运算时,经常会碰到这样的情况:0.1 + 0.2=0.30000000000000004,记得当时,教程告诉我们说,0.1 + 0.2在JavaScript运算中,它的值是不固定的,可是在后面学习和试验中,渐渐发现,这个值似乎每次都是0.30000000000000004,于是开始怀疑它的和值是不固定的说法。

更多细节可参考笔者的另一篇文章:前端装逼技巧 108 式(三)—— 冇得感情的API调用工程师 第五十三式:你看到的0.1其实并不是真的0.1 —— 老生长谈的 0.1 + 0.2 !== 0.3,这次我们说点不一样的

为什么0.1 + 0.2 != 0.3?

  其实有一定编程基础的同学们应该都知道,计算机是采用二进制来表示十进制的,规则是:整数除以2,商继续除以2,得到0为止,将余数逆序排列;小数乘以2,取整,小数部分继续乘以2,取整,得到小数部分0为止,将整数顺序排列。例如:
整数转二进制小数转二进制

  其实不管是十进制转二进制还是八进制、十六进制,原理都是一样的,即,基数连除(整数)或者连乘(小数)进制转换

  再回到我们最初的问题, JS 采用 IEEE 754 双精度版本(64位),并且只要采用 IEEE 754 的语言都有前面的问题。

IEEE 754 标准是IEEE浮点数算术标准(IEEE Standard for Floating-Point Arithmetic)的标准编号 ,等同于国际标准ISO/IEC/IEEE 60559 。IEEE 754 标准规定了计算机程序设计环境中的二进制和十进制的浮点数之间的交换、算术格式以及方法 。

根据前面介绍的知识,0.1 的二进制表示为:

0.1 = 2^-4 * 1.10011(0011)// (0011) 表示循环

0.2 的二进制表示为:

0.2 = 2^-3 * 1.10011(0011)// (0011) 表示循环

  前面说了,JS 采用 IEEE 754 双精度版本(64位),六十四位中符号位占一位,整数位占十一位,其余五十二位都为小数位。因为 0.1 和 0.2 都是无限循环的二进制,所以在小数位末尾处需要判断是否进位(规则和十进制里的四舍五入一样)。
  所以 0.1的二进制表示(0.1 = 2^-4 * 1.10011(0011)) 进位后就变成了 2^-4 * 1.10011(0011 * 12次)010,同理可得0.2的二进制表示 。把这两个二进制加起来得到 2^-2 * 1.0011(0011 * 11次)0100 , 这个值再换算成十进制就是 0.30000000000000004


  所以说,0.1 + 0.2=0.30000000000000004,在JavaScript中,它的结果并非不固定的。

如何解决?

  那么,如果需要比较0.1 + 0.20.3的关系,我们又该如何进行呢?
  其实对于在大学学过数学分析、数值逼近或者高中数学的同学来说,自然可以想到让0.1 + 0.2的和减去0.3小于一个任意小的数,比如说我们可以通过他们差值是否小于0.0000000001来判断他们是否相等(其实ES6 已经在Number对象上面,新增一个极小的常量Number.EPSILON。根据规格,它表示 1 与大于 1 的最小浮点数之间的差。Number.EPSILON实际上是 JavaScript 能够表示的最小精度。误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了。)。JavaScript也提供了一些原生的方法,比如toFixed() 方法可把 Number 四舍五入为指定小数位数的数字,语法:NumberObject.toFixed(num)

参数描述:num,必需。规定小数的位数,是 0 ~ 20 之间的值,包括 0 和 20,有些实现可以支持更大的数值范围。如果省略了该参数,将用 0 代替。

  自然,你也可以封装一个处理函数,将小数运算转为整数运算:

/**
 * 精确加法
 */
function add(num1, num2) {
  const num1Digits = (num1.toString().split('.')[1] || '').length;
  const num2Digits = (num2.toString().split('.')[1] || '').length;
  const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
  return (num1 * baseNum + num2 * baseNum) / baseNum;
}
add(0.1,0.2); // 0.3

  更多解法细节可参考笔者的另一篇文章:前端装逼技巧 108 式(三)—— 冇得感情的API调用工程师 第五十三式:你看到的0.1其实并不是真的0.1 —— 老生长谈的 0.1 + 0.2 !== 0.3,这次我们说点不一样的

个人博客地址
参考内容:JavaScript toFixed() 方法
几道高级前端面试题解析


獨釣寒江雪
3.2k 声望12.1k 粉丝

👑 Nothing is given, Everything is earned!