我一直在 C# 中使用 Math.Round(myNumber, MidpointRounding.ToEven)
进行服务器端舍入,但是,用户需要“实时”知道服务器端操作的结果是什么,这意味着(避免 Ajax 请求)创建一个用于复制 C# 使用的 MidpointRounding.ToEven
方法的 JavaScript 方法。
MidpointRounding.ToEven 是 Gaussian/banker's rounding,这是一种非常常见的舍入方法,用于描述的会计系统 here。
有人对这个有经验么?我在网上找到了例子,但它们没有四舍五入到给定的小数位数......
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);
}
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)}
(降低易读性是一个好处)。
这是@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);
};
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 和再次 shift 的开销。
我对其他答案不满意。它们的代码要么过于冗长或复杂,要么无法正确舍入负数。对于负数,我们必须巧妙地修复 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
) 等其他编程语言一样进行舍入。
/** * 返回提供的数字表达式,四舍五入到最接近的整数,同时四舍五入为偶数。 */ 函数 roundMidpointToEven(x) { const n = x >= 0 ? 1 : -1 // n 描述了对中点奇数舍入的调整 const r = n * Math.round(n * x) // 乘以 n 将修复负舍入 return Math.abs(x) % 1 === 0.5 && r % 2 !== 0 ? r - n : r // 如果我们处理奇数舍入的一半,我们调整 n } // 通过舍入美分进行测试: 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_ROUND_HALF_EVEN
的 PHP 以及使用 MidpointRounding.ToEven
的 C# 的相同输出:
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
成功!
严格来说,所有这些实现都应该处理要舍入为负数的情况。
这是一个边缘情况,但仍然明智的做法是禁止它(或者非常清楚这意味着什么,例如 -2 舍入到最接近的数百)。
对于希望能够更好地阅读代码的人来说,这里有一个似乎可行的替代实现。
function bankersRound(n, decimalPlaces) { // 为浮点精度问题创建乘数。常量乘数 = Math.pow(10, decimalPlaces); // 小数位乘以避免四舍五入问题 w/ floats const num = n * multiplier; // 使用标准舍入 const rounded = Math.round(num); // 只有奇数应该四舍五入 const shouldUseBankersRound = rounded % 2 !== 0; // 减一以确保四舍五入为偶数 const bankersRound = shouldUseBankersRound ?四舍五入 - 1:四舍五入; // 返回原始精度 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) );
bankersRounding(9.55, 2)
的行为不太正确,这不应导致 9.54
const isMedian = Math.abs(num) % 1 === 0.5
Math.pow(10, d)
表达式上的溢出/下溢(至少)。出现此错误并且当 decimalPlaces 为正时,返回 num,否则重新引发该异常。其次,为了补偿 IEEE 二进制转换错误,我会将f == 0.5
更改为f >= 0.499999999 && f <= 0.500000001
- 这取决于您选择的“epsilon”(不确定 .toFixed(epsilon) 是否足够)。那你就金了!