最近接丸め、バンカーズラウンディング 桁の切り上げ切り下げは意外とややこしい

四捨五入が使われることが多い端数の丸め処理ですが、これはよくよく考えてみるとわずかながらも大きくなってしまうんですね。

0,1,2,3,4 切り捨て

5,6,7,8,9 切り上げ

一見対対称にみえます。しかし小数点2位までで考えてみると、

0.01~0.49は切り捨て。つまり0.49分が無視される。

0.50以上の値でも対称にするなら、0.51~0.99にならなければいけないはず。

まん中にあるはずの0.50が余ってしまう。四捨五入ではこの0.50を切り上げてしまうことになるため、0.01分だけ大きくなるケースがあるわけです。

0が切上げ切り下げ対象でないのに対し、0.50は切り上げ切り下げの対象になるために生じます。

最近接丸め、偶数丸め、バンカーズラウンディング

0.50の行き先を偏らせないことでバランスを取る方法として、切り上げ・切り下げの結果が偶数になるように丸める方法があります。0.50の行き先が半々にできるわけです。

この方法は一般に「偶数丸め」「最近接丸め」と呼ばれますが、「JIS丸め」や「ISO丸め」とも言われます。銀行で使われていたために、バンカーズラウンディング(banker’s rounding)とも表記されることがあります。

呼び方が一定しないので、調べたい時には結構めんどくさいです。

PHPとjavascriptでの偶数丸め

PHPにはrounding関数に偶数・奇数丸めのオプションがあるので楽です。

round(数値[,桁数[, モード]])
PHP.net

定数説明
PHP_ROUND_HALF_UPval が小数点第 precision 位の値になるように、 ゼロから離れる方向に丸めます。1.5 は 2 に、そして -1.5 は -2 になります。
PHP_ROUND_HALF_DOWNval が小数点第 precision 位の値になるように、 ゼロに近づく方向に丸めます。1.5 は 1 に、そして -1.5 は -1 になります。
PHP_ROUND_HALF_EVENval が小数点第 precision 位の値になるように、 次の偶数に丸めます。
PHP_ROUND_HALF_ODDval が小数点第 precision 位の値になるように、 次の奇数に丸めます。
[]内はオプション

小数点以下切り上げのceil() 、小数点以下切り捨てのfloor()は使うところがはっきりしているので迷うことはないですが、roundはモードのことを頭に入れておくほうがいいですね。

一方、javascriptは関数ではないようなので、自分で対処するしかなさそうです。

そこで探してきました。これ簡単です。

Gaussian/banker’s rounding in JavaScript

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