【问题标题】:How to deal with floating point number precision in JavaScript?如何处理 JavaScript 中的浮点数精度?
【发布时间】:2022-01-20 17:50:47
【问题描述】:

我有以下虚拟测试脚本:

function test() {
  var x = 0.1 * 0.2;
  document.write(x);
}
test();

这将打印结果0.020000000000000004,而它应该只打印0.02(如果您使用计算器)。据我了解,这是由于浮点乘法精度的错误造成的。

有没有人有一个好的解决方案,以便在这种情况下我得到正确的结果0.02?我知道有像toFixed 这样的功能,或者舍入是另一种可能性,但我真的希望打印整数而不进行任何切割和舍入。只是想知道你们中的某个人是否有一些不错的、优雅的解决方案。

当然,否则我会四舍五入到 10 位左右。

【问题讨论】:

  • 其实报错是因为没有办法将0.1映射成有限的二进制浮点数。
  • 大多数分数不能以精确的精度转换为小数。一个很好的解释在这里:docs.python.org/release/2.5.1/tut/node16.html
  • @SalmanA:你的 JavaScript 运行时对你隐藏了这个问题并不意味着我错了。
  • 不同意 Aaron 的观点,有一些方法可以完美且完全地用二进制编码 0.1。但 IEEE 754 不一定对此进行定义。想象一下一种表示形式,您一方面用二进制编码整数部分,另一方面用小数部分编码,最多 n 位小数,也用二进制编码,就像一个普通整数 > 0,最后是小数点的位置.好吧,您将完美地表示 0.1,没有错误。顺便说一句,由于 JS 在内部使用有限数量的小数,因此他们的开发人员不妨编写代码,以免在最后一个小数上犯错误。

标签: javascript floating-point


【解决方案1】:

来自Floating-Point Guide

我可以做些什么来避免这个问题?

这取决于是什么类型的 你正在做的计算。

  • 如果您确实需要将结果精确相加,尤其是当您 用钱工作:使用特殊的小数 数据类型。
  • 如果您不想看到所有这些多余的小数位:只需 格式化你的结果四舍五入到一个固定 时的小数位数 显示它。
  • 如果您没有可用的十进制数据类型,另一种方法是工作 带整数,例如赚钱 完全以美分计算。但 这是更多的工作,并且有一些 缺点。

请注意,第一点仅适用于您确实需要特定精确的十进制 行为。大多数人不需要这个,他们只是对他们的程序不能正确处理像 1/10 这样的数字感到恼火,却没有意识到如果出现 1/3,他们甚至不会因同样的错误而眨眼。

如果第一点真的适用于您,请使用BigDecimal for JavaScript,它一点也不优雅,但实际上解决了问题,而不是提供不完美的解决方法。

【讨论】:

  • 我注意到您的 BigDecimal 死链接,在寻找镜像时,我发现了一个名为 BigNumber 的替代方案:jsfromhell.com/classes/bignumber
  • @bass-t:是的,但是浮点数可以精确地表示整数,直到有效数字的长度,并且根据 ECMA 标准,它是 64 位浮点数。所以它可以精确地表示最大为 2^52 的整数
  • @Karl:十进制小数 1/10 不能表示为以 2 为底的有限二进制小数,这就是 Javascript 数字。所以它其实完全一样的问题。
  • 我今天了解到,即使整数在 javascript 中也存在精度问题。考虑console.log(9332654729891549) 实际上打印9332654729891548(即减一!)
  • @mlathe: Doh..;P... 在2⁵²=4,503,599,627,370,4962⁵³=9,007,199,254,740,992 之间,可表示的数字是完全是整数 .对于下一个范围,从2⁵³2⁵⁴,所有内容都乘以2,因此可表示的数字是偶数等。 相反,对于之前从2⁵¹2⁵² 的范围,间距为0.5等等。 这是由于简单地增加|减少基数|基数2|二进制exponent in/of 64 位浮点值(这反过来解释了 toPrecision() 对于 01 之间的值的很少记录的“意外”行为)。
【解决方案2】:

我喜欢 Pedro Ladaria 的解决方案并使用类似的方法。

