BigDecimal商业计算详解

1、浮点数精确计算
   胜利油田三流合一项目中一直存在一个问题,就是每次报表统计的物资金额和实际的金额要差那么几分钱,和实际金额不一致,让客户觉得总是不那么舒服,原因是因为我们使用java的浮点类型double来定义物资金额,并且在报表统计中我们经常要进行一些运算,但Java中浮点数(double、float)的计算是非精确计算,请看下面一个例子:

       System.out.println(0.05 + 0.01);
    System.out.println(1.0 - 0.42);
    System.out.println(4.015 * 100);
    System.out.println(123.3 / 100);
你的期望输出是什么?可实际的输出确实这样的:
       0.060000000000000005
       0.5800000000000001
       401.49999999999994
       1.2329999999999999
   这个问题就非常严重了,如果你有123.3元要购买商品,而计算机却认为你只有123.29999999999999元,钱不够,计算机拒绝交易。

2、四舍五入
    是否可以四舍五入呢?当然可以,习惯上我们本能就会这样考虑,但四舍五入意味着误差,商业运算中可能意味着错误,同时Java中也没有提供保留指定位数的四舍五入方法,只提供了一个Math.round(double d)和Math.round(float f)的方法,分别返回长整型和整型值。round方法不能设置保留几位小数,我们只能象这样(保留两位):
public double round(double value){ 
	return Math.round( value * 100 ) / 100.0; 
}


但非常不幸的是,上面的代码并不能正常工作,给这个方法传入4.015它将返回4.01而不是4.02,如我们在上面看到的

4.015 * 100 = 401.49999999999994

因此如果我们要做到精确的四舍五入,这种方法不能满足我们的要求。

还有一种方式是使用java.text.DecimalFormat,但也存在问题,format采用的舍入模式是ROUND_HALF_DOWN(舍入模式在下面有介绍),比如说4.025保留两位小数会是4.02,因为.025距离” nearest neighbor”(.02和.03)长度是相等,向下舍入就是.02,如果是4.0251那么保留两位小数就是4.03。

System.out.println(new java.text.DecimalFormat("0.00").format(4.025)); 
System.out.println(new java.text.DecimalFormat("0.00").format(4.0251)); 

输出是
4.02
4.03

3、浮点数输出(科学记数法)
Java浮点型数值在大于9999999.0就自动转化为科学记数法来表示,我们看下面的例子:
    System.out.println(999999999.04);
    System.out.println(99999999.04);
    System.out.println(10000000.01);
    System.out.println(9999999.04);
输出的结果如下:

    9.9999999904E8
    9.999999904E7
    1.000000001E7
    9999999.04
   但有时我们可能不需要科学记数法的表示方法,需要转换为字符串,还不能直接用toString()等方法转换,很烦琐。
BigDecimal介绍(详看附件)

8种舍入模式(Rounding mode):

在银行、帐户、计费等商业领域,BigDecimal提供了精确的数值计算。其中8种舍入方式值得掌握。

1、ROUND_UP
舍入远离零的舍入模式。
在丢弃非零部分之前始终增加数字(始终对非零舍弃部分前面的数字加1)。
注意,此舍入模式始终不会减少计算值的大小。
2、ROUND_DOWN
接近零的舍入模式。
在丢弃某部分之前始终不增加数字(从不对舍弃部分前面的数字加1,即截短)。
注意,此舍入模式始终不会增加计算值的大小。
3、ROUND_CEILING
接近正无穷大的舍入模式。
如果 BigDecimal 为正,则舍入行为与 ROUND_UP 相同;
如果为负,则舍入行为与 ROUND_DOWN 相同。
注意,此舍入模式始终不会减少计算值。
4、ROUND_FLOOR
接近负无穷大的舍入模式。
如果 BigDecimal 为正,则舍入行为与 ROUND_DOWN 相同;
如果为负,则舍入行为与 ROUND_UP 相同。
注意,此舍入模式始终不会增加计算值。
5、ROUND_HALF_UP
向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。
如果舍弃部分 >= 0.5,则舍入行为与 ROUND_UP 相同;否则舍入行为与 ROUND_DOWN 相同。
注意,这是我们大多数人在小学时就学过的舍入模式(四舍五入)。
6、ROUND_HALF_DOWN
向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为上舍入的舍入模式。
如果舍弃部分 > 0.5,则舍入行为与 ROUND_UP 相同;否则舍入行为与 ROUND_DOWN 相同(五舍六入)。
7、ROUND_HALF_EVEN
向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。
如果舍弃部分左边的数字为奇数,则舍入行为与 ROUND_HALF_UP 相同;
如果为偶数,则舍入行为与 ROUND_HALF_DOWN 相同。
注意,在重复进行一系列计算时,此舍入模式可以将累加错误减到最小。
此舍入模式也称为“银行家舍入法”,主要在美国使用。四舍六入,五分两种情况。
如果前一位为奇数,则入位,否则舍去。
以下例子为保留小数点1位,那么这种舍入方式下的结果。
1.15>1.2 1.25>1.2
8、ROUND_UNNECESSARY
断言请求的操作具有精确的结果,因此不需要舍入。
如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException。

工具类提供:
这里我们提供了一个工具类,定义浮点数的加、减、乘、除和四舍五入等运算方法。以供参考。

package com.test;

import java.math.BigDecimal;

/**
 * <li>由于Java的简单类型不能够精确的对浮点数进行运算,这个工具类提供精 确的浮点数运算,包括加减乘除和四舍五入。</li>
 * <li>推荐使用 String 参数类型,这样计算结果是最精确的</li>
 */
public class Arith {

	// 默认除法运算精度
	private static final int DEF_DIV_SCALE = 10;

	// 这个类不能实例化
	private Arith() {
	}

	/**
	 * 提供精确的加法运算。
	 * @param v1 被加数
	 * @param v2 加数
	 * @return 两个参数的和
	 */
	public static double add(double v1, double v2) {
		BigDecimal b1 = new BigDecimal(Double.toString(v1));
		BigDecimal b2 = new BigDecimal(Double.toString(v2));
		return b1.add(b2).doubleValue();
	}
	
	/**
	 * 提供精确的加法运算。
	 * @param v1 被加数
	 * @param v2 加数
	 * @return 两个参数的和
	 */
	public static double add(String v1, String v2) {
		BigDecimal b1 = new BigDecimal(v1);
		BigDecimal b2 = new BigDecimal(v2);
		return b1.add(b2).doubleValue();
	}

	/**
	 * 提供精确的减法运算。
	 * @param v1 被减数
	 * @param v2 减数
	 * @return 两个参数的差
	 */
	public static double sub(double v1, double v2) {
		BigDecimal b1 = new BigDecimal(Double.toString(v1));
		BigDecimal b2 = new BigDecimal(Double.toString(v2));
		return b1.subtract(b2).doubleValue();
	}
	
	/**
	 * 提供精确的减法运算。
	 * @param v1 被减数
	 * @param v2 减数
	 * @return 两个参数的差
	 */
	public static double sub(String v1, String v2) {
		BigDecimal b1 = new BigDecimal(v1);
		BigDecimal b2 = new BigDecimal(v2);
		return b1.subtract(b2).doubleValue();
	}

	/**
	 * 提供精确的乘法运算。
	 * @param v1 被乘数
	 * @param v2 乘数
	 * @return 两个参数的积
	 */
	public static double mul(double v1, double v2) {
		BigDecimal b1 = new BigDecimal(Double.toString(v1));
		BigDecimal b2 = new BigDecimal(Double.toString(v2));
		return b1.multiply(b2).doubleValue();
	}
	
	/**
	 * 提供精确的乘法运算。
	 * @param v1 被乘数
	 * @param v2 乘数
	 * @return 两个参数的积
	 */
	public static double mul(String v1, String v2) {
		BigDecimal b1 = new BigDecimal(v1);
		BigDecimal b2 = new BigDecimal(v2);
		return b1.multiply(b2).doubleValue();
	}
	
	/**
	 * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 小数点以后10位元,以后的数字四捨五入。
	 * @param v1 被除数
	 * @param v2 除数
	 * @return 两个参数的商
	 */
	public static double div(double v1, double v2) {
		return divide(v1, v2, DEF_DIV_SCALE);
	}
	
	/**
	 * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
	 * 定精度,以后的数字四舍五入。舍入模式采用ROUND_HALF_UP
	 * @param v1
	 * @param v2
	 * @param scale 表示需要精确到小数点以后几位
	 * @return double 两个参数的商<br/>
	 */
	public static double divide(double v1,double v2, int scale)
	{
		return divide(v1, v2, scale, BigDecimal.ROUND_HALF_UP);
	}
	
