如何处理 JavaScript 中的浮点数精度?

IT技术 javascript floating-point
2021-01-04 23:04:35

我有以下虚拟测试脚本:

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

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

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

当然,否则我会四舍五入到大约 10 位数。

6个回答

浮点指南

我该怎么做才能避免这个问题?

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

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

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

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

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

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

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

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

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

toPrecision返回一个字符串而不是一个数字。这可能并不总是可取的。
2021-02-07 23:04:35
@ user2428118,我知道,我的意思是显示舍入误差,结果是 1.00 而不是 1.01
2021-02-19 23:04:35
@user2428118 所说的可能不够明显:(9.99*5).toPrecision(2)= 50而不是49.95因为 toPrecision 计算整数,而不仅仅是小数。然后您可以使用toPrecision(4),但是如果您的结果 >100,那么您又不走运了,因为它允许前三个数字和一个小数,这样移动点,并使其或多或少无法使用。我结束了使用toFixed(2)替代
2021-02-21 23:04:35
你有什么理由选择12?
2021-02-27 23:04:35
parseFloat(1.005).toPrecision(3) => 1.00
2021-03-08 23:04:35

对于数学倾向: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这样的经过测试的库

不要使用上面的代码。如果它不起作用,这绝对不是一个“快速解决方案”。这是一个与数学相关的问题,因此需要准确性。
2021-02-12 23:04:35
他说的非常快速的解决方案......没有人说过的坏的解决方案。
2021-02-22 23:04:35
这一切看起来都很棒,但似乎在某处有一两个错误。
2021-03-04 23:04:35
我喜欢这种优雅的解决方法,但似乎并不完美:jsfiddle.net/Dm6F5/1 Math.a(76.65, 38.45) 返回 115.10000000000002
2021-03-06 23:04:35
Math.m(10,2332226616) 给了我“-19627406800”这是一个负值......我希望必须有一个上限 - 可能是导致这个问题的原因。请建议
2021-03-06 23:04:35

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

@Lostfields - 你是对的!我已经更新了我的答案。
2021-02-12 23:04:35
你有这方面的引文吗?另请注意,除法并非如此。
2021-02-14 23:04:35
...以及如何获得精确的小数位数。
2021-03-05 23:04:35
@NateZaugg 你不能截断溢出的小数,你必须四舍五入,因为 2090.5 * 8.61 是 17999.205 但浮点数是 17999.204999999998
2021-03-05 23:04:35
0.5 * 0.2 = 0.10;您仍然可以截断 2 位小数(或更少)。但是永远不会有一个数字超出这条定律具有任何数学意义。
2021-03-06 23:04:35

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

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

它有很好的文档,作者非常勤奋地回应反馈。

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

大.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>

在我看来,使用库绝对是最好的选择。
2021-02-23 23:04:35
从这个链接github.com/MikeMcl/big.js/issues/45 bignumber.js -> 金融十进制.js -> 科学 big.js -> ???
2021-02-23 23:04:35