5

都知道0.1+0.2 = 0.30000000000000004,那要怎么让它等于0.3 - 初见雨夜

 2 years ago
source link: https://www.cnblogs.com/rainy-night/p/16091646.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

都知道0.1+0.2 = 0.30000000000000004,那要怎么让它等于0.3

小学数学老师教过我们,0.1 + 0.2 = 0.3,但是为什么在我们在浏览器的控制台中输出却是0.30000000000000004?

除了加法有这个奇怪的现象,带小数点的减法和乘除计算也会得出意料之外的结果

console.log(0.3 - 0.1) // 0.19999999999999998
console.log(0.1 * 0.2) // 0.020000000000000004
console.log(0.3 / 0.1) // 2.9999999999999996

我们都知道计算机时是通过二进制来进行计算的,即 0 和 1

就拿 0.1 + 0.2 来说,0.1表示为0.0001100110011001...,而0.2表示为0.0011001100110011...

而在二进制中 1 + 1 = 10,所以 0.1 + 0.2 = 0.0100110011001100...

转成10进制就近似表示为 0.30000000000000004

简单来说就是,浮点数转成二进制时丢失了精度,因此在二进制计算完再转回十进制时可能会和理论结果不同

对于浮点数的四则运算,许多编程语言都会有理论值和实际值不同的问题。例如Java中也会出现类似的问题,但是Java中可以使用java.math.BigDecimal类来避免这种情况

可是JS是弱类型的语言,作者Brendan Eich自述10天内开发出JS语言,一开始设计的时候就没有对浮点数计算有个处理的好方法

那么在日常开发的前端项目中我们可以怎么解决嘞?

使用toFixed()<不推荐>

可以控制小数点后几位,如果为空的话会用0补充,返回一个字符串

> 0.123.toFixed(2) // '0.12'
  • 在不同浏览器中得出的值可能不相同,且部分数字得不到预计的结果,并不是执行严格的四舍五入
// 在chrome控制台中
> 1.014.toFixed(2) // '1.01'
> 1.215.toFixed(2) // '1.22'
> 1.105.toFixed(2) // '1.10'
> 1.115.toFixed(2) // '1.11'

乘以一个10的幂次方

把需要计算的数字乘以10的n次方,让数值都变为整数,计算完后再除以10的n次方,这样就不会出现浮点数精度丢失问题

> (0.1 * 10 + 0.2 *10) / 10  // 0.3

我们可以将它封装成一个函数

mathFloat = function (float, digit) {
  const math = Math.pow(10, digit);
  return parseInt(float * math, 10) / math;
}
mathFloat(0.1 + 0.2, 3)  // 0.3
  • JS中的存储都是通过8字节的double浮点类型表示的,因此它并不能准确记录所有数字,它存在一个数值范围

    Number.MAX_SAFE_INTEGER为 9007199254740991,而Number.MIN_SAFE_INTEGER为 -9007199254740991,超出这个范围的话JS是无法表示的
    虽然范围有限制,但是数值一般都够用

较为完整的实现

function mathPlus(arg1, arg2) {
  let r1, r2, m;
  try {
    r1 = arg1.toString().split(".")[1].length; // 获取小数点后字符长度
  } catch (error) {
    r1 = 0; // 为整数状态,r1赋0
  }
  try {
    r2 = arg2.toString().split(".")[1].length;
  } catch (error) {
    r2 = 0;
  }
  m = Math.pow(10, Math.max(r1, r2)); // 确保所有参数都为整数
  return (arg1 * m + arg2 * m) / m;
}
> mathPlus(0.1, 0.2); // 0.3
> mathPlus(1, 2); // 3
function mathSubtract(arg1, arg2) {
  let r1, r2, m;
  try {
    r1 = arg1.toString().split(".")[1].length;
  } catch (error) {
    r1 = 0;
  }
  try {
    r2 = arg2.toString().split(".")[1].length;
  } catch (error) {
    r2 = 0;
  }
  m = Math.pow(10, Math.max(r1, r2));
  return ((arg1 * m - arg2 * m) / m);
}
> mathSubtract(0.3, 0.1); // 0.2
> mathSubtract(3, 1); // 2
function mathMultiply(arg1, arg2) {
  let m = 0;
  let s1 = arg1.toString();
  let s2 = arg2.toString();
  try {
    m += s1.split('.')[1].length; // 小数相乘,小数点后个数相加
  } catch (e) {}
  try {
    m += s2.split('.')[1].length;
  } catch (e) {}
  return (
    (Number(s1.replace('.', '')) * Number(s2.replace('.', ''))) /
    Math.pow(10, m)
  );
}
> mathMultiply(0.1, 0.2); // 0.02
> mathMultiply(1, 2); // 2
function mathDivide(arg1, arg2) {
  let m1 = 0;
  let m2 = 0;
  let n1 = 0;
  let n2 = 0;
  try {
    m1 = arg1.toString().split('.')[1].length;
  } catch (e) {}
  try {
    m2 = arg2.toString().split('.')[1].length;
  } catch (e) {}
  n1 = Number(arg1.toString().replace('.', ''));
  n2 = Number(arg2.toString().replace('.', ''));
   /**
   * 将除法转换成乘法
   * n1 / n2 必为整数
   * 乘以它们的小数点后个数差
   */
  return mathMultiply(n1 / n2, Math.pow(10, m2 - m1));
}
// > 0.2 / 0.03 => 6.666666666666667
> mathDivide(0.2, 0.03); // 6.666666666666665
> mathDivide(0.3, 0.1); // 3
> mathDivide(3, 1); // 3

引入第三方库

站在前人的肩膀上,可以前进的更快。下面这些成熟的库封装了很多实用的函数,虽然部分函数可能永远不会用到

Math.js

介绍:功能强大,内置大量函数,体积较大

Github地址:https://github.com/josdejong/mathjs

star: 12.2k+

decimal.js

介绍:支持三角函数等,并支持非整数幂

Github地址:https://github.com/MikeMcl/decimal.js

star: 4.8k+

big.js

介绍:体积6k,提供了CDN

Github地址:https://github.com/MikeMcl/big.js

star: 3.9k+

number-precision

介绍:体积很小,只有1k左右

Github地址:https://github.com/nefe/number-precision

star: 3.4k+


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK