我一直在 C# 中使用
Math.Round(myNumber, MidpointRounding.ToEven)
进行服务器端舍入,但是,用户需要“实时”知道服务器端操作的结果是什么,这意味着(避免 Ajax 请求)创建一个 JavaScript 方法来复制 C# 使用的 MidpointRounding.ToEven
方法。
MidpointRounding.ToEven 是 Gaussian/banker's rounding,这是 here.
描述的一种非常常见的会计系统舍入方法有人有这方面的经验吗?我在网上找到了示例,但它们没有四舍五入到 given 小数位数...
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 实现。
这是不寻常的 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);
}
这是@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) );
接受的答案确实四舍五入到给定的位置。在此过程中,它调用 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);
};
我对其他答案不满意。他们的代码要么过于冗长或复杂,要么无法正确舍入负数。对于负数,我们必须巧妙地修复 JavaScript 的一个奇怪行为:
JavaScript 的 Math.round 有一个不寻常的属性,它会将中途情况四舍五入到正无穷大,无论它们是正数还是负数。因此,例如 2.5 将四舍五入为 3.0,但 -2.5 将四舍五入为 -2.0。 来源
这是错误的,因此我们必须在应用银行家四舍五入之前对负数进行四舍五入
.5
,相应地。
此外,就像
Math.round
一样,我想四舍五入到下一个整数并强制精度为0。我只希望Math.round
在正数和负数中使用正确和固定的“四舍五入到偶数”方法。它需要像其他编程语言一样四舍五入,例如 PHP (PHP_ROUND_HALF_EVEN
) 或 C# (MidpointRounding.ToEven
)。
/**
* Returns a supplied numeric expression rounded to the nearest integer while rounding halves to even.
*/
function roundMidpointToEven(x) {
const n = x >= 0 ? 1 : -1 // n describes the adjustment on an odd rounding from midpoint
const r = n * Math.round(n * x) // multiplying n will fix negative rounding
return Math.abs(x) % 1 === 0.5 && r % 2 !== 0 ? r - n : r // we adjust by n if we deal with a half on an odd rounded number
}
// testing by rounding cents:
for(let i = -10; i <= 10; i++) {
const val = i + .5
console.log(val + " => " + roundMidpointToEven(val))
}
Math.round
以及我们的自定义 roundMidpointToEven
函数不会关心精度,因为最好用分来计算,以避免任何计算中的浮点问题。
但是,如果你不处理美分,你可以简单地乘以和除以小数占位符数量的适当因子,就像你为
Math.round
做的一样:
const usd = 9.225;
const fact = Math.pow(10, 2) // A precision of 2, so 100 is the factor
console.log(roundMidpointToEven(usd * fact) / fact) // outputs 9.22 instead of 9.23
为了完全验证自定义
roundMidpointToEven
函数,这里是使用 PHP 及其官方 PHP_ROUND_HALF_EVEN
以及 C# 使用 MidpointRounding.ToEven
的相同输出:
for($i = -10; $i <= 10; $i++) {
$val = $i + .5;
echo $val . ' => ' . round($val, 0, PHP_ROUND_HALF_EVEN) . "<br />";
}
for(int i = -10; i <= 10; i++)
{
double val = i + .5;
Console.WriteLine(val + " => " + Math.Round(val, MidpointRounding.ToEven));
}
两个片段都返回与我们自定义的测试调用相同
roundMidpointToEven
:
-9.5 => -10
-8.5 => -8
-7.5 => -8
-6.5 => -6
-5.5 => -6
-4.5 => -4
-3.5 => -4
-2.5 => -2
-1.5 => -2
-0.5 => 0
0.5 => 0
1.5 => 2
2.5 => 2
3.5 => 4
4.5 => 4
5.5 => 6
6.5 => 6
7.5 => 8
8.5 => 8
9.5 => 10
10.5 => 10
成功!
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);
roundHalfToEven 是一个生成固定比例舍入函数的函数。我使用美分而不是美元进行货币操作,以避免引入 FPE。 unshift 参数的存在是为了避免为这些操作再次进行 unshifting 和 shifting 的开销。
严格来说,所有这些实现都应该处理要舍入到负数的情况。
这是一个边缘案例,但禁止它仍然是明智的(或者非常清楚这意味着什么,例如 -2 是四舍五入到最接近的百位)。
这个解决方案比当前的任何答案都稍微优雅一些。它处理四舍五入的负数和正确的小数位负数。
function bankersRound (value, nDec = 2) {
let x = value * Math.pow(10, nDec);
let r = Math.round(x);
return (Math.abs(x) % 1 === .5 ? r - (r % 2) : r) / Math.pow(10, nDec);
}
这个还利用了这样一个事实,即
Math.round
对带小数点 +Infinity
的值四舍五入到 .5
。负数和正数四舍五入到最接近的偶数。
let roundGaussian = num => {
const sign = Math.sign(num);
num = Math.abs(num);
if (Math.floor(num % 2) !== 0) return Math.round(num) * sign;
else return Math.abs(Math.round(-num)) * sign;
}
let tests = [123.5, 234.5, -123.5, -234.5];
for (let n of tests) console.log(roundGaussian(n));
// 124
// 234
// -124
// -234
在我的情况下,我已经有了要四舍五入为整数的数字和小数位数(例如,1.23 存储为
{m: 123, e: 2}
。(这通常是处理货币时的好方法。)将数字四舍五入为那,你可以做
function round({e, m}, places) {
if (e < places) return {e, m};
const frac = m / (2 * 10 ** (e - places));
const rnd = Math.abs(frac % 1);
const offs = Math.sign(frac) * ((rnd > 0.25) + (rnd >= .75));
return {e: places, m: Math.trunc(frac) * 2 + offs};
}
想法是,由于
e
、m
和places
是整数,所以当结果在舍入断点上时,m / (2 * 10 ** (e - places))
将是精确的。
对于希望能够更好地阅读代码的人来说,这里有一个似乎可行的替代实现。
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)
);