Java BigDecimal 与 java的四舍五入 详解

文章出自http://blog.csdn.net/jackiehff/article/details/8582449


文章出自http://blog.csdn.net/chenssy/article/details/12719811

1.引言

        借用《Effactive Java》这本书中的话,float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。但是,商业计算往往要求结果精确,这时候BigDecimal就派上大用场啦。

 

2.BigDecimal简介

        BigDecimal 由任意精度的整数非标度值 和32 位的整数标度 (scale) 组成。如果为零或正数,则标度是小数点后的位数。如果为负数,则将该数的非标度值乘以 10 的负scale 次幂。因此,BigDecimal表示的数值是(unscaledValue × 10-scale)

 

3.测试代码

3.1构造函数(主要测试参数类型为double和String的两个常用构造函数

       BigDecimal aDouble =new BigDecimal(1.22);

        System.out.println("construct with a double value: " + aDouble);

        BigDecimal aString =new BigDecimal("1.22");

         System.out.println("construct with a String value: " + aString);

        你认为输出结果会是什么呢?如果你没有认为第一个会输出1.22,那么恭喜你答对了,输出结果如下:

         construct with a doublevalue:1.2199999999999999733546474089962430298328399658203125

         construct with a String value: 1.22

        JDK的描述:1、参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。

        2、另一方面,String 构造方法是完全可预知的:写入 newBigDecimal("0.1") 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言,通常建议优先使用String构造方法

        3、double必须用作BigDecimal的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用Double.toString(double)方法,然后使用BigDecimal(String)构造方法,将double转换为String。要获取该结果,请使用static valueOf(double)方法。

3.2 加法操作

        BigDecimal a =new BigDecimal("1.22");

        System.out.println("construct with a String value: " + a);

        BigDecimal b =new BigDecimal("2.22");

        a.add(b);

        System.out.println("aplus b is : " + a);

        我们很容易会认为会输出:

        construct with a Stringvalue: 1.22

        a plus b is :3.44

        但实际上a plus b is : 1.22

4.源码分析

4.1 valueOf(doubleval)方法

    public   static BigDecimal valueOf(double val) {

       // Reminder: a zero double returns '0.0', so we cannotfastpath

       // to use the constant ZERO. This might be important enough to

       // justify a factory approach, a cache, or a few private

       // constants, later.

       returnnew BigDecimal(Double.toString(val));//3.1关于JDK描述的第三点

    }

4.2 add(BigDecimal augend)方法

      public BigDecimal   add(BigDecimal augend) {

          long xs =this.intCompact; //整型数字表示的BigDecimal,aintCompact值为122

          long ys = augend.intCompact;//同上

          BigInteger fst = (this.intCompact !=INFLATED) ?null :this.intVal;//初始化BigInteger的值,intValBigDecimal的一个BigInteger类型的属性

          BigInteger snd =(augend.intCompact !=INFLATED) ?null : augend.intVal;

          int rscale =this.scale;//小数位数

 

          long sdiff = (long)rscale - augend.scale;//小数位数之差

          if (sdiff != 0) {//取小数位数多的为结果的小数位数

              if (sdiff < 0) {

                 int raise =checkScale(-sdiff);

                 rscale =augend.scale;

                 if (xs ==INFLATED ||

                     (xs =longMultiplyPowerTen(xs,raise)) ==INFLATED)

                     fst =bigMultiplyPowerTen(raise);

                }else {

                   int raise =augend.checkScale(sdiff);

                   if (ys ==INFLATED ||(ys =longMultiplyPowerTen(ys,raise)) ==INFLATED)

                       snd = augend.bigMultiplyPowerTen(raise);

               }

          }

          if (xs !=INFLATED && ys !=INFLATED) {

              long sum = xs + ys;

              if ( (((sum ^ xs) &(sum ^ ys))) >= 0L)//判断有无溢出

                 return BigDecimal.valueOf(sum,rscale);//返回使用BigDecimal的静态工厂方法得到的BigDecimal实例

           }

           if (fst ==null)

               fst =BigInteger.valueOf(xs);//BigInteger的静态工厂方法

           if (snd ==null)

               snd =BigInteger.valueOf(ys);

           BigInteger sum =fst.add(snd);

           return (fst.signum == snd.signum) ?new BigDecimal(sum,INFLATED, rscale, 0) :

              new BigDecimal(sum,compactValFor(sum),rscale, 0);//返回通过其他构造方法得到的BigDecimal对象

       }

 

        以上只是对加法源码的分析,减乘除其实最终都返回的是一个新的BigDecimal对象,因为BigIntegerBigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以a.add(b);虽然做了加法操作,但是a并没有保存加操作后的值,正确的用法应该是a=a.add(b);


5.java的四舍五入详解

四舍五入是我们小学的数学问题,这个问题对于我们程序猿来说就类似于1到10的加减乘除那么简单了。在讲解之间我们先看如下一个经典的案例:

  1. public static void main(String[] args) {  
  2.         System.out.println("12.5的四舍五入值:" + Math.round(12.5));  
  3.         System.out.println("-12.5的四舍五入值:" + Math.round(-12.5));  
  4.     }  
  5. Output:  
  6. 12.5的四舍五入值:13  
  7. -12.5的四舍五入值:-12  
      这是四舍五入的经典案例,也是我们参加校招时候经常会遇到的(貌似我参加笔试的时候遇到过好多次)。从这儿结果中我们发现这两个绝对值相同的数字,为何近似值会不同呢?其实这与Math.round采用的四舍五入规则来决定。

      四舍五入其实在金融方面运用的非常多,尤其是银行的利息。我们都知道银行的盈利渠道主要是利息差,它从储户手里收集资金,然后放贷出去,期间产生的利息差就是银行所获得的利润。如果我们采用平常四舍五入的规则话,这里采用每10笔存款利息计算作为模型,如下:

      四舍:0.000、0.001、0.002、0.003、0.004。这些舍的都是银行赚的钱。

      五入:0.005、0.006、0.007、0.008、0.009。这些入的都是银行亏的钱,分别为:0.005、0.004、.003、0.002、0.001。

      所以对于银行来说它的盈利应该是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元,千万别小看这个数字,这对于银行来说就是一笔非常大的损失。面对这个问题就产生了如下的银行家涉入法了。该算法是由美国银行家提出了,主要用于修正采用上面四舍五入规则而产生的误差。如下:

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

      舍去位的数值大于5时,进位后舍去。

      当舍去位的数值等于5时,若5后面还有其他非0数值,则进位后舍去,若5后面是0时,则根据5前一位数的奇偶性来判断,奇数进位,偶数舍去。

      对于上面的规则我们举例说明

         11.556 = 11.56 ------六入

         11.554 = 11.55 -----四舍

         11.5551 = 11.56 -----五后有数进位

         11.545 = 11.54 -----五后无数,若前位为偶数应舍去

         11.555 = 11.56 -----五后无数,若前位为奇数应进位

      下面实例是使用银行家舍入法:

  1. public static void main(String[] args) {  
  2.         BigDecimal d = new BigDecimal(100000);      //存款  
  3.         BigDecimal r = new BigDecimal(0.001875*3);   //利息  
  4.         BigDecimal i = d.multiply(r).setScale(2,RoundingMode.HALF_EVEN);     //使用银行家算法   
  5.           
  6.         System.out.println("季利息是:"+i);  
  7.         }  
  8. Output:  
  9. 季利息是:562.50  

      在上面简单地介绍了银行家舍入法,目前java支持7中舍入法:

        1、 ROUND_UP:远离零方向舍入。向绝对值最大的方向舍入,只要舍弃位非0即进位。

        2、 ROUND_DOWN:趋向零方向舍入。向绝对值最小的方向输入,所有的位都要舍弃,不存在进位情况。

        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是要舍弃的。

        7、 HAIF_EVEN:银行家舍入法。

      提到四舍五入那么保留位就必不可少了,在java运算中我们可以使用多种方式来实现保留位。 

  保留位

     方法一:四舍五入

  1. double   f   =   111231.5585;  
  2. BigDecimal   b   =   new   BigDecimal(f);  
  3. double   f1   =   b.setScale(2,   RoundingMode.HALF_UP).doubleValue();  

      在这里使用BigDecimal ,并且采用setScale方法来设置精确度,同时使用RoundingMode.HALF_UP表示使用最近数字舍入法则来近似计算。在这里我们可以看出BigDecimal和四舍五入是绝妙的搭配。

      方式二:

  1. java.text.DecimalFormat   df   =new   java.text.DecimalFormat(”#.00″);  
  2. df.format(你要格式化的数字);  

      例:new java.text.DecimalFormat(”#.00″).format(3.1415926)

      #.00 表示两位小数 #.0000四位小数 以此类推…

     方式三:

  1. double d = 3.1415926;  
  2.   
  3. String result = String .format(”%.2f”);  
  4.   
  5. %.2f %. 表示 小数点前任意位数   2 表示两位小数 格式后的结果为f 表示浮点型。  
      方式四:

      此外如果使用struts标签做输出的话,有个format属性,设置为format="0.00"就是保留两位小数

      例如:

  1. <bean:write name="entity" property="dkhAFSumPl"  format="0.00" />  
  2.   
  3. 或者  
  4.   
  5. <fmt:formatNumber type="number" value="${10000.22/100}" maxFractionDigits="0"/>  
  6.   
  7. maxFractionDigits表示保留的位数 

6.总结

        (1)商业计算使用BigDecimal。

        (2)尽量使用参数类型为String的构造函数。

        (3) BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以在做加减乘除运算时千万要保存操作后的值。

        (4)我们往往容易忽略JDK底层的一些实现细节,导致出现错误,需要多加注意。

7.封装Arith类

package lj.basic;

import java.math.BigDecimal;

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 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 mul(double v1, double v2)

	{

		BigDecimal b1 = new BigDecimal(Double.toString(v1));

		BigDecimal b2 = new BigDecimal(Double.toString(v2));

		return b1.multiply(b2).doubleValue();

	}

	/**
	 * 
	 * 
	 * 
	 * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
	 * 
	 * 
	 * 
	 * 小数点以后10位,以后的数字四舍五入。
	 * 
	 * 
	 * 
	 * @param v1
	 *            被除数
	 * 
	 * 
	 * 
	 * @param v2
	 *            除数
	 * 
	 * 
	 * 
	 * @return 两个参数的商
	 */

	public static double div(double v1, double v2)

	{

		return div(v1, v2, DEF_DIV_SCALE);

	}

	/**
	 * 
	 * 
	 * 
	 * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
	 * 
	 * 
	 * 
	 * 定精度,以后的数字四舍五入。
	 * 
	 * 
	 * 
	 * @param v1
	 *            被除数
	 * 
	 * 
	 * 
	 * @param v2
	 *            除数
	 * 
	 * 
	 * 
	 * @param scale
	 *            表示表示需要精确到小数点以后几位。
	 * 
	 * 
	 * 
	 * @return 两个参数的商
	 */

	public static double div(double v1, double v2, int scale)

	{

		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, BigDecimal.ROUND_HALF_UP).doubleValue();

	}

	/**
	 * 
	 * 
	 * 
	 * 提供精确的小数位四舍五入处理。
	 * 
	 * 
	 * 
	 * @param v
	 *            需要四舍五入的数字
	 * 
	 * 
	 * 
	 * @param scale
	 *            小数点后保留几位
	 * 
	 * 
	 * 
	 * @return 四舍五入后的结果
	 */

	public static double round(double v, int scale) {

		if (scale < 0)

		{

			throw new IllegalArgumentException(
					"The scale must be a positive integer or zero");

		}

		BigDecimal b = new BigDecimal(Double.toString(v));

		BigDecimal one = new BigDecimal("1");

		return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();

	}

}


  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java BigDecimalJava中用于高精度计算的类。它允许我们进行任意精度的数字计算,而不会出现舍入误差。 在Java中,基本数据类型(如int、double等)的计算是有限制的。例如,double类型只能存储15位有效数字,而且在计算过程中可能会出现舍入误差。这在需要精确计算的场合下是不可接受的,这就需要使用BigDecimal类。 以下是一些Java BigDecimal的常用方法: 1. 实例化BigDecimal对象 可以使用BigDecimal的构造函数来实例化一个对象,例如: ``` BigDecimal num1 = new BigDecimal("1234.5678"); BigDecimal num2 = new BigDecimal(9876.5432); ``` 2. 加法、减法、乘法和除法 可以使用add()、subtract()、multiply()和divide()方法进行加、减、乘和除运算,例如: ``` BigDecimal result1 = num1.add(num2); BigDecimal result2 = num1.subtract(num2); BigDecimal result3 = num1.multiply(num2); BigDecimal result4 = num1.divide(num2, 2, RoundingMode.HALF_UP); // 保留两位小数 ``` 3. 取反、取绝对值、取反余弦等 可以使用negate()、abs()、acos()等方法进行相应的计算,例如: ``` BigDecimal result5 = num1.negate(); // 取反 BigDecimal result6 = num1.abs(); // 取绝对值 BigDecimal result7 = new BigDecimal(Math.PI).acos(); // 取反余弦 ``` 4. 比较大小 可以使用compareTo()方法进行大小比较,例如: ``` int cmp = num1.compareTo(num2); if (cmp > 0) { System.out.println("num1 > num2"); } else if (cmp < 0) { System.out.println("num1 < num2"); } else { System.out.println("num1 = num2"); } ``` 以上是Java BigDecimal的一些常用方法,使用BigDecimal类可以很方便地进行高精度计算。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值