Javascript舍入失败



这是我们正在使用的舍入函数(取自stackoverflow关于如何舍入的答案)。它将的一半四舍五入到2dp(默认情况下)

例如2.185应该转到2.19

function myRound(num, places) {
    if (places== undefined) {
        // default to 2dp
        return  Math.round(num* 100) / 100;
    }
    var mult = Math.pow(10,places);
    return  Math.round(num* mult) / mult;
}

它运行得很好,但现在我们发现了一些错误(在chrome和IIS 7.5上作为jscript classic asp运行)

例如:

alert(myRound(2.185));      // = 2.19
alert (myRound(122.185));   // = 122.19
alert (myRound(511.185));   // = 511.19
alert (myRound(522.185));   // = 522.18  FAIL!!!!
alert (myRound(625.185));   // = 625.18  FAIL!!!!

有人知道吗:

  1. 为什么会发生这种情况
  2. 我们如何在没有像这样的随机舍入误差的情况下将一半舍入到2 dp

更新:好的,问题的关键是在js中,625.185*100=62518.499999我们怎样才能克服这个问题?

您的问题不容易解决。之所以会出现这种情况,是因为IEEE替身使用的二进制表示法不能准确地表示所有小数。最接近625.185的内部表示是625.184999999994543031789362430572509765625,它始终略小于625.185,并且正确的舍入是向下的。


根据你的情况,你可能会逃脱以下惩罚:

Math.round(Math.round(625.185 * 1000) / 10) / 100    // evaluates to 625.19

然而,这并不是严格正确的,因为例如,它将从625.1847向上取整到625.19。只有当您知道输入的小数位数永远不会超过三位时,才能使用它。


一个更简单的选择是在舍入前添加一个小ε:

Math.round(625.185 * 100 + 1e-6) / 100

这仍然是一个折衷方案,因为您可能会有一个略小于625.185的数字,但它可能比第一个解决方案更健壮。不过要注意负数。

尝试对值使用toFixed函数。示例如下:

var value = parseFloat(2.185);
var fixed = value.toFixed(2);
alert(fixed);

我试过了,效果很好。

编辑:您可以始终使用parseFloat(stringVar)将字符串转换为数字。

第2版:

function myRound(num, places) {
    return parseFloat(num.toFixed(places));
}

编辑3:

更新的答案,测试和工作:

function myRound(num, places) {
    if (places== undefined) {
     places = 2;
    }
    var mult = Math.pow(10,places + 1);
    var mult2 = Math.pow(10,places);
    return  Math.round(num* mult / 10) / mult2;
}

第4版:

对评论中提到的大多数示例进行了测试:

function myRound(num, places) {
    if (places== undefined) {
     places = 2;
    }
    var mult = Math.pow(10,places);
    var val = num* mult;
    var intVal = parseInt(val);
    var floatVal = parseFloat(val);
    if (intVal < floatVal) {
        val += 0.1;
    }
    return Math.round(val) / mult;
}

第5版:我设法找到的唯一解决方案是使用字符串对精确的小数进行四舍五入。解决方案粘贴在下面,带有String原型扩展方法replaceAt。如果有人发现一些不起作用的例子,请检查并告诉我。

function myRound2(num, places) {
    var retVal = null;
    if (places == undefined) {
        places = 2;
    }
    var splits = num.split('.');
    if (splits && splits.length <= 2) {
        var wholePart = splits[0];
        var decimalPart = null;
        if (splits.length > 1) {
            decimalPart = splits[1];
        }
        if (decimalPart && decimalPart.length > places) {
            var roundingDigit = parseInt(decimalPart[places]);
            var previousDigit = parseInt(decimalPart[places - 1]);
            var increment = (roundingDigit < 5) ? 0 : 1;
            previousDigit = previousDigit + increment;
            decimalPart = decimalPart.replaceAt(places - 1, previousDigit + '').substr(0, places);
        }
        retVal = parseFloat(wholePart + '.' + decimalPart);
    }
    return retVal;
}
String.prototype.replaceAt = function (index, character) {
    return this.substr(0, index) + character + this.substr(index + character.length);
}

OK,找到了问题的"完整"解决方案。

首先,从这里下载Big.js:https://github.com/MikeMcl/big.js/

然后修改了源代码,使其可以使用jscript/asp:

/* big.js v2.1.0 https://github.com/MikeMcl/big.js/LICENCE */
var Big = (function ( global ) {
    'use strict';
:
// EXPORT
return Big;
})( this );

然后使用Big类型进行计算,并使用Big to Fixed(dp),然后如此转换回数字:

var bigMult = new Big (multiplier);
var bigLineStake = new Big(lineStake);
var bigWin = bigLineStake.times(bigMult);
var strWin = bigWin.toFixed(2);  // this does the rounding correctly.
var win = parseFloat(strWin);  // back to a number!

这基本上在toFixed中使用了Bigs自己的舍入,这似乎在所有情况下都能正确工作。

Shame Big没有一个方法可以在不经过字符串的情况下转换回数字。

最新更新