您的位置:首页 > 编程语言 > Java开发

提高你的Java代码质量吧:不容忽视的四舍五入细节

2013-07-23 09:37 134 查看
一、分析 

在许多数学计算的场景中,会用到近似取值的计算方法。常用的近似取值有四舍五入。 

但是在某些金融行业或特殊场景中,四舍五入的方式就不太适宜。目前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来指定精度的整数或小数: 

[java]
view plaincopyprint?

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));   
    }   
}   

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));
}
}


由于Math.round采用的舍入规则是正无穷方向舍入。所以输出结果为: 

10.5近似值:11 

-10.5近似值:-10 

 

但是在银行计算利息的场景下,适应四舍五入计算利息就会出现如下情况: 

按照概率计算克制,被舍入的数字均匀的分布在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元,每笔利息计算损失0.0005元。对于一家银行(上亿储户)来说,这个误差造成的损失也不可小视觉。 

 

这个误差是由美国的银行家发现的,所以提供出了一个修正算法,叫做银行家舍入的近似算法,其规则如下: 

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

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

当舍去的数值等于5时,分两种情况: 

5后面还有其它数字(非0),则进位后舍去; 

若5后面是0(即5是最后一个数字),则根据5前一位数的奇偶性来判断是否需要进位,奇数进位,偶数舍去。 

以上规则汇总成一句话:四舍六入考虑五,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一。 

 

在Java5以上的版本中,使用银行家的舍入算法非常简单,直接使用RoundingMode类,提供Round模式即可,示例代码如下: 

[java]
view plaincopyprint

public class Client{   
    public static void main(String[] args){   
        //存款    
        BigDecimal d = new BigDecimal(88888);   
        //月利率,乘3计算季利率 
  
        BigDecimal r = new BigDecimal(0.001875*3);   
        //计算利息    
        BigDecimal i = d.mutiply(r)setScale(2,RoundingMode.HALF_EVEN);   
        System.out.println("季利息是:" + i);   
    }    
} <SPAN style="LINE-HEIGHT: 17px; BACKGROUND-COLOR: transparent; FONT-FAMILY: Calibri, sans-serif; COLOR: windowtext; FONT-SIZE: 11pt"> </SPAN>  

public class Client{
public static void main(String[] args){
//存款
BigDecimal d = new BigDecimal(88888);
//月利率,乘3计算季利率
BigDecimal r = new BigDecimal(0.001875*3);
//计算利息
BigDecimal i = d.mutiply(r)setScale(2,RoundingMode.HALF_EVEN);
System.out.println("季利息是:" + i);
}
}  


三、建议 

根据不同的场景,慎重选择不同的舍入模式,以提高项目的精准度,减少算法损失。在大量与货币数字交互的项目中,一定要选择好近似的计算模式,尽量减少因算法不同而造成的损失。 

本文由www.shihanwater.com转载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: