【问题标题】:Avoiding problems with JavaScript's weird decimal calculations避免 JavaScript 奇怪的十进制计算问题
【发布时间】:2011-02-18 05:06:39
【问题描述】:

just read on MDN JS 处理数字的怪癖之一是 “双精度 64 位格式 IEEE 754 值” 是当您执行类似 .2 + .1 的操作时你会得到0.30000000000000004(这就是文章的内容,但我在Firefox中得到0.29999999999999993)。因此:

(.2 + .1) * 10 == 3

计算为false

这似乎很成问题。那么如何避免由于 JS 中的小数计算不精确而导致的错误呢?

我注意到,如果您选择1.2 + 1.1,您会得到正确的答案。那么,您是否应该避免任何涉及值小于 1 的数学运算?因为这看起来很不切实际。用 JS 做数学还有其他危险吗?

编辑:
我知道许多小数部分不能存储为二进制,但我遇到的大多数其他语言似乎处理错误的方式(如 JS 处理大于 1 的数字)似乎更直观,所以我不习惯这就是为什么我想看看其他程序员是如何处理这些计算的。

【问题讨论】:

  • 浮点数几乎从来没有与任何语言的其他数字完美比较。
  • 四舍五入是你的朋友。
  • @mellamokb:所以你必须将所有内容乘以 10 的幂,然后四舍五入到最接近的整数?
  • @Lèse $ php -r 'var_dump((0.1 + 0.2) * 10 == 3);'bool(false) :)
  • @mellamokb 好吧,我遇到了舍入问题。 0.5 应四舍五入为 1,但我得到的不是 0.5,而是 0.4999......8,它正在四舍五入为 0。

标签: javascript math decimal


【解决方案1】:

1.2 + 1.1 可能没问题,但 0.2 + 0.1 可能不行。

这是当今使用的几乎所有语言中的一个问题。问题是 1/10 不能准确地表示为二进制分数,就像 1/3 不能表示为十进制分数一样。

解决方法包括仅四舍五入到您需要的小数位数,或者使用准确的字符串:

(0.2 + 0.1).toFixed(4) === 0.3.toFixed(4) // true

或者您可以在此之后将其转换为数字:

+(0.2 + 0.1).toFixed(4) === 0.3 // true

或使用 Math.round:

Math.round(0.2 * X + 0.1 * X) / X === 0.3 // true

X 是 10 的幂,例如100 或 10000 - 取决于您需要的精度。

或者您可以在数钱时使用美分而不是美元:

cents = 1499; // $14.99

这样您只需要处理整数,根本不用担心十进制和二进制小数。

2017 年更新

在 JavaScript 中表示数字的情况可能比以前复杂一点。 过去是这样,我们在 JavaScript 中只有一种数字类型:

现在不再是这种情况了 - 现在 JavaScript 中不仅有更多的数值类型,而且还在开发中,包括向 ECMAScript 添加任意精度整数的提议,并希望,随后将出现任意精度的小数 - 有关详细信息,请参阅此答案:

另见

另一个相关答案,其中包含一些如何处理计算的示例:

【讨论】:

  • toFixed 返回一个数字的字符串表示,不确定这是不是你想要的。
  • 很好的解释!
【解决方案2】:

在这种情况下,您通常宁愿使用 epsilon 估计。

类似(伪代码)

if (abs(((.2 + .1) * 10) - 3) > epsilon)

其中 epsilon 类似于 0.00000001,或者您需要的任何精度。

快速阅读Comparing floating point numbers

【讨论】:

【解决方案3】:
(Math.floor(( 0.1+0.2 )*1000))/1000

这会降低浮点数的精度,但如果您不使用非常小的值,则可以解决问题。 例如:

.1+.2 =
0.30000000000000004

在建议的操作之后,您将获得 0.3 但介于以下之间的任何值:

0.30000000000000000
0.30000000000000999

也将被视为 0.3

