转自:http://blog.csdn.net/u013066244/article/details/53172243
情形
由于公司最近要求把股票相关的数据,全部交给后端来处理,不再由前端来处理。
股票大家都知道,这里面的计算都是商业级别的,小数点4+位那是再正常不过啦。
比如这样几组数字
2539230979.0000 //流通受限股份
8680253870 //某个股东持股数
0.4081 //某某股东所占总股数的比例
需求是这样的:股份单位是 万股。比例是百分之多少(%);
所以对于股份我们需要除以10000,保留2位小数
对于比例 是要乘以100,保留2位小数。
除法
首先我们来写除法。
/**
* scale 小数点保留几位
*/
public static BigDecimal divi(double v1,double v2, int scale){
BigDecimal b1 = new BigDecimal(String.valueOf(v1));
BigDecimal b2 = new BigDecimal(String.valueOf(v2));
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP);
}
首先我们是传入两个double类型的参数和精度(小数点保留的位数),
我们再先转为String类型后,在利用BigDecimal的构造方法来生成BigDecimal对象v1、v2。
v1.divide(v2…)就是v1除以v2,保留scale为小数,BigDecimal.ROUND_HALF_UP就是我们学的四舍五入。
乘法
public static BigDecimal muli(double v1, double v2, int scale){
BigDecimal b1 = new BigDecimal(String.valueOf(v1));
BigDecimal b2 = new BigDecimal(String.valueOf(v2));
BigDecimal multiply = b1.multiply(b2);
return multiply.setScale(scale, BigDecimal.ROUND_HALF_UP)
}
这个和除法类似,首先把v1 、v2转成BigDecimal对象,然后调用BigDecimal中的multiply方法,
这个方法不像divide可以设置精度,所以得使用setScale()方法来设置精度。
设置精度(保留几位小数)
public static BigDecimal scale(double v1, int scale){
//String.valueOf(v1)
BigDecimal b1 = new BigDecimal(Double.toString(v1));
return b1.setScale(scale, BigDecimal.ROUND_HALF_UP);
}
这里我要讲的是Double.toString(v1)方法是把v1转成字符串。String.valueOf(v1),也是一样的。
那两者的区别是什么呢?其实String.valueOf(v1)源码里就是调用Double.toString(v1)的方法。
上面这种设置精度方法,有个问题:
要是double v1 = 0.0002,执行scale(v1, 2)时,得到的答案:0.00,其实有时候我们是想保存有效数字。
设置有效精度(保留有效位数)
/**
* 保留有效位(eg:0.00002 -- 得到的是0.000020)
*
* @author yutao
* @return
* @date 2016年11月14日下午1:27:28
*/
public static BigDecimal validScale(double v1, int scale){
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(String.valueOf(v1));
BigDecimal divisor = BigDecimal.ONE;
MathContext mc = new MathContext(scale);
return b.divide(divisor, mc);
}
这里就用到了MathContext类,它的构造方法有:
1、MathContext(int setPrecision)
2、MathContext(int setPrecision, RoundingMode setRoundingMode)
3、MathContext(String val)
参数setPrecision是指有效位数,不是指保留小数多少位。之前就在这里坑到过。
参数setRoundingMode是指舍入模式。这个和BigDecimal类似。
也就是说要想设置有效位,就是通过MathContext来设置的。
一般常用第1、第2中构造方法
枚举常量摘要
ROUND_CEILING
向正无限大方向舍入的舍入模式。
ROUND_DOWN
向零方向舍入的舍入模式。
ROUND_FLOOR
向负无限大方向舍入的舍入模式。
ROUND_HALF_DOWN
向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向下舍入。
ROUND_HALF_EVEN
向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。
ROUND_HALF_UP
向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向上舍入。
ROUND_UNNECESSARY
用于断言请求的操作具有精确结果的舍入模式,因此不需要舍入。(默认模式)
ROUND_UP
远离零方向舍入的舍入模式。
BigDecimal构造方法应使用String类型的
例子
假设我们先使用Double类型的构造方法。
BigDecimal d1 = new BigDecimal(9.86);
BigDecimal d2 = new BigDecimal(0.4);
BigDecimal d3 = d1.divide(d2);
System.out.println(d3);
我们这样执行后,会报如下异常:
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.math.BigDecimal.divide(BigDecimal.java:1616)
at common.ToolsUtil.main(ToolsUtil.java:164)
原因:创建BigDecimal时,0.6和0.4是浮动类型的,浮点型放入BigDecimal内,其存储值为
9.8599999999999994315658113919198513031005859375
0.40000000000000002220446049250313080847263336181640625
这两个浮点数相除时,由于除不尽,而又没有设置精度和保留小数点位数,导致抛出异常。
但是要是我们使用String构造方法就OK
BigDecimal d1 = new BigDecimal("9.86");
BigDecimal d2 = new BigDecimal("0.4");
BigDecimal d3 = d1.divide(d2);
System.out.println(d3);
为什么可以这样呢?接下来,我们探索BigDecimal原理:
BigDecimal,不可变的、任意精度的有符号十进制数。
BigDecimal 由任意精度的整数非标度值 和 32 位的整数标度(scale) 组成。
如果为零或正数,则标度是小数点后的位数。
如果为负数,则将该数的非标度值乘以 10 的负 scale 次幂。
因此,BigDecimal 表示的数值是 (unscaledValue × 10-scale)。我们知道BigDecimal有三个主要的构造函数
public BigDecimal(double val)
将double表示形式转换为BigDecimal
public BigDecimal(int val)
将int表示形式转换为BigDecimal
public BigDecimal(String val)
将字符串表示形式转换为BigDecimal
通过这三个构造函数,可以把double类型,int类型,String类型构造为BigDecimal对象,
在BigDecimal对象内通过BigIntegerintVal存储传递对象数字部分,通过int scale;记录小数点位数,
通过int precision;记录有效位数(默认为0)。
BigDecimal的加减乘除就成了BigInteger与BigInteger之间的加减乘除,浮点数的计算也转化为整形的计算,
可以大大提供性能,并且通过BigInteger可以保存大数字,从而实现真正大十进制的计算,
在整个计算过程中,还涉及scale的判断和precision判断从而确定最终输出结果。
通过上面的例子可以看出String的构造函数就是通过BigInteger记录BigDecimal的值,
使其计算变成BigInteger之间的计算。所以我们一般最好使用String类型的构造方法。
那如果非要使用Double类型的构造方法呢?
我们可以利用divide设置精度的方式来做
BigDecimal d1 = new BigDecimal(9.86);
BigDecimal d2 = new BigDecimal(0.4);
BigDecimal d3 = d1.divide(d2 ,1 , BigDecimal.ROUND_HALF_UP);
System.out.println(d3);
通过/1,然后设置保留小数点方式,以及设置数字保留模式,从而得到两个数乘积的小数部分。
也就是给它设置好精度和舍入模式,就OK啦。(它就是通过舍入方式得到正确的答案)
《BigDecimal 的那些坑事儿》> http://blog.csdn.net/ugg/article/details/8213666