	/**
	 * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 
	 * 定精度,以后的数字四舍五入。舍入模式采用用户指定舍入模式
	 * @param v1
	 * @param v2
	 * @param scale 表示需要精确到小数点以后几位
	 * @param round_mode 表示用户指定的舍入模式
	 * @return double 两个参数的商<br/>
	 */
	public static double divide(double v1,double v2,int scale, int round_mode){
		if(scale < 0)
		{
			throw new IllegalArgumentException("The scale must be a positive integer or zero");
		} 
		BigDecimal b1 = new BigDecimal(Double.toString(v1));
		BigDecimal b2 = new BigDecimal(Double.toString(v2));
		return b1.divide(b2, scale, round_mode).doubleValue();
	}
	
	/**
	 * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 小数点以后10位元,以后的数字四捨五入。
	 * @param v1 被除数
	 * @param v2 除数
	 * @return 两个参数的商
	 */
	public static double div(String v1, String v2) {
		return divide(v1, v2, DEF_DIV_SCALE);
	}
	
	/**
	 * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
	 * 定精度,以后的数字四舍五入。舍入模式采用ROUND_HALF_UP
	 * @param v1
	 * @param v2
	 * @param scale 表示需要精确到小数点以后几位
	 * @return double 两个参数的商<br/>
	 */
	public static double divide(String v1,String v2, int scale)
	{
		return divide(v1, v2, scale, BigDecimal.ROUND_HALF_UP);
	}
	
	/**
	 * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 
	 * 定精度,以后的数字四舍五入。舍入模式采用用户指定舍入模式
	 * @param v1
	 * @param v2
	 * @param scale 表示需要精确到小数点以后几位
	 * @param round_mode 表示用户指定的舍入模式
	 * @return double 两个参数的商<br/>
	 */
	public static double divide(String v1,String v2,int scale, int round_mode){
		if(scale < 0)
		{
			throw new IllegalArgumentException("The scale must be a positive integer or zero");
		} 
		BigDecimal b1 = new BigDecimal(v1);
		BigDecimal b2 = new BigDecimal(v2);
		return b1.divide(b2, scale, round_mode).doubleValue();
	}

	/**
	 * 提供精确的小数位四舍五入处理,舍入模式采用ROUND_HALF_UP
	 * @param v 需要四舍五入的数字
	 * @param scale 小数点后保留几位
	 * @return 四舍五入后的结果
	 */
	public static double round(double v, int scale) {
		return round(v, scale, BigDecimal.ROUND_HALF_UP);
	}
	
	/**
	 * 提供精确的小数位四舍五入处理
	 * @param v 需要四舍五入的数字
	 * @param scale 小数点后保留几位
	 * @param round_mode 指定的舍入模式
	 * @return double 四舍五入后的结果<br/>
	 */
	public static double round(double v, int scale, int round_mode)
	{
		if(scale<0)
		{
			throw new IllegalArgumentException("The scale must be a positive integer or zero");
		}
		BigDecimal b = new BigDecimal(Double.toString(v));
		return b.setScale(scale, round_mode).doubleValue();
	}
	
	/**
	 * 提供精确的小数位四舍五入处理,舍入模式采用ROUND_HALF_UP
	 * @param v 需要四舍五入的数字
	 * @param scale 小数点后保留几位
	 * @return 四舍五入后的结果
	 */
	public static double round(String v, int scale) {
		return round(v, scale, BigDecimal.ROUND_HALF_UP);
	}
	
	/**
	 * 提供精确的小数位四舍五入处理
	 * @param v 需要四舍五入的数字
	 * @param scale 小数点后保留几位
	 * @param round_mode 指定的舍入模式
	 * @return double 四舍五入后的结果<br/>
	 */
	public static double round(String v, int scale, int round_mode)
	{
		if(scale<0)
		{
			throw new IllegalArgumentException("The scale must be a positive integer or zero");
		}
		BigDecimal b = new BigDecimal(v);
		return b.setScale(scale, round_mode).doubleValue();
	}
}

猜你喜欢

转载自gaojunwei.iteye.com/blog/2360642
今日推荐