js计算精度问题, 这个函数是什么原理?

我们知道, js里面的计算有时候是有误差的,是因为JavaScript使用二进制表示浮点数,因此不能精确表示所有十进制数

比如0.1 + 0.2不等于0.3
0.14 * 100不等于14

我一般是用这个函数处理的

Math.signFigures = function(num, rank = 6) {
  if(!num) return(0);
  const sign = num / Math.abs(num);
  const number = num * sign;
  const temp = rank - 1 - Math.floor(Math.log10(number));
  let ans;
  if (temp > 0) {
      ans = parseFloat(number.toFixed(temp));
  }
  else if (temp < 0) {
      ans = Math.round(number / Math.pow(10, temp)) * temp;
  }
  else {
      ans = Math.round(number);
  }
  return (ans * sign);
};

这个函数是从网上来的, 比如计算0.1 + 0.2, Math.sign(0.1 + 0.2)就很准确的等于0.3

我没明白这个函数的运行原理是啥, 看不懂

阅读 2.7k
6 个回答

数学函数

Math.log10(x) :以10为底的x的对数

Math.pow(x,y):x的y次方

Math.abs(x):x的绝对值

Math.floor(x):使x向下取整

console.log(Math.floor(5.95));//  output: 5
console.log(Math.floor(5.05));//  output: 5
console.log(Math.floor(5));//  output: 5
console.log(Math.floor(-5.05)); //  output: -6

x.toFixed:对x定点

Math.round(x):对x进行四舍五入

一些主要的代码

 const sign = num / Math.abs(num); 

上面是获取num的符号, 当num > 0, sign = 1; num < 0 , sign = -1; num = 0 情况 if(!num) return(0) 已经排除;

Math.log10(x) = y
Math.log10(1) = 0
Math.log10(10) = 1
Math.log10(100) = 2
Math.log10(1000) = 3
Math.log10(10000) = 4

上面x的位数比y的值多 1, 那么 y + 1 就等于 x的位数; 但是log10(x)大多数情况下存在小数, 比如log10(88) = 1.94448..; 对此可以使用Math.floor(x) 向下取整,Math.floor(Math.log10(88)) = 1;所以 1 + Math.floor(Math.log10(number) 就是计算num的整数位数;

Math.signFigures = function(num, rank = 6) {.......}

上面rank 默认设置为 6,也许是单精度浮点数在大多数平台能够保证6位有效数字; 基于此, 可以对原来的浮点数进行一些转换来解决丢失精度的问题。

  const temp = rank - 1 - Math.floor(Math.log10(number));
             = rank - (1 + Math.floor(Math.log10(number)));

1 + Math.floor(Math.log10(number) 是计算num的整数部分的位数, 令rank = 6, temp的含义是以6位有效数字的长度为分界点进行讨论:

  1. num的整数位数小于6时 temp > 0, 因为有效数位是6位,把num转化为一个定点数表示也没有问题,可以把temp设置为精确的小数部分(number.toFixed(temp));
  2. num的整数位数等于6时 temp = 0, 直接使用Math.round(number)进行四舍五入;
  3. num的整数位数大于6时 temp < 0, 此时要把num转化为一个整数; number / Math.pow(10, temp) 实际是把number扩大10的 |temp|次方倍

其他

可以这样:
if(Math.signFigures(x) == Math.signFigures(y)){
   //TODO:
}
但如果像下面这样,有时候会发现 x 与 value 相差很多
let value = Math.signFigures(x); // 令x = 10000001,或者令x的位数大于默认的6

该函数的原理是将输入数字 num 分解为正负号和绝对值。然后计算绝对值的有效数字位数,并根据 rank 参数进行调整。最后,将计算出的数字与其原始符号相乘,以恢复其符号。
具体实现过程如下:

  1. 如果输入数字为 0,则直接返回 0。
  2. 通过除以绝对值来提取数字的符号。
  3. 将数字的符号去掉后,计算数字的绝对值的有效数字位数。
  4. 根据 rank 参数调整有效数字位数,使用 toFixed() 方法将数字四舍五入到指定的有效数字位数。
  5. 如果计算出的有效数字位数小于 0,则将数字除以 10 的幂来提高有效数字位数。然后使用 Math.round() 方法将数字四舍五入到最接近的整数。
  6. 最后,将计算出的数字与其原始符号相乘,并将其作为函数的返回值。

这个出现的原理就是 js 底层存储数据的方案是这样的。number 都是 双精度 64 位二进制格式 IEEE 754

所以规避方法就是不用小数,直接整数计算。

当然,你这个看上去是是个 tofixed

Math.signFigures = function(num, rank = 6) {
  if(!num) return(0);
  const sign = num / Math.abs(num);
  const number = num * sign;
  const temp = rank - 1 - Math.floor(Math.log10(number));
  let ans;
  if (temp > 0) {
      ans = parseFloat(number.toFixed(temp));
  }
  else if (temp < 0) {
      ans = Math.round(number / Math.pow(10, temp)) * temp;
  }
  else {
      ans = Math.round(number);
  }
  return (ans * sign);
};
console.log(Math.signFigures(0.1 + 0.2), 0.1 + 0.2)
console.log(Math.signFigures(0.30000000000000004), 0.30000000000000004)
console.log(Math.signFigures(0.987654321), 0.987654321)

console.log(Math.signFigures(0.14 * 100), 0.14 * 100)

image.png


看了一下 java 好像也这样

image.png

本文参与了SegmentFault 思否面试闯关挑战赛,欢迎正在阅读的你也加入。

js 中进行小数计算时有误差是因为使用了 IEE754 的标准,换句话来说,任何使用 IEE754 标准的言语中都会有相同的问题,既然本质原因是小数计算的时候有问题,那么解决方案就可以变成,将小数转成整数进行计算,然后转回小数就好了,只不过要考虑正负数。

本文参与了SegmentFault 思否面试闯关挑战赛,欢迎正在阅读的你也加入。

在计算浮点数时,由于 JavaScript 内部使用二进制来表示浮点数,因此存在精度丢失的问题。例如,0.1 的二进制表示为 0.0001100110011001100110011001100110011001100110011...,这是一个无限循环的二进制小数,无法精确表示。

这个方法中使用了以下技巧来解决精度问题:

  1. 通过计算符号,将浮点数转化为正数来处理,这样可以避免一些问题。
  2. 使用 toFixed 方法将浮点数转化为字符串,并保留指定有效位数。
  3. 对于一些精度不足的小数,例如 0.1 和 0.2,会出现一个类似于 0.30000000000000004 的结果。这时候,使用 Math.round 和 Math.pow 方法进行处理,将小数转化为整数,再除以 10 的幂次方,最后再乘上幂次方即可。
本文参与了SegmentFault 思否面试闯关挑战赛,欢迎正在阅读的你也加入。

关于精度问题,不是只有JavaScript才存在。

所有编程语言都存在计算精度问题,这是因为计算机在进行数字计算时使用的是二进制数,而在二进制中,有些小数是无法被精确表示的,比如0.1,它在二进制中表示为0.00011001100110011......(无限循环),因此计算机在进行浮点数计算时,往往会出现精度损失。

JavaScript中的精度问题主要是由于使用浮点数所导致的。

在JavaScript中,数字类型分为整数和浮点数两种。整数类型使用64位二进制补码表示,可以表示的范围是-2^53 ~ 2^53,超出这个范围就会出现精度丢失的问题。而浮点数类型使用IEEE 754标准表示,它只能精确表示部分小数,其他的小数位只能近似表示。这就导致了在进行小数计算时会出现精度丢失的问题。

例如,当我们在JavaScript中进行如下计算时:
0.1 + 0.2 = 0.30000000000000004
这是因为0.1和0.2在使用二进制表示时是无限循环小数,而JavaScript中只能用近似值表示它们,所以在进行计算时就会出现精度丢失的问题。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