function strip(number) {
    return (parseFloat(number).toPrecision(12));
}

与 Pedros 解决方案不同,这将四舍五入 0.999...重复,并且精确到最低有效数字的加/减一。

注意:处理 32 或 64 位浮点数时,应使用 toPrecision(7) 和 toPrecision(15) 以获得最佳结果。有关原因的信息,请参阅this question

【讨论】:

  • 您选择 12 的任何原因?
  • toPrecision 返回字符串而不是数字。这可能并不总是可取的。
  • parseFloat(1.005).toPrecision(3) => 1.00
  • @user2428118,我知道,我的意思是显示舍入误差,结果是 1.00 而不是 1.01
  • @user2428118 所说的可能不够明显:(9.99*5).toPrecision(2) = 50 而不是 49.95 因为 toPrecision 计算整数,而不仅仅是小数.然后你可以使用toPrecision(4),但如果你的结果>100,那么你又不走运了,因为它会允许前三个数字和一个小数,这样会移动点,并使其或多或少无法使用.我最终改用toFixed(2)
【解决方案3】:

数学爱好者:http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

推荐的方法是使用校正因子(乘以适当的 10 次方,以便在整数之间进行算术运算)。例如0.1 * 0.2的情况下,校正因子为10,你正在执行计算:

> var x = 0.1
> var y = 0.2
> var cf = 10
> x * y
0.020000000000000004
> (x * cf) * (y * cf) / (cf * cf)
0.02

一个(非常快速的)解决方案看起来像:

var _cf = (function() {
  function _shift(x) {
    var parts = x.toString().split('.');
    return (parts.length < 2) ? 1 : Math.pow(10, parts[1].length);
  }
  return function() { 
    return Array.prototype.reduce.call(arguments, function (prev, next) { return prev === undefined || next === undefined ? undefined : Math.max(prev, _shift (next)); }, -Infinity);
  };
})();

Math.a = function () {
  var f = _cf.apply(null, arguments); if(f === undefined) return undefined;
  function cb(x, y, i, o) { return x + f * y; }
  return Array.prototype.reduce.call(arguments, cb, 0) / f;
};

Math.s = function (l,r) { var f = _cf(l,r); return (l * f - r * f) / f; };

Math.m = function () {
  var f = _cf.apply(null, arguments);
  function cb(x, y, i, o) { return (x*f) * (y*f) / (f * f); }
  return Array.prototype.reduce.call(arguments, cb, 1);
};

Math.d = function (l,r) { var f = _cf(l,r); return (l * f) / (r * f); };

在这种情况下:

> Math.m(0.1, 0.2)
0.02

我绝对推荐使用经过测试的库,例如 SinfulJS

【讨论】:

  • 我喜欢这种优雅的解决方法,但似乎并不完美:jsfiddle.net/Dm6F5/1 Math.a(76.65, 38.45) 返回 115.10000000000002
  • Math.m(10,2332226616) 给了我“-19627406800”,这是一个负值......我希望必须有一个上限 - 可能是导致这个问题的原因。请建议
  • 这一切看起来都不错,但似乎在某个地方有一两个错误。
  • 他说的非常快速的解决方案...没有人说过的坏修复。
  • 不要使用上面的代码。如果它不起作用,这绝对不是一个“快速解决方案”。这是一道数学题,所以要求准确。
【解决方案4】:

你只执行乘法吗?如果是这样,那么您可以利用关于十进制算术的巧妙秘密。那就是NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals。也就是说,如果我们有0.123 * 0.12,那么我们知道会有5个小数位,因为0.123有3个小数位,0.12有2个小数位。因此,如果 JavaScript 给我们一个像 0.014760000002 这样的数字,我们可以安全地四舍五入到小数点后 5 位,而不必担心丢失精度。

【讨论】:

  • ...以及如何获得准确个小数位数。
  • 0.5 * 0.2 = 0.10;您仍然可以截断小数点后 2 位(或更少)。但是永远不会有一个数字在这个定律之外具有任何数学意义。
  • 您对此有引用吗?另请注意,除法并非如此。
  • @NateZaugg 你不能截断溢出的小数,你必须四舍五入,因为 2090.5 * 8.61 是 17999.205 但浮动它是 17999.204999999998
  • @Lostfields - 你是对的!我已经更新了我的答案。
【解决方案5】:

我发现 BigNumber.js 满足我的需求。

用于任意精度十进制和非十进制算术的 JavaScript 库。

documentation不错,作者回复反馈很用心。

同一作者还有 2 个其他类似的库:

Big.js

用于任意精度十进制算术的小型快速 JavaScript 库。 bignumber.js 的小妹妹。

Decimal.js

JavaScript 的任意精度 Decimal 类型。

这是一些使用 BigNumber 的代码:

$(function(){

  
  var product = BigNumber(.1).times(.2);  
  $('#product').text(product);

  var sum = BigNumber(.1).plus(.2);  
  $('#sum').text(sum);


});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<!-- 1.4.1 is not the current version, but works for this example. -->
<script src="http://cdn.bootcss.com/bignumber.js/1.4.1/bignumber.min.js"></script>

.1 &times; .2 = <span id="product"></span><br>
.1 &plus; .2 = <span id="sum"></span><br>

【讨论】:

【解决方案6】:

令人惊讶的是,该功能尚未发布,尽管其他功能也有类似的变体。它来自 Math.round() 的 MDN 网络文档。 它简明扼要,允许不同的精度。

function precisionRound(number, precision) {
    var factor = Math.pow(10, precision);
    return Math.round(number * factor) / factor;
}

console.log(precisionRound(1234.5678, 1));
// expected output: 1234.6

console.log(precisionRound(1234.5678, -1));
// expected output: 1230

var inp = document.querySelectorAll('input');
var btn = document.querySelector('button');

btn.onclick = function(){
  inp[2].value = precisionRound( parseFloat(inp[0].value) * parseFloat(inp[1].value) , 5 );
};

//MDN function
function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}
button{
display: block;
}
<input type='text' value='0.1'>
<input type='text' value='0.2'>
<button>Get Product</button>
<input type='text'>

更新:2019 年 8 月 20 日

刚刚注意到这个错误。我相信这是由于 Math.round() 的浮点精度错误造成的。

precisionRound(1.005, 2) // produces 1, incorrect, should be 1.01

这些条件正常工作:

precisionRound(0.005, 2) // produces 0.01
precisionRound(1.0005, 3) // produces 1.001
precisionRound(1234.5, 0) // produces 1235
precisionRound(1234.5, -1) // produces 1230

修复:

function precisionRoundMod(number, precision) {
  var factor = Math.pow(10, precision);
  var n = precision < 0 ? number : 0.01 / factor + number;
  return Math.round( n * factor) / factor;
}

这只是在四舍五入时在右侧添加一个数字。 MDN 已经更新了Math.round() 页面,所以也许有人可以提供更好的解决方案。

【讨论】:

  • 错误答案。 10.2 将始终返回 10.19。 jsbin.com/tozogiwide/edit?html,js,console,output
  • @Žilvinas 您发布的 JSBin 链接没有使用上面列出的 MDN 功能。我认为您的评论是针对错误的人。
  • Math.ceil 会不会以同样的方式计算 0.01(它使它成为一个整数,然后再向下转换为浮点数 afaik)
  • 哇,谢谢,这非常适合我的需要,使用 12precisionRoundMod 的精度可以满足我的用例!
【解决方案7】:

您正在寻找 JavaScript 的 sprintf 实现,以便您可以以您期望的格式写出带有小错误的浮点数(因为它们以二进制格式存储)。

试试javascript-sprintf,你会这样称呼它:

var yourString = sprintf("%.2f", yourNumber);

将您的数字打印为带有两位小数的浮点数。

您也可以使用Number.toFixed() 进行显示,如果您不想仅仅为了浮点舍入到给定精度而包含更多文件。

【讨论】:

  • 我认为这是最干净的解决方案。除非你真的需要结果为 0.02,否则这个小误差可以忽略不计。听起来重要的是你的号码显示很好,而不是你有任意的精度。
  • 对于显示这确实是最好的选择,对于复杂的计算,请查看 Borgwardt 的答案。
  • 但话又说回来,这将返回与 yourNumber.toFixed(2) 完全相同的字符串。
