Math.round()亏了一方

    一个小学数学问题:四舍五入。四舍五入是一种近似精确的计算方法,在Java 5以前,咱们通常是经过使用Math.round来得到指定精度的整数或小数的,这种方法使用很是普遍,代码以下: java

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

这是四舍五入的经典案例,也是初级面试官很乐意选择的考题,绝对值相同的两个数字,近似值为何就不一样了呢?这是由Math.round采用的舍入规则所决定的(采用的是正无穷方向舍入规则,后面会讲解)。咱们知道四舍五入是有偏差的:其偏差值是舍入位的一半。咱们以舍入运用最频繁的银行利息计算为例来阐述该问题。 面试

咱们知道银行的盈利渠道主要是利息差,从储户手里收拢资金,而后放贷出去,其间的利息差额即是所得到的利润。对一个银行来讲,对付给储户的利息的计算很是频繁,人民银行规定每一个季度末月的20日为银行结息日,一年有4次的结息日。 算法

场景介绍完毕,咱们回过头来看四舍五入,小于5的数字被舍去,大于等于5的数字进位后舍去,因为全部位上的数字都是天然计算出来的,按照几率计算可知,被舍入的数字均匀分布在0到9之间,下面以10笔存款利息计算做为模型,以银行家的身份来思考这个算法: spa

四舍。舍弃的数值:0.000、0.00一、0.00二、0.00三、0.004,由于是舍弃的,对银行家来讲,就不用付款给储户了,那每舍弃一个数字就会赚取相应的金额:0.000、0.00一、0.00二、0.00三、0.004。 code

五入。进位的数值:0.00五、0.00六、0.00七、0.00八、0.009,由于是进位,对银行家来讲,每进一位就会多付款给储户,也就是亏损了,那亏损部分就是其对应的10进制补数:0.00五、0.00四、0.00三、0.00二、0.001。 ip

由于舍弃和进位的数字是在0到9之间均匀分布的,因此对于银行家来讲,每10笔存款的利息因采用四舍五入而得到的盈利是: ci

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千万是个很小的数字),每一年仅仅由于四舍五入的偏差而损失的金额是: 数学

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”。即,每一年由于一个算法偏差就损失了10万元,事实上以上的假设条件都是很是保守的,实际状况可能损失得更多。那各位可能要说了,银行还要放贷呀,放出去这笔计算偏差不就抵消掉了吗?不会抵销,银行的贷款数量是很是有限的,其数量级根本没有办法和存款相比。 class

这个算法偏差是由美国银行家发现的(那但是私人银行,钱是本身的,白白损失了可不行),而且对此提出了一个修正算法,叫作银行家舍入(Banker's Round)的近似算法,其规则以下: 方法

舍去位的数值小于5时,直接舍去;

舍去位的数值大于等于6时,进位后舍去;

当舍去位的数值等于5时,分两种状况:5后面还有其余数字(非0),则进位后舍去;若5后面是0(即5是最后一个数字),则根据5前一位数的奇偶性来判断是否须要进位,奇数进位,偶数舍去。

以上规则汇总成一句话:四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一。咱们举例说明,取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支持如下七种舍入方式:

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方法,但在大量与货币数字交互的项目中,必定要选择好近似的计算模式,尽可能减小因算法不一样而形成的损失。

注意 根据不一样的场景,慎重选择不一样的舍入模式,以提升项目的精准度,减小算法损失。

相关文章
相关标签/搜索