JavaScript 中的高斯/银maven舍入

IT技术 javascript rounding
2021-01-21 08:29:00

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

MidpointRounding.ToEven 是高斯/银maven的舍入这里描述的会计系统的一种非常常见的舍入方法

有人对这个有经验么?我在网上找到了一些例子,但它们没有四舍五入到给定的小数位数......

6个回答
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实现。

@Marius:好点。当我写这篇文章时,我对 JS 数字的了解是粗略的,现在也好不到哪里去,所以我会仔细阅读然后更新它。
2021-03-17 08:29:00
实际上 evenRound(0.545,2) 应该四舍五入到 0.54,而不是 0.55,因此函数正确返回它。如果左边的数字是偶数,则它的行为与向下舍入一样,在本例中为 4。
2021-03-21 08:29:00
差不多十年过去了,你仍然用这个答案来拯救人们,蒂姆。你传奇。
2021-03-22 08:29:00
我想再建议两个补充。首先,我会检查Math.pow(10, d)表达式上的上溢/下溢(至少)。在此错误上,并且当 decimalPlaces 为正数时,返回 num,否则重新引发该异常。其次,为了补偿 IEEE 二进制转换错误,我会更改f == 0.5为类似f >= 0.499999999 && f <= 0.500000001- 取决于您选择的“epsilon”(不确定 .toFixed(epsilon) 是否足够)。那你就是金子!
2021-03-30 08:29:00
@TimDown 我发现题为“每个计算机科学家应该了解浮点运算的知识”的文章非常宝贵。
2021-04-06 08:29:00

这是不寻常的 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);
}
...这是比答案更好的不寻常评论。这是相同的代码,只是压缩成一个“单行”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)} 并重命名):(降低易读性是一个奖励)。
2021-03-17 08:29:00

接受的答案会舍入到给定数量的位置。在此过程中,它调用 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);
};
这是一个很好的解决方案,谢谢!以下是如何使其适用于小数。看起来我必须发布一个单独的答案来显示代码......
2021-03-25 08:29:00
您的函数不允许指定要保留的小数位数
2021-04-11 08:29:00
真的。那是留给更红的:-)。阅读:我所需要的只是正确四舍五入为整数。
2021-04-14 08:29:00

这是@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(例如:默认参数值)。
2021-04-06 08:29:00
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 参数的存在是为了避免这些操作的取消移位和再次移位的开销。