盆暗の学習記録

データサイエンスを中心として,日々学んだことの備忘録としていく予定です。初心者であり独学なので内容には誤りが含まれる可能性が大いにあります。

Rのround()は四捨五入をするわけではない

round関数の意味を誤解したまま使っていて面倒なことになったのでメモ。

round()は四捨五入をするわけではない

偶数への最近接丸め

round()helpを確認してみると,Detailsの部分に

Note that for rounding off a 5, the IEC 60559 standard (see also ‘IEEE 754’) is expected to be used, ‘go to the even digit’.

(5を丸めるときはIEC 60559規格が使われ,”偶数へ丸める”ことになるので注意してください)

と書いてあります。どういうことかというと,偶数への最近接丸め(Round to nearest, ties to even)(偶数丸め)と呼ばれる,丸め誤差を最小化するための方式で,

  1. 端数が0.5より小さいなら切り捨て
  2. 端数が0.5より大きいならは切り上げ
  3. 端数がちょうど0.5なら切り捨てと切り上げのうち結果が偶数となる方へ丸める。

という処理を行うようです(1.3.3偶数への丸め(round to even) - Wikipedia)。

言い換えると

最近接丸めの方式はISO 31-0やJIS Z 8401でも定められており,丸め方についての話はJIS規格の資料が分かりやすかったです。

日本工業標準調査会で「Z8401」と検索することでアクセスできます)

この資料では,

  • 「丸める」とは,与えられた数値を,ある一定の「丸めの幅」の整数倍がつくる系列から選んだ数値に置き換えること

であると整理した上で

  1. 与えられた数値に最も近い整数倍が1つしかない場合には,それを丸めた数値とする
  2. 与えられた数値に等しく近い,2つの隣り合う整数倍がある場合(端数が0.5の場合)には,偶数倍のほうを丸めた数値とする

という処理であると説明されています。

(また,2回以上繰り返して丸めると誤差の原因となるため,目的の桁の手前から1回だけ丸める(常に1段階で行う)という規則も書かれています。)

端数が5の場合,どっちの整数倍にも等しく近いから,そこでどうするかという部分で四捨五入とは異なるようです

f:id:nigimitama:20181130131008p:plain

round()は厳密に偶数丸めをするわけではない

偶数丸めのアルゴリズムから予想される結果と数値計算の結果との誤差

ニューヨーク大学のIEEE754についての講義資料に書かれていたIEEEの丸めの例が,以下のようなものでした

もとの数値 丸めた数値 理由
7.8949999 7.89 中間の値よりも小さいため
7.8950001 7.90 中間の値よりも大きいため
7.8950000 7.90 中間の値 - 最後の桁が偶数になるよう切り上げ
7.8850000 7.88 中間の値 - 最後の桁が偶数になるよう切り下げ

とくに最後の2つはちょうど中間にくる値を最後の桁が偶数になるようにしていて,上記のJISの説明とも整合します。

しかし,これをRで行うと

> x = c(7.8949999,7.8950001,7.8950000,7.8850000)
> df = data.frame(x, rounded = round(x,2))
> print(df, digits = 10)
          x rounded
1 7.8949999    7.89
2 7.8950001    7.90
3 7.8950000    7.89
4 7.8850000    7.88

3番目の結果が異なります。

例えば1.05や1.15を丸めた場合も,同様のことが起こります

> round(0.05, 1) # 偶数丸めになる
[1] 0
> round(0.15, 1) # 偶数丸めになる
[1] 0.2
> round(1.05, 1) # 偶数丸めにならない
[1] 1.1
> round(1.15, 1) # 偶数丸めにならない
[1] 1.1
> round(1.25, 1) # 偶数丸めになる
[1] 1.2
> round(1.35, 1) # 偶数丸めになる
[1] 1.4

representation errorに注意する必要がある

round()helpではこの誤差についても軽く触れられていて,representation errorに依るのだとか。

this is dependent on OS services and on representation error (since e.g.0.15 is not represented exactly, the rounding rule applies to the represented number and not to the printed number, and so round(0.15, 1) could be either 0.1 or 0.2).

(これはOSとrepresentation errorに依存します(例えば0.15は正確に表現されないため,丸めルールは表示される数字ではなくrepresented numberに適用されます。そのためround(0.15, 1)0.10.2のいずれかになります))

このあたりになってくると正直よくわからないんですが,

  • コンピュータは2進数で数値を処理しているため計算の過程で誤差が生じる
  • Rの場合,表示されている数字が「ぴったりその数字」ではないことがある(誤差の部分は表示を省略している)

ということが関係しているのだと思います。

後者は例えば,1.05を演算したあとの0.05がそのまま入力した0.05と一致しない,といった現象から察することができます。

> (1.05 - 1)
[1] 0.05
> (1.05 - 1) == 0.05
[1] FALSE
> (1.05 - 1) - 0.05
[1] 4.163336e-17

表示上はぴったり0.05であるものの,内部では 4.163336 \cdot 10^{-17}= 0.00000000000000004163336の誤差が生じているようです。

round()関数の内部を覗いたところ,(x - xの整数部)*丸め幅という計算をしてこれを丸める処理を行っている箇所がありました。(x - xの整数部)の計算,あるいは* 丸め幅の計算で「端数がぴったり5」にならなくなる値は,「2つの整数倍のちょうど中間の値」ではなくどちらかの整数倍に(誤差の分だけ)偏るため,丸め方が想定通りのものではなくなるようです。

参考