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

経済・統計

利息や消費税の計算などで日常でも使われる小数点以下の切り上げ切り捨ては一般に端数処理、丸め処理などと呼ばれています。

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

 

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の行き先を偏らせないことでバランスを取る方法として、切り上げ・切り下げの結果が偶数になるように丸める方法があります。

2.50 なら、切り捨てて2に

3.50 なら、切り上げて4に

こうすれば切り捨てと切り上げで 0.50 の行き先を半々にできるわけです。

この方法は一般に「偶数丸め」「最近接丸め」と呼ばれますが、「JIS丸め」や「ISO丸め」とも言われます。

「偶数丸め」は銀行で使われていたために、バンカーズラウンディング(banker’s rounding)とも表記されることがあります。

銀行は昔から几帳面だったんですねw

閑話休題。厳密に端数処理ができるバンカーズラウンドですが、呼び方が一定しないので、調べたい時や聞いたりするときに戸惑うことがままあります。

どうやらこの手の丸め処理はめんどくさいからか、使われなくなる方向のようです。数学の先生なら知っているのに、学校では教わらないのもそのせいかもしれませんね。

 

そしてここからはプログラムの話。

PHPとjavascriptでの偶数丸め

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

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

定数 説明
PHP_ROUND_HALF_UP val が小数点第 precision 位の値になるように、 ゼロから離れる方向に丸めます。1.5 は 2 に、そして -1.5 は -2 になります。
PHP_ROUND_HALF_DOWN val が小数点第 precision 位の値になるように、 ゼロに近づく方向に丸めます。1.5 は 1 に、そして -1.5 は -1 になります。
PHP_ROUND_HALF_EVEN val が小数点第 precision 位の値になるように、 次の偶数に丸めます。
PHP_ROUND_HALF_ODD val が小数点第 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

経済・統計