【问题标题】:Gaussian/banker's rounding in JavaScriptJavaScript 中的高斯/银行家四舍五入
【发布时间】:2011-03-07 17:50:36
【问题描述】:

我一直在 C# 中使用Math.Round(myNumber, MidpointRounding.ToEven) 进行服务器端舍入,但是,用户需要知道“实时”服务器端操作的结果是什么意思(避免Ajax 请求) 创建一个 JavaScript 方法来复制 C# 使用的 MidpointRounding.ToEven 方法。

MidpointRounding.ToEven 是 Gaussian/banker's rounding,这是 here 描述的会计系统的一种非常常见的舍入方法。

有人有这方面的经验吗?我在网上找到了示例,但它们并没有四舍五入到给定小数位数...

【问题讨论】:

  • 好问题。 this script 是您找到的示例之一吗?看起来它可能是合适的,但我不是这方面的专家:-)
  • 结束了!但不幸的是不适用于负数 - 我会对其进行一些更改并在此处发布...谢谢:)

标签: javascript rounding


【解决方案1】:
function evenRound(num, decimalPlaces) {
    var d = decimalPlaces || 0;
    var m = Math.pow(10, d);
    var n = +(d ? num * m : num).toFixed(8); // Avoid rounding errors
    var i = Math.floor(n), f = n - i;
    var e = 1e-8; // Allow for rounding errors in f
    var r = (f > 0.5 - e && f < 0.5 + e) ?
                ((i % 2 == 0) ? i : i + 1) : Math.round(n);
    return d ? r / m : r;
}

console.log( evenRound(1.5) ); // 2
console.log( evenRound(2.5) ); // 2
console.log( evenRound(1.535, 2) ); // 1.54
console.log( evenRound(1.525, 2) ); // 1.52

现场演示:http://jsfiddle.net/NbvBp/

对于看起来更严格的处理方法(我从未使用过),您可以尝试这个BigNumber 实现。

【讨论】:

  • 我想建议另外两个补充。首先,我会检查Math.pow(10, d) 表达式上的溢出/下溢(至少)。出现此错误并且当 decimalPlaces 为正时,返回 num,否则重新引发该异常。其次,为了补偿 IEEE 二进制转换错误,我会将 f == 0.5 更改为 f &gt;= 0.499999999 &amp;&amp; f &lt;= 0.500000001 - 取决于您选择的“epsilon”(不确定 .toFixed(epsilon) 是否足够)。那你就是金子了!
  • @Marius:好点。我写这篇文章的时候对 JS 数字的了解很粗略,现在也好不了多少,所以我会阅读然后更新。
  • @TimDown 我发现题为“每个计算机科学家应该了解的关于浮点运算的知识”的文章非常宝贵。
  • 实际上 evenRound(0.545,2) 应该舍入到 0.54,而不是 0.55,因此函数会正确返回。如果左边的数字是偶数,它的行为就像向下舍入一样,在这种情况下是 4。
  • 近十年过去了,蒂姆,你仍然用这个答案拯救了人们。你的传奇。
【解决方案2】:

这是不寻常的 stackoverflow,底部的答案比接受的要好。刚刚清理了@xims 解决方案并使其更易读:

function bankersRound(n, d=2) {
    var x = n * Math.pow(10, d);
    var r = Math.round(x);
    var br = Math.abs(x) % 1 === 0.5 ? (r % 2 === 0 ? r : r-1) : r;
    return br / Math.pow(10, d);
}

【讨论】:

  • ...这是比答案更好的不寻常评论。它是相同的代码,只是压缩成 "one-liner"(并重命名):function round2(n,d=2){var n=n*Math.pow(10,d),o=Math.round(n);return(Math.abs(n)%1==.5?o%2==0?o:o-1:o)/Math.pow(10,d)}(降低的易读性是额外的好处)。
【解决方案3】:

接受的答案会四舍五入到给定数量的位置。在此过程中,它调用 toFixed 将数字转换为字符串。由于这很昂贵,我提供以下解决方案。它将以 0.5 结尾的数字四舍五入到最接近的偶数。它不处理四舍五入到任意数量的位置。

function even_p(n){
  return (0===(n%2));
};

function bankers_round(x){
    var r = Math.round(x);
    return (((((x>0)?x:(-x))%1)===0.5)?((even_p(r))?r:(r-1)):r);
};

【讨论】:

  • 您的函数不允许指定要保留的小数位数
  • 是的。那是留给更红的:-)。阅读:我所需要的只是正确舍入为整数。
  • 这是一个很好的解决方案,谢谢!这是使它适用于小数的方法。看来我必须发布一个单独的答案才能显示代码...
