java四捨五入中注意的地方

四捨五入是一種近似精確的計算方法,在Java5以前,咱們通常是經過Math.round來得到指定精度的整數或小數的,這種方法使用很是普遍,代碼以下:java

System.out.println("10.5近似值: "+Math.round(10.5));
System.out.println("-10.5近似值: "+Math.round(-10.5));

輸出結果:11,-10 (+0.5 取floor)面試

這是四捨五入的經典案例,也是初級面試官很樂意選擇的考題,絕對值相同的兩個數字,近似值爲何就不一樣了呢?這是由Math.round採用的舍入 規則決定的(採用的是正無窮方向舍入規則),咱們知道四捨五入是由偏差的:其偏差值是舍入的一半。咱們以舍入運用最頻繁的銀行利息計算爲例來闡述此問題。算法

  咱們知道銀行的盈利渠道主要是利息差,從儲戶手裏收攏資金,而後房貸出去,期間的利息差額即是所得到利潤,對一個銀行來講,對付給儲戶的利息計算很是頻繁,人民銀行規定每一個季度末月的20日爲銀行結息日,一年有4次的結息日。code

  場景介紹完畢,咱們回頭來看看四捨五入,小於5的數字被捨去,大於5的數字進位後捨去,因爲單位上的數字都是天然計算出來的,按照利率計算可知,被捨去的數字都分佈在0~9之間,下面以10筆存款利息計算做爲模型,以銀行家的身份來思考這個算法:ip

  四舍:捨棄的數值是:0.000、0.00一、0.00二、0.00三、0.004由於是捨棄的,對於銀行家來講就不須要付款給儲戶了,那每舍一個數字就會賺取相應的金額:0.000、0.00一、0.00二、0.00三、0.004.ci

  五入:進位的數值是:0.00五、0.00六、0.00七、0.00八、0.009,由於是進位,對銀行家來講,每進一位就會多付款給儲戶,也就是虧損了,那虧損部分就是其對應的10進制補數:0.00五、.000四、0.00三、0.00二、0.001.源碼

  由於捨棄和進位的數字是均勻分佈在0~9之間,對於銀行家來講,沒10筆存款的利息因採用四捨五入而得到的盈利是:class

  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元,即每筆利息計算就損失0.0005元,這對一家有5千萬儲戶的銀行家來講(對國內銀行來講,5千萬是個小數字),每一年僅僅由於四捨五入的偏差而損失的金額是:程序

  銀行帳戶數量(5千萬)*4(一年計算四次利息)*0.0005(每筆利息損失的金額)

  5000*10000*0.0005*4=100000.0;即,每一年由於一個算法偏差就損失了10萬元,事實上以上的假設條件都是很是保守 的,實際狀況可能損失的更多。那各位可能要說了,銀行還要放貸呀,放出去這筆計算偏差不就抵消了嗎?不會抵消,銀行的貸款數量是很是有限的其數量級根本無 法和存款相比。

  這個算法偏差是由美國銀行家發現的(那但是私人銀行,錢是本身的,白白損失了可不行),而且對此提出了一個修正算法,叫作銀行家舍入(Banker's Round)的近似算法,其規則以下:

  1. 捨去位的數值小於5時,直接捨去;
  2. 捨去位的數值大於等於6時,進位後捨去;
  3. 當捨去位的數值等於5時,分兩種狀況:5後面還有其它數字(非0),則進位後捨去;若5後面是0(即5是最後一個數字),則根據5前一位數的奇偶性來判斷是否須要進位,奇數進位,偶數捨去。

  以上規則彙總成一句話:四捨六入五考慮,五後非零就進一,五後爲零看奇偶,五前爲偶應捨去,五前爲奇要進一。咱們舉例說明,取2位精度;

  round(10.5551)  =  10.56   round(10.555)  =  10.56   round(10.545)  =  10.56  

  要在Java5以上的版本中使用銀行家的舍入法則很是簡單,直接使用RoundingMode類提供的Round模式便可,示例代碼以下:

public class Bank_round {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		// 存款
        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支持如下七種舍入方式:

  1. ROUND_UP:原理零方向舍入。向遠離0的方向舍入,也就是說,向絕對值最大的方向舍入,只要捨棄位非0即進位。
  2. ROUND_DOWN:趨向0方向舍入。向0方向靠攏,也就是說,向絕對值最小的方向輸入,注意:全部的位都捨棄,不存在進位狀況。
  3. ROUND_CEILING:向正無窮方向舍入。向正最大方向靠攏,若是是正數,舍入行爲相似於ROUND_UP;若是爲負數,則舍入行爲相似於ROUND_DOWN.注意:Math.round方法使用的即爲此模式。
  4. ROUND_FLOOR:向負無窮方向舍入。向負無窮方向靠攏,若是是正數,則舍入行爲相似ROUND_DOWN,若是是負數,舍入行爲相似以ROUND_UP。
  5. HALF_UP:最近數字舍入(5舍),這就是咱們經典的四捨五入。
  6. HALF_DOWN:最近數字舍入(5舍)。在四捨五入中,5是進位的,在HALF_DOWN中倒是捨棄不進位。
  7. HALF_EVEN:銀行家算法

  在普通的項目中舍入模式不會有太多影響,能夠直接使用Math.round方法,但在大量與貨幣數字交互的項目中,必定要選擇好近似的計算模式,儘可能減小因算法不一樣而形成的損失。

具體源碼能夠查看RoundingMode類

注意:根據不一樣的場景,慎重選擇不一樣的舍入模式,以提升項目的精準度,減小算法損失。

ps:另外在計算含小數的運算中(須要精確考慮的)能夠轉換爲整數以後再計算。

參考自:編寫高質量代碼:改善Java程序的151個建議

相關文章
相關標籤/搜索