はじめに
今回は、モブで開発中にチームメンバーがポロッと言った
「Rubyのroundメソッドのデフォルトって偶数丸めじゃなくて四捨五入だよね?」
という一言をきっかけに偶数丸めについて調べたことを書き留めておこうと思います。
目次
- 偶数丸めについてのざっくり説明
- 偶数丸めを使ってみた
- 偶数丸めで行っている処理
- 偶数丸めはいつ使う?
1. 偶数丸めについてのざっくり説明
偶数丸めとは、端数処理の一種で、処理対象がちょうど半分の値の場合、もっとも近い偶数に丸めるという処理のことを指します。
端数処理で一般的な、四捨五入では1.5
を小数第一位で丸めると2
になり、2.5
を小数第一位で丸めると3
になりますが、偶数丸めでは、1.5
を小数第一位で丸めると2
になり、2.5
を小数第一位で丸めると2
になります。
例えば、
1.5 + 2.5 + 1.5 + 2.5 + 1.5 + 2.5 + 1.5 = 13.5
ですが、
各値を四捨五入して足し算を行うと
2 + 3 + 2 + 3 + 2 + 3 + 2 = 17
になります。
一方、各値を偶数丸めして足し算を行うと
2 + 2 + 2 + 2 + 2 + 2 + 2 = 14
になります。
このように、端数処理を行った結果を更に集計する場合、四捨五入よりも偶数丸めを行った方が、より元の数値を集計した値に近づけることが出来ます。
2. 偶数丸めを使ってみた
先程述べた特徴は、数値が多いほど顕著に現れます。
それを示すために、ランダムな数字100万個の合計値を以下の3つの方法で算出し、結果を比較したいと思います。
・端数処理をせずに合計
・小数第一位を四捨五入して合計
・小数第一位を偶数丸めして合計
なお、今回は私が普段使っているRubyのroundメソッドを使います。
Rubyではroundメソッドのオプションを使って簡単に偶数丸めをすることが出来ます。その他のオプションはこちら。
# 四捨五入
2.5.round(1) # => 3
2.5.round(half: :even) # => 2
ではやってみます!
# 小数第一位までの数字をランダムに100万個作成し配列に詰める
array = []
1000000.times do
random_number = rand(1.0..10.0).floor(1)
array.push(random_number)
end
# そのまま合計する
pp array.sum
# 小数第一位を四捨五入したものを合計する
round = array.map{|i|i.round(0)}
pp round.sum
# 小数第一位を偶数丸めしたものを合計する
round_even = array.map{|i|i.round(half: :even)}
pp round_even.sum
実行結果
# 元の値をそのまま合計
5449937.5
# 四捨五入
# 端数処理をしない場合との差:50,653.5
5500591
# 偶数丸め
# 端数処理をしない場合との差:5,862.5
5455800
この結果から、偶数丸めをした結果を集計するほうが、四捨五入をした結果を集計するより、誤差が小さいことが分かったと思います。
しかし、偶数丸めを使用する場合には少し注意点があります。以下を御覧ください。
array = []
1000000.times do
random_number = rand(1.0..10.0)
array.push(random_number)
end
# そのまま合計する
pp array.sum
# 小数第一位を四捨五入したものを合計する
round = array.map{|i|i.round(0)}
pp round.sum
# 小数第一位を偶数丸めしたものを合計する
round_even = array.map{|i|i.round(half: :even)}
pp round_even.sum
結果
# 元の値をそのまま合計
5504232.324396809
# 四捨五入
5504080
# 偶数丸め
5504080
...あ....れ?結果同じやん?
そう、偶数丸めはそう単純ではなかったのです。
3. 偶数丸めで行っている処理
偶数丸めで小数n桁に丸める場合、処理は以下のルールに沿って行われます。
- 小数n+1桁が5でない場合 => 四捨五入と同様に端数処理を行う
- 小数nが5である場合
2.1 小数n+2桁以下が「0」に相当しない場合 => 繰り上げ処理を行う
2.2 小数n+2桁以下が「0」に相当する場合または不明の場合
2.2.1 小数n+1桁が偶数 => 切り捨て処理を行う
2.2.2 小数n+1桁が奇数 => 繰り上げ処理を行う
example_2
では、rand(1.0..10.0)
によってランダムに生成された小数15桁の数値において、小数代2位以下がピッタリ0になる確率はかなり低かったため、偶数丸めの良さが全く出なかったということです。(example_1
ではfloorメソッドを使って小数第二位以下を切り捨てています。)
4. 偶数丸めはいつ使う?
偶数丸めは、銀行丸めとも呼ばれ、誤差の累積を嫌った銀行員が好んで使っていたそうです。
しかし、最近では、桁数の多い計算も簡単にできるようになってきたため、端数処理は数字を最終的に整形する時にのみ使用するイメージがあります。
このことから偶数丸めが効力を十分発揮する場面は少なくなっていると思います。
一度整形したデータが、更に集計される場合、かつ、その規模がかなり大きい場合は、偶数丸めを用いる方が有効であるため、偶数丸めを使える場面に出くわしたときには使ってみたいです。