【解决方案4】:

这是来自@soegaard 的一个很好的解决方案。 这是一个小改动,使它适用于小数点:

bankers_round(n:number, d:number=0) {
    var x = n * Math.pow(10, d);
    var r = Math.round(x);
    var br = (((((x>0)?x:(-x))%1)===0.5)?(((0===(r%2)))?r:(r-1)):r);
    return br / Math.pow(10, d);
}

在此期间 - 这里有一些测试:

console.log(" 1.5 -> 2 : ", bankers_round(1.5) );
console.log(" 2.5 -> 2 : ", bankers_round(2.5) );
console.log(" 1.535 -> 1.54 : ", bankers_round(1.535, 2) );
console.log(" 1.525 -> 1.52 : ", bankers_round(1.525, 2) );

console.log(" 0.5 -> 0 : ", bankers_round(0.5) );
console.log(" 1.5 -> 2 : ", bankers_round(1.5) );
console.log(" 0.4 -> 0 : ", bankers_round(0.4) );
console.log(" 0.6 -> 1 : ", bankers_round(0.6) );
console.log(" 1.4 -> 1 : ", bankers_round(1.4) );
console.log(" 1.6 -> 2 : ", bankers_round(1.6) );

console.log(" 23.5 -> 24 : ", bankers_round(23.5) );
console.log(" 24.5 -> 24 : ", bankers_round(24.5) );
console.log(" -23.5 -> -24 : ", bankers_round(-23.5) );
console.log(" -24.5 -> -24 : ", bankers_round(-24.5) );

【讨论】:

  • 注意:其中一些约定仅适用于 ES6(例如:默认参数值)。
【解决方案5】:
const isEven = (value: number) => value % 2 === 0;
const isHalf = (value: number) => {
    const epsilon = 1e-8;
    const remainder = Math.abs(value) % 1;

    return remainder > .5 - epsilon && remainder < .5 + epsilon;
};

const roundHalfToEvenShifted = (value: number, factor: number) => {
    const shifted = value * factor;
    const rounded = Math.round(shifted);
    const modifier = value < 0 ? -1 : 1;

    return !isEven(rounded) && isHalf(shifted) ? rounded - modifier : rounded;
};

const roundHalfToEven = (digits: number, unshift: boolean) => {
    const factor = 10 ** digits;

    return unshift
        ? (value: number) => roundHalfToEvenShifted(value, factor) / factor
        : (value: number) => roundHalfToEvenShifted(value, factor);
};

const roundDollarsToCents = roundHalfToEven(2, false);
const roundCurrency = roundHalfToEven(2, true);
  • 如果您不喜欢调用 toFixed() 的开销
  • 希望能够提供任意比例
  • 不想引入浮点错误
  • 希望有可读、可重用的代码

roundHalfToEven 是一个生成固定比例舍入函数的函数。我用美分而不是美元进行货币操作,以避免引入 FPE。存在 unshift 参数是为了避免为这些操作取消移位和再次移位的开销。

【讨论】:

    【解决方案6】:

    严格来说,所有这些实现都应该处理要舍入为负数的情况。

    这是一种极端情况,但禁止它仍然是明智的(或者非常清楚这意味着什么,例如 -2 舍入到最接近的数百)。

    【讨论】:

      【解决方案7】:

      对于希望能够更好地阅读代码的人,这里有一个似乎可行的替代实现。

      function bankersRound(n, decimalPlaces) {
        // Create our multiplier for floating point precision issues.
        const multiplier = Math.pow(10, decimalPlaces);
        // Multiple by decimal places to avoid rounding issues w/ floats
        const num = n * multiplier;
        // Use standard rounding
        const rounded = Math.round(num);
        // Only odd numbers should be rounded
        const shouldUseBankersRound = rounded % 2 !== 0;
        // Subtract one to ensure the rounded number is even
        const bankersRound = shouldUseBankersRound ? rounded - 1 : rounded;
        // Return to original precision
        return bankersRound / multiplier;
      }
      
      console.log(
        bankersRound(1.5255, 2),
        bankersRound(1.53543, 2),
        bankersRound(1.54543, 2),
        bankersRound(1.54543, 3),
        bankersRound(1.53529, 4),
        bankersRound(1.53529, 2),
        bankersRound(4.5, 0),
        bankersRound(5.5, 0),
        bankersRound(0.045, 2),
        bankersRound(0.055, 2)
      );

      【讨论】:

        猜你喜欢
        • 2023-03-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-23
        • 2017-12-26
        相关资源
        最近更新 更多