なんじゃくにっき

プログラミングの話題中心。

数値の丸めの挙動は処理系によって違う

 Round関数を使った丸めの挙動は処理系によって違う。
0.5を丸めると、四捨五入されていつも1になりそうだが、実はそうとも限らない。
丸め方にはいくつかある。
 
●四捨五入(算術丸め)
 目的の桁の次の桁の数値が4以下なら切捨て、5以上なら切上げ。
 一番よくあるパターン。
 少なくとも日本では小学校で習うし一番身近な気がする。
 
 実装処理系: java.lang.math(Decimal型はフォーマット指定可能, J#は銀行型), Oracle, PostgreSQL, SQL Server, Excel
  
●銀行丸め(JIS丸め)
 JIS Z8401でも定められている丸め方。
 意外にこの丸め方の処理系は多い。
 丸める際に一番近い数が2つあるとき、偶数の方に丸める。
 四捨五入と違って統計的な誤差が偏らない。

 例)0.5を整数に丸めるとき、一番近い数は0と1だが偶数である0の方に丸める。
   1.5を整数に丸めるときは2に丸められる。
 
 実装処理系: C, Delphi, C#, VBA, MySQL
 
 
●ランダム丸め
 丸める際に一番近い数が2つあるとき、どちらを選ぶかは毎回ランダム。
  
●交互丸め
 丸める際に一番近い数が2つあるとき、丸める方向を交互に変える。
 
  
 四捨五入だけでなく結構銀行型があるので移植の際には要注意。
言語やDBのバージョンによって違うかもしれないので鵜呑み厳禁。
ランダム丸めと交互丸めを実際に使っている処理系は知らない…
Excelは四捨五入なのにVBAが銀行型だったり、
Oracleが四捨五入なのにMySQLが銀行型だったりややこしい。
 
 対応する処理系が自分の丸めたい方法を実装していない場合は自分で関数を定義することになる。
四捨五入を定義する際に Trunc(Number + 0.9) とかは割とよくある話。
現在では殆どの処理系が負数の切捨ては0の方向へ切り捨てるみたい。
昔のC言語実装依存だったけども。
 
Oracleのストアドで銀行丸めを書いてみるとこんな感じ?

CREATE OR REPLACE FUNCTION BROUND(A IN NUMBER, B IN NUMBER DEFAULT 0)
RETURN NUMBER IS
BEGIN
IF MOD(A * POWER(10, B), 2) = 0.5 THEN
RETURN TRUNC(A, B);
ELSE
RETURN TRUNC(A + SIGN(A) * 5 * POWER(10, -(B + 1)), B);
END IF;
END;