1.引言 借用《Effactive Java》这本书中的话,float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供彻底精确的结果,因此不该该被用于要求精确结果的场合。可是,商业计算每每要求结果精确,这时候BigDecimal就派上大用场啦。java
2.BigDecimal简介 BigDecimal 由任意精度的整数非标度值 和32 位的整数标度 (scale) 组成。若是为零或正数,则标度是小数点后的位数。若是为负数,则将该数的非标度值乘以 10 的负scale 次幂。所以,BigDecimal表示的数值是(unscaledValue × 10-scale)。git
3.测试代码app
3.1构造函数(主要测试参数类型为double和String的两个经常使用构造函数)ide
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的描述:工具
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,例a的intCompact值为122 long ys = augend.intCompact;//同上 BigInteger fst = (this.intCompact !=INFLATED) ?null :this.intVal;//初始化BigInteger的值,intVal为BigDecimal的一个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对象,由于BigInteger与BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,因此a.add(b);虽然作了加法操做,可是a并无保存加操做后的值,正确的用法应该是a=a.add(b);this
5.总结设计
** (1)商业计算使用BigDecimal。
(2)尽可能使用参数类型为String的构造函数。
(3) BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,因此在作加减乘除运算时千万要保存操做后的值。
(4)咱们每每容易忽略JDK底层的一些实现细节,致使出现错误,须要多加注意。**
BigDecimal 由任意精度的整数非标度值 和 32 位的整数标度 (scale) 组成。若是为零或正数,则标度是小数点后的位数。若是为负数,则将该数的非标度值乘以 10 的负 scale 次幂。所以,BigDecimal 表示的数值是 (unscaledValue × 10-scale)。
能够处理任意长度的浮点数运算。
BigDecimal add(BigDecimal val) //BigDecimal 加法
BigDecimal subtract (BigDecimal val) //BigDecimal 减法
BigDecimal multiply (BigDecimal val) //BigDecimal 乘法
BigDecimal divide (BigDecimal val,RoundingMode mode) 除法
具体使用 计算:
加: a.add(b);
减: a.subtract(b);
乘: a.multiply(b);
除: a.divide(b,2);//2为精度取值
** 除法细解:
//注意如下相除会抛出异常,缘由: 经过BigDecimal的divide方法进行除法时当不整除,出现无限循环小数时,就会抛异常
//BigDecimal divideBg = a.divide(b);
//解决方法是:设置精确度;就是给divide设置精确的小数点
divide(xxxxx,2, BigDecimal.ROUND_HALF_EVEN)
//其中的第二个参数表示的是:保留小数点以后多少位 **
BigDecimal不整除抛出的异常,请设置精确度! Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result. at java.math.BigDecimal.divide(BigDecimal.java:1278) at main.Main.main(Main.java:41)
下面咱们来看看除法的详细说明: divide(BigDecimal divisor, int scale, introundingMode)
BigDecimal的setScale方法
BigDecimal.setScale()
方法用于格式化小数点
表示保留一位小数,默认用四舍五入方式 setScale(1)
直接删除多余的小数位,如2.35会变成2.3 setScale(1,BigDecimal.ROUND_DOWN)
进位处理,2.35变成2.4 setScale(1,BigDecimal.ROUND_UP)
四舍五入,2.35变成2.4 setScale(1,BigDecimal.ROUND_HALF_UP)
四舍五入,2.35变成2.3,若是是5则向下舍setScaler(1,BigDecimal.ROUND_HALF_DOWN)
**注意点一 **
scale指的是你小数点后的位数。 scale()就是BigDecimal类中的方法。如
BigDecimal b = new BigDecimal("123.456");
b.scale()返回的就是3
注意点二
roundingMode是小数的保留模式。它们都是BigDecimal中的常量字段,
有不少种,如 BigDecimal.ROUND_HALF_UP表示的就是4舍5入
**注意点三 **
divide(BigDecimal divisor, int scale, introundingMode)的意思是说: 我用一个BigDecimal对象除以divisor后的结果,而且要求这个结果保留有scale个小数位,roundingMode表示的就是保留模式是什么,是四舍五入啊仍是其它的
BigDecimal aa = new BigDecimal(135.95 );
BigDecimal bb=new BigDecimal("100" );
BigDecimal result=aa.multiply(bb); //作加法
**3.java中 BigDecimal类型的能够转换到double类型: **
用 变量.doubleValue();函数 便可将 BigDecimal 类型数据 转化为 double类型!
**4.java BigDecimal比较大小 **
能够经过BigDecimal的compareTo方法来进行比较。 返回的结果是int类型,-1表示小于,0是等于,1是大于。
看下面这个例子: BigDecimal a = new BigDecimal("1.00"); BigDecmial b = new BigDecimal(1);
想比较一下a和b的大小,通常都会用equals
System.out.println(a.equals(b)); 可是输出结果是:false 缘由是:BigDecimal比较时,不只比较值,并且还比较精度?
if(a.compareTo(b)==0) 结果是true
比较大小能够用 a.compareTo(b) 返回值 -1 小于 0 等于 1 大于
**5.BigDecimal取其中最大、最小值、绝对值、相反数: **
a.max (b) //比较取最大值
a.min(b) //比较取最小值
a.abs()//取最绝对值
a.negate()//取相反数
6.下面是注意 :
BigDecimal枚举常量用法摘要 :
CEILING 向正无限大方向舍入的舍入模式。
DOWN 向零方向舍入的舍入模式。
FLOOR 向负无限大方向舍入的舍入模式。
HALF_DOWN 向最接近数字方向舍入的舍入模式,若是与两个相邻数字的距离相等,则向下舍入。
HALF_EVEN 向最接近数字方向舍入的舍入模式,若是与两个相邻数字的距离相等,则向相邻的偶数舍入。
HALF_UP 向最接近数字方向舍入的舍入模式,若是与两个相邻数字的距离相等,则向上舍入。
UNNECESSARY 用于断言请求的操做具备精确结果的舍入模式,所以不须要舍入。
UP 远离零方向舍入的舍入模式。
**7.关于BigDecimal格式化 **
public String formatValue(Object value){ String content = null; if (value == null) { content = ""; } else { if(value instanceof BigDecimal){ //conver to fortmat String NumberFormat nf = NumberFormat.getInstance(); nf.setMinimumFractionDigits(2); nf.setMaximumFractionDigits(2); content = nf.format(value); }else{ content = String.valueOf(value); } } return content; }
使用这样一个方法能够达到格式化的效果,其中value instanceof BigDecimal,表示的是字符类型是BigDecimal类型的时候执行,这里的NumberFormat就表示字符类型,下面的两句代码就表示小数点后面的精确位数。
这里还要提到NumberFormat的其余两个类型:
getCurrencyInstance(): 返回当前默认 环境的货币格式
CurrencyInstance(): 返回指定语言 环境的数字格式,通常是百分比格式
**8.在《Effective Java》这本书中也提到这个原则,float和double只能用来作科学计算或者是工程计算,在商业计算中咱们要用java.math.BigDecimal。 咱们若是须要精确计算,非要用String来够造BigDecimal不可! **
下面的这个工具类是是转载别人的,能够经过个工具类实现小数的精确计算。
1import java.math.BigDecimal; 2/** *//** 3* 因为Java的简单类型不可以精确的对浮点数进行运算,这个工具类提供精 4* 确的浮点数运算,包括加减乘除和四舍五入。 5*/ 6public class Arith{ 7 //默认除法运算精度 8 private static final int DEF_DIV_SCALE = 10; 9 //这个类不能实例化 10 private Arith(){ 11 } 12 13 /** *//** 14 * 提供精确的加法运算。 15 * @param v1 被加数 16 * @param v2 加数 17 * @return 两个参数的和 18 */ 19 public static double add(double v1,double v2){ 20 BigDecimal b1 = new BigDecimal(Double.toString(v1)); 21 BigDecimal b2 = new BigDecimal(Double.toString(v2)); 22 return b1.add(b2).doubleValue(); 23 } 24 /** *//** 25 * 提供精确的减法运算。 26 * @param v1 被减数 27 * @param v2 减数 28 * @return 两个参数的差 29 */ 30 public static double sub(double v1,double v2){ 31 BigDecimal b1 = new BigDecimal(Double.toString(v1)); 32 BigDecimal b2 = new BigDecimal(Double.toString(v2)); 33 return b1.subtract(b2).doubleValue(); 34 } 35 /** *//** 36 * 提供精确的乘法运算。 37 * @param v1 被乘数 38 * @param v2 乘数 39 * @return 两个参数的积 40 */ 41 public static double mul(double v1,double v2){ 42 BigDecimal b1 = new BigDecimal(Double.toString(v1)); 43 BigDecimal b2 = new BigDecimal(Double.toString(v2)); 44 return b1.multiply(b2).doubleValue(); 45 } 46 47 /** *//** 48 * 提供(相对)精确的除法运算,当发生除不尽的状况时,精确到 49 * 小数点之后10位,之后的数字四舍五入。 50 * @param v1 被除数 51 * @param v2 除数 52 * @return 两个参数的商 53 */ 54 public static double div(double v1,double v2){ 55 return div(v1,v2,DEF_DIV_SCALE); 56 } 57 58 /** *//** 59 * 提供(相对)精确的除法运算。当发生除不尽的状况时,由scale参数指 60 * 定精度,之后的数字四舍五入。 61 * @param v1 被除数 62 * @param v2 除数 63 * @param scale 表示表示须要精确到小数点之后几位。 64 * @return 两个参数的商 65 */ 66 public static double div(double v1,double v2,int scale){ 67 if(scale<0){ 68 throw new IllegalArgumentException( 69 "The scale must be a positive integer or zero"); 70 } 71 BigDecimal b1 = new BigDecimal(Double.toString(v1)); 72 BigDecimal b2 = new BigDecimal(Double.toString(v2)); 73 return b1.divid(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue(); 74 } 75 76 /** *//** 77 * 提供精确的小数位四舍五入处理。 78 * @param v 须要四舍五入的数字 79 * @param scale 小数点后保留几位 80 * @return 四舍五入后的结果 81 */ 82 public static double round(double v,int scale){ 83 if(scale<0){ 84 throw new IllegalArgumentException( 85 "The scale must be a positive integer or zero"); 86 } 87 BigDecimal b = new BigDecimal(Double.toString(v)); 88 BigDecimal one = new BigDecimal("1"); 89 return b.divid(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue(); 90 } 91 }