Math.round()は一方を損なった


一つの小学校の数学の問題:四捨五入.四捨五入は近似的に正確な計算方法であり、Java 5の前に、Math.roundを使用して指定された精度の整数または小数を得るのが一般的である.この方法は非常に広く使用されており、コードは以下の通りである.
public class Client {  
     public static void main(String[] args) {  
          System.out.println("10.5   :" + Math.round(10.5));  
          System.out.println("-10.5   :"+ Math.round(-10.5));  
     }  
}

出力結果:

10.5   :11  
-10.5   :-10

これは四捨五入の経典のケースで、初級面接官が喜んで選んだ試験問題でもあり、絶対値が同じ2つの数字で、近似値はどうして違うのでしょうか.これはMath.roundが採用する丸めルールによって決まります(正無限方向丸めルールを採用しており、後述します).四捨五入には誤差があることを知っています.その誤差値は捨入ビットの半分です.この問題を,最も頻繁に運用される銀行金利計算を例に挙げて述べた.
私たちは銀行の利益ルートが主に利息の差であることを知っていて、預金者の手から資金を集めて、それから貸し出して、その間の利息の差額は得た利益です.ある銀行にとって、預金者への利息の計算は非常に頻繁で、人民銀行は四半期末の20日を銀行の利息日と規定し、1年に4回の利息日がある.
シーンの绍介が终わって、私达は振り返って四舍五入を见て、5より小さい数字は舍てられて、5以上の数字は进位して舍てられて、すべてのビットの上の数字はすべて自然に计算したので、确率の计算によって分かって、舍てられた数字は均一に0から9の间に分布して、以下は10笔の预金の利息の计算を模型として、銀行家としてこのアルゴリズムを考えてみましょう
四舎.切り捨てた数値:0.000、0.001、0.002、0.003、0.004は切り捨てなので、銀行家にとっては預金者に支払う必要はありません.では、数字を捨てるたびに相応の金額を稼ぐことができます:0.000、0.001、0.002、0.003、0.004.
五入.キャリーの数値:0.005、0.006、0.007、0.008、0.009、キャリーなので、銀行家にとって、キャリーごとに預金者に多く支払うことができて、つまり損失で、その損失の部分はその対応する10進数の補数です:0.005、0.004、0.003、0.002、0.001.
切り捨てと進位の数字は0から9の間に均一に分布しているため、銀行家にとって、預金10件当たりの利息は四捨五入で得られる利益は:
0.000 + 0.001 + 0.002 + 0.003 + 0.004 - 0.005 - 0.004 - 0.003 - 0.002 - 0.001 = -0.005

つまり、10件ごとの利息計算で0.005元の損失、すなわち1件当たりの利息計算で0.0005元の損失が発生し、これは5千万人の預金者を持つ銀行にとって(国内の銀行にとって、5千万は小さな数字である)、毎年4つの切り捨ての誤差だけで損失する金額は:
public class Client {  
     public static void main(String[] args) {  
          //      ,5    
          int accountNum =5000*10000;  
          //       ,       20         
          double cost = 0.0005 * accountNum * 4 ;  
          System.out.println("         :" + cost);  
     }  
}

出力された結果は、「銀行の年間損失額:100000.0」です.すなわち,毎年1つのアルゴリズム誤差により10万元の損失が発生し,事実上以上の仮定条件は非常に保守的であり,実際の状況はより多くの損失をもたらす可能性がある.では、皆さんは言うかもしれませんが、銀行はローンをしなければなりません.この計算誤差を出したら相殺してしまうのではないでしょうか.相殺はできません.銀行のローンの数は非常に限られていて、その数級は預金と比べることができません.
このアルゴリズムの誤差はアメリカの銀行家によって発見され(それは個人銀行で、お金は自分のもので、むだに損失してはいけない)、これに対して銀行家捨入(Banker's Round)という近似アルゴリズムを修正し、その規則は以下の通りである.
切り捨てビットの数値が5未満の場合、直接切り捨てます.
切り捨てビットの数値が6以上の場合、キャリー後に切り捨てられます.
切り捨てビットの数値が5に等しい場合、5の後ろに他の数字(0以外)がある場合、キャリー後に切り捨てられます.5の後ろが0(すなわち5が最後の数字)であれば、5の前の桁数のパリティからキャリー、奇数キャリー、偶数切り捨てが必要か否かを判断する.
以上の规则は1つの言叶にまとめられています:四舍六入五考虑して、5后はゼロではなく1を进んで、5后はゼロでパリティを见て、5前は私のために舍ててて、5前は奇で1を进めます.例として、2ビットの精度をとります.
round(10.5551) = 10.56  
round(10.555)  = 10.56  
round(10.545)  = 10.54

Java 5以上のバージョンで銀行家の丸めアルゴリズムを使用するには、RoundingModeクラスが提供するRoundモードを直接使用すると簡単です.サンプルコードは次のとおりです.
public class Client {  
     public static void main(String[] args) {  
          //    
          BigDecimal d = new BigDecimal(888888);  
          //   , 3       
          BigDecimal r = new BigDecimal(0.001875*3);  
          //      
          BigDecimal i = d.multiply(r).setScale(2,RoundingMode.HALF_EVEN);  
          System.out.println("    :"+i);  
     }  
}

上記の例では、BigDecimalクラスを使用し、setScaleメソッドを使用して精度を設定し、RoundingMode.HALF_を渡します.EVENパラメータは銀行家の切り込み法則を用いて近似計算を行うことを示し,BigDecimalとRoundingModeは絶妙であり,どの切り込みモードを用いてRoundingMode設定を使用すればよいかを示す.現在Javaでは、次の7つの切り込み方法がサポートされています.
ROUND_UP:ゼロ方向から切り捨てます.
0から離れた方向に丸め、すなわち絶対値が最も大きい方向に丸め、切り捨てビットが0でない限り進位する.
ROUND_DOWN:ゼロ方向に丸められます.
0方向に寄せて、つまり、絶対値が最も小さい方向に入力します.注意:すべてのビットは捨てられ、キャリーは存在しません.
ROUND_CEILING:正無限方向に丸めます.
正の最大方向に寄せ、正数の場合は丸め動作はROUND_に似ています.UP;負数の場合、丸め動作はROUND_と似ています.DOWN.注意:Math.roundメソッドでは、このモードが使用されます.
ROUND_FLOOR:負の無限方向に丸められます.
負の無限方向に寄せ、正数の場合は丸め動作はROUND_に似ています.DOWN;負数の場合、丸め動作はROUND_に似ています.UP.
HALF_UP:最近の数値丸め(5進).
これが私たちの最も古典的な四捨五入モードです.
HALF_DOWN:最近の数値丸め(5丸め).
四捨五入では5がキャリーでHALF_DOWNでは捨てられて進位しない.
HALF_EVEN:銀行家アルゴリズム.
通常のプロジェクトでは丸めモードはあまり影響を及ぼさず、Math.roundメソッドを直接使用することができますが、通貨デジタルとインタラクティブなプロジェクトでは、アルゴリズムの違いによる損失を最小限に抑えるために、近似的な計算モードを選択する必要があります.
注意異なるシーンに基づいて、プロジェクトの精度を高め、アルゴリズムの損失を減らすために、異なる切り込みモードを慎重に選択します.