【解决方案8】:
var times = function (a, b) {
    return Math.round((a * b) * 100)/100;
};

---或者---

var fpFix = function (n) {
    return Math.round(n * 100)/100;
};

fpFix(0.1*0.2); // -> 0.02

---也---

var fpArithmetic = function (op, x, y) {
    var n = {
            '*': x * y,
            '-': x - y,
            '+': x + y,
            '/': x / y
        }[op];        

    return Math.round(n * 100)/100;
};

--- 如 ---

fpArithmetic('*', 0.1, 0.2);
// 0.02

fpArithmetic('+', 0.1, 0.2);
// 0.3

fpArithmetic('-', 0.1, 0.2);
// -0.1

fpArithmetic('/', 0.2, 0.1);
// 2

【讨论】:

  • 我认为这会导致同样的问题。您返回一个浮点数,因此返回值也很有可能是“不正确的”。
  • 非常聪明实用,+1。
【解决方案9】:

此函数将从两个浮点数相乘中确定所需的精度,并返回具有适当精度的结果。虽然不是很优雅。

function multFloats(a,b){
  var atens = Math.pow(10,String(a).length - String(a).indexOf('.') - 1), 
      btens = Math.pow(10,String(b).length - String(b).indexOf('.') - 1); 
  return (a * atens) * (b * btens) / (atens * btens); 
}

【讨论】:

  • Ew。是的,让我们将数字转换为字符串以进行浮点数学运算,我们也建议将其作为答案。
【解决方案10】:

如果您想绕过此问题进行小操作,可以使用parseFloat()toFixed()

a = 0.1;
b = 0.2;

a + b = 0.30000000000000004;

c = parseFloat((a+b).toFixed(2));

c = 0.3;

a = 0.3;
b = 0.2;

a - b = 0.09999999999999998;

c = parseFloat((a-b).toFixed(2));

c = 0.1;

