Gaussian/banker's rounding in JavaScript
我一直在使用C中的
MidpointRounding.toeven是高斯/银行家的舍入,这是一种非常常见的会计系统舍入方法。
有人对此有经验吗?我在网上找到了一些例子,但它们并没有四舍五入到给定的小数位数…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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实现。
被接受的答案会四舍五入到一定数量的位置。在此过程中,它调用ToFixed,将数字转换为字符串。由于这是昂贵的,我提供下面的解决方案。它将以0.5结尾的数字舍入到最接近的偶数。它不处理到任意数量的位置的舍入。
1 2 3 4 5 6 7 8 | 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); }; |
这是@soegaard的一个很好的解决方案。这里有一个小的变化,使它适用于小数点:
1 2 3 4 5 6 | 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); } |
尽管如此,以下是一些测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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) ); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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参数的存在是为了避免那些操作的unshift和shifting的开销。
这是不寻常的堆栈溢出,其中底部的答案比接受的好。刚刚清理了@xims溶液,使其更清晰:
4值得注意的是,所有这些实现都应该处理负数的情况。
这是一个边缘情况,但还是不允许它(或者非常清楚这意味着什么,例如-2是四舍五入到最接近的数百)。