【讨论】:

    【解决方案4】:

    libraries that seek to solve this problem,但如果您不想包含其中之一(或由于某种原因不能包含,例如在 GTM variable 中工作),那么您可以使用我写的这个小函数:

    用法:

    var a = 194.1193;
    var b = 159;
    a - b; // returns 35.11930000000001
    doDecimalSafeMath(a, '-', b); // returns 35.1193
    

    函数如下:

    function doDecimalSafeMath(a, operation, b, precision) {
        function decimalLength(numStr) {
            var pieces = numStr.toString().split(".");
            if(!pieces[1]) return 0;
            return pieces[1].length;
        }
    
        // Figure out what we need to multiply by to make everything a whole number
        precision = precision || Math.pow(10, Math.max(decimalLength(a), decimalLength(b)));
    
        a = a*precision;
        b = b*precision;
    
        // Figure out which operation to perform.
        var operator;
        switch(operation.toLowerCase()) {
            case '-':
                operator = function(a,b) { return a - b; }
            break;
            case '+':
                operator = function(a,b) { return a + b; }
            break;
            case '*':
            case 'x':
                precision = precision*precision;
                operator = function(a,b) { return a * b; }
            break;
            case '÷':
            case '/':
                precision = 1;
                operator = function(a,b) { return a / b; }
            break;
    
            // Let us pass in a function to perform other operations.
            default:
                operator = operation;
        }
    
        var result = operator(a,b);
    
        // Remove our multiplier to put the decimal back.
        return result/precision;
    }
    

    【讨论】:

      【解决方案5】:

      了解浮点运算中的舍入误差不适合胆小的人!基本上,计算就像有无穷大的精度可用一样完成。然后根据相关 IEEE 规范中规定的规则对结果进行四舍五入。

      这个四舍五入可以抛出一些时髦的答案:

      Math.floor(Math.log(1000000000) / Math.LN10) == 8 // true
      

      这是一个一个完整的数量级。这是一些舍入错误!

      对于任何浮点架构,都有一个数字表示可区分数字之间的最小间隔。它被称为 EPSILON。

      它将在不久的将来成为 EcmaScript 标准的一部分。同时,可以如下计算:

      function epsilon() {
          if ("EPSILON" in Number) {
              return Number.EPSILON;
          }
          var eps = 1.0; 
          // Halve epsilon until we can no longer distinguish
          // 1 + (eps / 2) from 1
          do {
              eps /= 2.0;
          }
          while (1.0 + (eps / 2.0) != 1.0);
          return eps;
      }
      

      然后你可以使用它,像这样:

      function numericallyEquivalent(n, m) {
          var delta = Math.abs(n - m);
          return (delta < epsilon());
      }
      

      或者,由于舍入误差会累积得惊人,您可能希望使用delta / 2delta * delta 而不是delta

      【讨论】:

      【解决方案6】:

      你需要一些错误控制。

      做一点双重比较的方法:

      int CompareDouble(Double a,Double b) {
          Double eplsilon = 0.00000001; //maximum error allowed
      
          if ((a < b + epsilon) && (a > b - epsilon)) {
              return 0;
          }
          else if (a < b + epsilon)
              return -1;
          }
          else return 1;
      }
      

      【讨论】:

        【解决方案7】:

        正如我在处理货币值时发现的那样,我找到了一个解决方案,只需将值更改为美分,因此我做了以下操作:

        result = ((value1*100) + (value2*100))/100;
        

        使用货币价值时,我们只有两个小数点,这就是我乘以 100 并跳水的原因。 如果您要使用更多的十进制房子,那么您必须乘以当时的十进制房子的数量,有:

        • .0 -> 10
        • .00 -> 100
        • .000 -> 1000
        • .0000 -> 10000 ...

        有了这个,你总是可以避免使用十进制值。

        【讨论】:

          【解决方案8】:

          通过乘法将小数转换为整数,然后最后将结果除以相同的数字。

          你的例子:

          (0.2 * 100 + 0.1 * 100) / 100 * 10 === 3

          【讨论】:

            猜你喜欢
            • 2021-07-23
            • 1970-01-01
            • 2018-04-14
            • 2015-03-10
            • 2014-06-05
            • 2014-02-12
            • 1970-01-01
            • 2018-03-26
            • 1970-01-01
            相关资源
            最近更新 更多