【讨论】:

    【解决方案11】:

    你只需要决定你真正想要多少个十进制数字 - 不能既吃蛋糕又吃它:-)

    数字错误随着每一次进一步的操作而累积,如果你不及早将其切断,它只会增加。呈现看起来干净的结果的数值库只是在每一步中截断最后 2 位数字,出于同样的原因,数值协处理器也具有“正常”和“完整”长度。 Cuf-offs 对于处理器来说很便宜,但在脚本中对你来说非常昂贵(乘法和除法以及使用 pov(...))。好的数学库会提供 floor(x,n) 来为你做截止。

    因此,至少您应该使用 pov(10,n) 制作全局 var/constant - 这意味着您决定了所需的精度 :-) 然后执行:

    Math.floor(x*PREC_LIM)/PREC_LIM  // floor - you are cutting off, not rounding
    

    您也可以继续做数学运算,只在最后截断 - 假设您只显示结果而不做 if-s。如果你能做到这一点,那么 .toFixed(...) 可能会更有效。

    如果您正在执行 if-s/comparisons 并且不想削减,那么您还需要一个小常数,通常称为 eps,它比最大预期误差高一位小数。假设您的截止值是最后两位小数 - 那么您的 eps 在倒数第三位(第三位最低有效位)为 1,您可以使用它来比较结果是否在预期的 eps 范围内(0.02 -eps

    【讨论】:

    • 你也可以加上 0.5 来做一个穷人的四舍五入:Math.floor(x*PREC_LIM + 0.5)/PREC_LIM
    • 请注意,例如Math.floor(-2.1)-3。所以也许使用例如Math[x&lt;0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
    • 为什么是floor 而不是round
    【解决方案12】:

    phpjs.org 上的 round() 函数运行良好:http://phpjs.org/functions/round

    num = .01 + .06;  // yields 0.0699999999999
    rnum = round(num,12); // yields 0.07
    

    【讨论】:

    • @jrg 按照惯例,以“5”结尾的数字会四舍五入到最接近的偶数(因为总是向上或向下四舍五入会给您的结果带来偏差)。因此,四舍五入到小数点后两位的 4.725 确实应该是 4.72。
    【解决方案13】:

    请注意,对于一般用途,这种行为可能是可以接受的。
    比较这些浮点值以确定适当的操作时会出现问题。
    随着 ES6 的出现,定义了一个新的常量Number.EPSILON 来确定可接受的误差范围:
    所以不要像这样进行比较

    0.1 + 0.2 === 0.3 // which returns false
    

    你可以定义一个自定义的比较函数,像这样:

    function epsEqu(x, y) {
        return Math.abs(x - y) < Number.EPSILON;
    }
    console.log(epsEqu(0.1+0.2, 0.3)); // true
    

    来源:http://2ality.com/2015/04/numbers-math-es6.html#numberepsilon

    【讨论】:

    • 在我的情况下 Number.EPSILON 太小,导致例如0.9 !== 0.8999999761581421
    【解决方案14】:

    在不同语言、处理器和操作系统的浮点实现中,您得到的结果是正确且相当一致的——唯一改变的是当浮点数实际上是双精度(或更高)时的不准确程度。

    二进制浮点数的 0.1 就像十进制数的 1/3(即 0.3333333333333... 永远),只是没有准确的方法来处理它。

    如果您正在处理浮点数总是预计会出现小的舍入误差,因此您还必须始终将显示的结果舍入到合理的值。作为回报,您将获得非常快速且强大的算法,因为所有计算都在处理器的本机二进制文件中。

    大多数情况下,解决方案不是切换到定点算术,主要是因为它的速度要慢得多,而且 99% 的时间您不需要精度。如果您正在处理确实需要这种准确性的东西(例如金融交易),Javascript 可能不是最好的工具(因为您想要强制执行定点类型,静态语言可能会更好)。

    您正在寻找优雅的解决方案,那么恐怕就是这样:浮点数很快但舍入误差很小 - 在显示结果时总是舍入到合理的值。

    【讨论】:

      【解决方案15】:

      0.6 * 3 太棒了!)) 对我来说,这很好用:

      function dec( num )
      {
          var p = 100;
          return Math.round( num * p ) / p;
      }
      

      非常非常简单))

      【讨论】:

      • 这是否适用于 8.22e-8 * 1.3 之类的东西?
      • 0.6 x 3 = 1.8,你给 2 的结果的代码......所以不好。
      • @Zyo 在这种情况下它返回 1.8。你是怎么运行的?
      • 有趣。您可以在此交换乘法和除法运算符,它也可以。
      【解决方案16】:

      为避免这种情况,您应该使用整数值而不是浮点数。因此,当您希望 2 个位置精度使用值 * 100 时,3 个位置使用 1000。显示时使用格式化程序放入分隔符。

      许多系统都忽略了以这种方式处理小数。这就是为什么许多系统使用美分(作为整数)而不是美元/欧元(作为浮点数)工作的原因。

      【讨论】:

        【解决方案17】:

        问题

        浮点不能精确存储所有十进制值。因此,当使用浮点格式时,输入值总是会出现舍入错误。 输入上的错误当然会导致输出上的错误。 如果是离散函数或运算符,则在函数或运算符离散点附近的输出可能会有很大差异。

        浮点值的输入和输出

        因此,在使用浮点变量时,您应该始终注意这一点。并且无论您想从浮点计算中获得什么输出,都应该在考虑到这一点之前进行格式化/调整。
        当只使用连续函数和运算符时,通常会舍入到所需的精度(不要截断)。用于将浮点数转换为字符串的标准格式化功能通常会为您执行此操作。
        因为舍入会增加一个误差,导致总误差超过所需精度的一半,所以应根据输入的预期精度和输出的预期精度来校正输出。你应该

        • 将输入四舍五入到预期精度,或确保无法输入精度更高的值。
        • 在舍入/格式化输出之前向输出添加一个小值,该值小于或等于所需精度的 1/4,并且大于输入和计算期间舍入误差导致的最大预期误差。如果这不可能,则所用数据类型的精度组合不足以为您的计算提供所需的输出精度。

        这两件事通常不会做,在大多数情况下,由于不做它们造成的差异对于大多数用户来说太小而不重要,但我已经有一个项目,如果没有这些更正,用户不会接受输出。

        离散函数或运算符(如模)

        当涉及离散运算符或函数时,可能需要进行额外的更正以确保输出符合预期。舍入和在舍入前添加小的修正不能解决问题。
        可能需要在应用离散函数或运算符后立即对中间计算结果进行特殊检查/更正。 对于特定情况(模运算符),请参阅我对问题的回答:Why does modulus operator return fractional number in javascript?

        最好避免出现问题

        使用数据类型(整数或定点格式)进行此类计算通常更有效地避免这些问题,这样可以存储预期输入而不会出现舍入错误。 这方面的一个例子是你不应该在财务计算中使用浮点值。

        【讨论】:

          【解决方案18】:

          decimal.jsbig.jsbignumber.js 可用于避免 Javascript 中的浮点操作问题:

          0.1 * 0.2                                // 0.020000000000000004
          x = new Decimal(0.1)
          y = x.times(0.2)                          // '0.2'
          x.times(0.2).equals(0.2)                  // true
          

          big.js:极简主义;使用方便;以小数位指定的精度;精度仅适用于除法。

          bignumber.js:基数 2-64;配置选项;钠;无穷;以小数位指定的精度;精度仅适用于除法;基本前缀。

          decimal.js:以 2-64 为基数;配置选项;钠;无穷;非整数幂,exp,ln,log;以有效数字指定的精度;始终应用精度;随机数。

          link to detailed comparisons

          【讨论】:

            【解决方案19】:

            不优雅,但可以完成工作(删除尾随零)

            var num = 0.1*0.2;
            alert(parseFloat(num.toFixed(10))); // shows 0.02
            

            【讨论】:

            【解决方案20】:

            优雅、可预测和可重复使用

            让我们以一种优雅的可重用方式来处理这个问题。以下七行将让您通过将.decimal 附加到数字、公式或内置Math 函数的末尾来访问任何数字所需的浮点精度。

            // First extend the native Number object to handle precision. This populates
            // the functionality to all math operations.
            
            Object.defineProperty(Number.prototype, "decimal", {
              get: function decimal() {
                Number.precision = "precision" in Number ? Number.precision : 3;
                var f = Math.pow(10, Number.precision);
                return Math.round( this * f ) / f;
              }
            });
            
            
            // Now lets see how it works by adjusting our global precision level and 
            // checking our results.
            
            console.log("'1/3 + 1/3 + 1/3 = 1' Right?");
            console.log((0.3333 + 0.3333 + 0.3333).decimal == 1); // true
            
            console.log(0.3333.decimal); // 0.333 - A raw 4 digit decimal, trimmed to 3...
            
            Number.precision = 3;
            console.log("Precision: 3");
            console.log((0.8 + 0.2).decimal); // 1
            console.log((0.08 + 0.02).decimal); // 0.1
            console.log((0.008 + 0.002).decimal); // 0.01
            console.log((0.0008 + 0.0002).decimal); // 0.001
            
            Number.precision = 2;
            console.log("Precision: 2");
            console.log((0.8 + 0.2).decimal); // 1
            console.log((0.08 + 0.02).decimal); // 0.1
            console.log((0.008 + 0.002).decimal); // 0.01
            console.log((0.0008 + 0.0002).decimal); // 0
            
            Number.precision = 1;
            console.log("Precision: 1");
            console.log((0.8 + 0.2).decimal); // 1
            console.log((0.08 + 0.02).decimal); // 0.1
            console.log((0.008 + 0.002).decimal); // 0
            console.log((0.0008 + 0.0002).decimal); // 0
            
            Number.precision = 0;
            console.log("Precision: 0");
            console.log((0.8 + 0.2).decimal); // 1
            console.log((0.08 + 0.02).decimal); // 0
            console.log((0.008 + 0.002).decimal); // 0
            console.log((0.0008 + 0.0002).decimal); // 0

            干杯!

            【讨论】:

            • 如果你选择投反对票,至少要说明理由。
            • 我没有投反对票,但是虽然这很优雅且可重用,但 JavaScript 原始类型对象的猴子补丁不太可能是可预测的。一些these 的担忧似乎适用。
            • 试试:((0.1*3)*1e14).decimal
            • @BobRodes 我完全同意这是一个猴子补丁,由于相关原因,它不适合某些项目。但对许多人来说,这种解决方案是两害相权取其轻的理想解决方案。
            • @trincot 同意。它可以走多远肯定是有限度的
            【解决方案21】:

            看看Fixed-point arithmetic。如果您要操作的数字范围很小(例如货币),它可能会解决您的问题。我会将它四舍五入到几个十进制值,这是最简单的解决方案。

            【讨论】:

            • 问题不是浮点与定点,问题是二进制与十进制。
            【解决方案22】:

            您不能用二进制浮点类型(ECMAScript 用来表示浮点值)精确地表示大多数小数。因此,除非您使用任意精度的算术类型或基于十进制的浮点类型,否则没有一个优雅的解决方案。例如,the Calculator app that ships with Windows now uses arbitrary precision arithmetic to solve this problem

            【讨论】:

              【解决方案23】:

              避免在使用整数的操作过程中处理浮点数

              正如迄今为止投票最多的答案所述,您可以使用整数,这意味着您使用的每个小数都将所有因子乘以 10,然后将结果除以使用相同的号码。

              例如,如果您使用 2 位小数,则在运算前将所有因子乘以 100,然后将结果除以 100。

              这里是一个例子,Result1 是通常的结果,Result2 使用解决方案:

              var Factor1="1110.7";
              var Factor2="2220.2";
              var Result1=Number(Factor1)+Number(Factor2);
              var Result2=((Number(Factor1)*100)+(Number(Factor2)*100))/100;
              var Result3=(Number(parseFloat(Number(Factor1))+parseFloat(Number(Factor2))).toPrecision(2));
              document.write("Result1: "+Result1+"<br>Result2: "+Result2+"<br>Result3: "+Result3);

              第三个结果是显示在使用 parseFloat 时会发生什么,这在我们的案例中造成了冲突。

              【讨论】:

              • 我喜欢这个,因为它很简单。但是,您仍然需要担心任何大数字。 “1120003000600.126” * 1 仍然出现在 1120003000600.126 “11200030006000.126” * 1 仍然出现在 1120003000600.127 这使得任何解决方案都很痛苦,任何超过 13 位的数字都会被破坏
              【解决方案24】:

              首先将两个数字都设为整数,然后执行表达式,然后将结果除以返回小数位,从而解决了这个问题:

              function evalMathematicalExpression(a, b, op) {
                  const smallest = String(a < b ? a : b);
                  const factor = smallest.length - smallest.indexOf('.');
              
                  for (let i = 0; i < factor; i++) {
                      b *= 10;
                      a *= 10;
                  }
              
                  a = Math.round(a);
                  b = Math.round(b);
                  const m = 10 ** factor;
                  switch (op) {
                      case '+':
                          return (a + b) / m;
                      case '-':
                          return (a - b) / m;
                      case '*':
                          return (a * b) / (m ** 2);
                      case '/':
                          return a / b;
                  }
              
                  throw `Unknown operator ${op}`;
              }
              
              

              几个操作的结果(排除的数字是来自eval的结果):

              0.1 + 0.002   = 0.102 (0.10200000000000001)
              53 + 1000     = 1053 (1053)
              0.1 - 0.3     = -0.2 (-0.19999999999999998)
              53 - -1000    = 1053 (1053)
              0.3 * 0.0003  = 0.00009 (0.00008999999999999999)
              100 * 25      = 2500 (2500)
              0.9 / 0.03    = 30 (30.000000000000004)
              100 / 50      = 2 (2)
              

              【讨论】:

                【解决方案25】:

                从我的角度来看,这里的想法是对 fp 数进行四舍五入,以便获得漂亮/简短的默认字符串表示形式。

                53 位有效数字精度提供 15 到 17 位有效十进制数字精度 (2−53 ≈ 1.11 × 10−16)。 如果将最多 15 个有效数字的十进制字符串转换为 IEEE 754 双精度表示, 然后转换回一个位数相同的十进制字符串,最终结果应该与原始字符串匹配。 如果将 IEEE 754 双精度数转换为具有至少 17 位有效数字的十进制字符串, 再转回双精度表示,最终结果必须与原数匹配。
                ...
                由于小数 (F) 有效数的 52 位出现在内存格式中,因此总精度为 53 位(大约 16 个十进制数字,53 log10(2) ≈ 15.955)。这些位的布局如下...wikipedia

                (0.1).toPrecision(100) ->
                0.1000000000000000055511151231257827021181583404541015625000000000000000000000000000000000000000000000
                
                (0.1+0.2).toPrecision(100) ->
                0.3000000000000000444089209850062616169452667236328125000000000000000000000000000000000000000000000000
                

                然后,据我了解,我们可以将值四舍五入到 15 位以保持良好的字符串表示形式。

                10**Math.floor(53 * Math.log10(2)) // 1e15
                

                例如。

                Math.round((0.2+0.1) * 1e15 ) / 1e15
                0.3
                
                (Math.round((0.2+0.1) * 1e15 ) / 1e15).toPrecision(100)
                0.2999999999999999888977697537484345957636833190917968750000000000000000000000000000000000000000000000
                

                函数是:

                function roundNumberToHaveANiceDefaultStringRepresentation(num) {
                
                    const integerDigits = Math.floor(Math.log10(Math.abs(num))+1);
                    const mult = 10**(15-integerDigits); // also consider integer digits
                    return Math.round(num * mult) / mult;
                }
                

                【讨论】:

                • 这个答案被低估了。 PS:我认为是52 * Math.log10(2),因为它是签名的替身?结果仍然是1e15
                • 为什么不直接做Math.round(num * 1e15) / 1e15
                【解决方案26】:

                试试我的 chiliadic 算术库,你可以看到 here。 如果你想要更高版本,我可以给你一个。

                【讨论】:

                • 一个很好的答案解释了。您的图书馆如何解决问题?
                【解决方案27】:

                你是对的,原因是浮点数的精度有限。将有理数存储为两个整数的除法,在大多数情况下,您将能够存储数字而不会损失任何精度。在打印方面,您可能希望将结果显示为分数。有了我提出的表示,它就变得微不足道了。

                当然,这对无理数没有多大帮助。但是您可能希望以它们会导致最少问题的方式优化您的计算(例如,检测像sqrt(3)^2) 这样的情况。

                【讨论】:

                • 你是对的,原因是浮点数的精度有限&lt;pedant&gt; 实际上,OP 将其归结为不精确的浮点运算,这是错误的&lt;/pedant&gt;
                【解决方案28】:

                我在使用 mod 3 时遇到了一个令人讨厌的舍入错误问题。有时当我应该得到 0 时,我会得到 0.000...01。这很容易处理,只需测试

                BigNumbers 解决了这个问题,但又引入了另一个有点讽刺的问题。当尝试将 8.5 加载到 BigNumbers 中时,我被告知它实际上是 8.4999……并且有超过 15 个有效数字。这意味着 BigNumbers 无法接受(我相信我提到这个问题有点讽刺)。

                讽刺问题的简单解决方案:

                x = Math.round(x*100);
                // I only need 2 decimal places, if i needed 3 I would use 1,000, etc.
                x = x / 100;
                xB = new BigNumber(x);
                

                【讨论】:

                  【解决方案29】:

                      You can use library https://github.com/MikeMcl/decimal.js/. 
                      it will   help  lot to give proper solution. 
                      javascript console output 95 *722228.630 /100 = 686117.1984999999
                      decimal library implementation 
                      var firstNumber = new Decimal(95);
                      var secondNumber = new Decimal(722228.630);
                      var thirdNumber = new Decimal(100);
                      var partialOutput = firstNumber.times(secondNumber);
                      console.log(partialOutput);
                      var output = new Decimal(partialOutput).div(thirdNumber);
                      alert(output.valueOf());
                      console.log(output.valueOf())== 686117.1985
                  

                  【讨论】:

                    【解决方案30】:

                    使用数字(1.234443).toFixed(2);它将打印 1.23

                    function test(){
                        var x = 0.1 * 0.2;
                        document.write(Number(x).toFixed(2));
                    }
                    test();
                    

                    【讨论】:

                      猜你喜欢
                      相关资源
                      最近更新 更多