我有以下虚拟测试脚本:

函数测试(){变量x=0.1*0.2;document.write(x);}测试();

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

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

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


当前回答

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

二进制浮点中的0.1与十进制中的1/3相似(即永远为0.333333333333333…),只是没有准确的方法来处理它。

如果您处理的是浮点数,那么总是会出现小的舍入误差,因此您也必须将显示的结果舍入到合理的值。作为回报,您可以得到非常快速和强大的算法,因为所有计算都是在处理器的本地二进制中进行的。

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

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

其他回答

首先将两个数字都设为整数,执行表达式,然后对结果进行除法运算,以返回小数点:

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)

我在MOD3中遇到了一个严重的舍入错误问题。有时当我应该得到0时,我会得到.000…01。这很容易处理,只需测试<=0.01即可。但有时我会得到2.99999999999998。哎哟!

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);

试试我的千年算术库,你可以在这里看到。如果你想要更高版本,我可以给你买一个。

我正在寻找相同的修复程序,我发现如果您在1中添加一个整数并计算console.log(0.1*0.2+1);。结果是1.02。这可用于将原始x变量舍入为正确的值。

在您的示例中检索到小数位数2的长度后,我们可以将其与toFixed()函数一起使用,以正确舍入原始的x变量。

在代码内部查看此函数在注释部分中的作用。

var myX=0.2*0.1;var myX=42.5-42.65;var myX=123+333+33.33+3333.3+333+333;console.log(myX);//输出(示例1):0.020000000000000004//输出(示例2):-0.149999999999999858//输出(示例3):34458.630000000005//错误函数fixRoundingError(x){// 1. 舍入到最接近的10//在其他一些情况下,还将值的舍入值加1,稍后将使用原始的x变量来获得校正的结果。var xRound=eval(x.toFixed(10))+1;// 2. 使用正则表达式,删除所有数字,直到计算出修正方程的小数位数。。var xDec=xRound.toString().replace(/\d+\.+/gm,“”);// 3. 获取小数位数的长度。var xDecLen=xDeclength;// 4. 使用原始x变量和十进制长度来解决舍入问题。var x=eval(x).toFixed(xDecLen);// 5. 计算新的x变量,以删除任何不需要的尾随0(如果有)。返回eval(x);}console.log(fixRoundingError(myX));//输出(示例1):0.02//输出(示例2):-0.15//输出(示例3):34458.63//正确

在我尝试过的每一种情况下,它都会返回与窗口中计算器相同的值,如果有任何尾随的0,也会自动循环结果。

令人惊讶的是,这个函数还没有发布,尽管其他函数也有类似的变体。它来自MDN web docs for Math.rround()。它简洁,允许不同的精度。

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('按钮');btn.onclick=函数(){inp[2].value=precisionRound(parseFloat(inp[0].value)*parseFlat(inp[1].value,5);};//MDN功能函数精度Round(数字,精度){var因子=数学功率(10,精度);return数学舍入(number*factor)/factor;}按钮{显示:块;}<输入类型=“文本”值=“0.1”><输入类型=“文本”值=“0.2”><button>获取产品</button><input-type='text'>

更新日期:2019年8月20日

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

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

Fix:

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.rround()页面,所以也许有人可以提供更好的解决方案。