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 >= 0.499999999 && f <= 0.500000001
的东西 - 取决于您选择的“epsilon”(不确定 .toFixed(epsilon) 是否足够)。然后你就完成啦! - Marius这是一个不同寻常的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)}
**(降低可读性是一个额外的奖励)。 - ashleedawgtoFixed()
方法,并且总体上使用更少的操作。 - undefinedtoFixed()
单独使用似乎是有效的,但是当你想要将其四舍五入到没有小数时,它返回的是一个字符串。内置的Intl.NumberFormat有一个roundingMode
选项,你可以将其设置为halfEven
,这个选项可以工作,但是它也返回一个字符串,并且带有逗号格式化。 - undefined这是@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。我只想使用正确且固定的“四舍六入五成双”方法来进行正负舍入。它需要和其他编程语言如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 是一个生成固定精度舍入函数的函数。我在货币操作中使用美分而不是美元,以避免引入 FPEs。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);
}
{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))
将是精确的。
这个也利用了Math.round
在带有小数.5
的值上向+Infinity
四舍五入的事实。负数和正数都会四舍五入到最接近的偶数。
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
Intl.NumberFormat
的所有人,@Gen1-1在这个评论中提到的是正确的答案:roundingMode: 'halfEven'
(MDN)。 - undefined