背景:最近做金融的项目,对于金额数字特别敏感,但是经常精度缺失,找的文章去学习。
最近查看rebate数据时,发现一个bug,主要现象是,当扣款支付宝的账号款项时,返回的是数字的金额为元,而数据库把金额存储为分,这中间要做元与分的转化,这个转化规则很简单,就是*100的,所以一开始代码很简单,如下。
- Float f = Float.valueOf(s);
- f =f*100;
- Long result = f.longValue();
- Double d = Double.valueOf(s);
- d = d*100;
- Long result = d.longValue();
针对这样的问题,如果使用C/C++语言,那么通用解决方案可以这样。
- Double d = Double.valueOf(s);
- d = d*100+0.5;// 注意这里,我们使用的是+0.5的形式。
- Long result = d.longValue();
使用BigDecimal的解决方案成这个样子
- Double dd= Double.valueOf(s);
- BigDecimal bigD = new BigDecimal(dd);
- bigD = bigD.multiply(new BigDecimal(100));
- Long result = bigD.longValue();
- System.out.println(bigD.toString());
- 985.9999999999999431565811391919851303100585937500
- Double dd= Double.valueOf(s);
- BigDecimal bigD = new BigDecimal(dd);
- MathContextmc = new MathContext(4,RoundingMode.HALF_UP);
- //4表示取四位有效数字,RoundingMode.HALF_UP表示四舍五入
- bigD= bigD.multiply(new BigDecimal(100),mc);
- Long result = bigD.longValue();
- Double dd =Double.valueOf(s);
- BigDecimalbigD = new BigDecimal(dd);
- MathContextmc = new MathContext(s.length(),RoundingMode.HALF_UP);
- //4表示取四位有效数字,RoundingMode.HALF_UP表示四舍五入
- bigD= bigD.multiply(new BigDecimal(100),mc);
- Long result = bigD.longValue();
BigDecimal,不可变的、任意精度的有符号十进制数。 BigDecimal 由任意精度的整数 非标度值 和 32 位的整数 标度(scale) 组成。如果为零或正数,则标度是小数点后的位数。如果为负数,则将该数的非标度值乘以 10 的负 scale 次幂。因此, BigDecimal 表示的数值是 (unscaledValue × 10-scale)。我们知道BigDecimal有三个主要的构造函数
1 | public BigDecimal(double val) | 将double表示形式转换为BigDecimal |
2 | public BigDecimal(int val) | 将int表示形式转换为BigDecimal |
3 | public BigDecimal(String val) | 将字符串表示形式转换为BigDecimal |
通过这三个构造函数,可以把double类型,int类型,String类型构造为BigDecimal对象,在BigDecimal对象内通过BigIntegerintVal存储传递对象数字部分,通过int scale;记录小数点位数,通过int precision;记录有效位数(默认为0)。
BigDecimal的加减乘除就成了BigInteger与BigInteger之间的加减乘除,浮点数的计算也转化为整形的计算,可以大大提供性能,并且通过BigInteger可以保存大数字,从而实现真正大十进制的计算,在整个计算过程中,还涉及scale的判断和precision判断从而确定最终输出结果。
我们先看一个例子
- BigDecimal d1 = new BigDecimal(0.6);
- BigDecimal d2 = new BigDecimal(0.4);
- BigDecimal d3 = d1.divide(d2);
- System.out.println(d3);
- BigDecimal d1 = new BigDecimal(“0.6”);
- BigDecimal d2 = new BigDecimal(“0.4”);
- BigDecimal d3 = d1.divide(d2);
- System.out.println(d3);
- 0.59999999999999997779553950749686919152736663818359375
- 0.40000000000000002220446049250313080847263336181640625
- 0.6
- 0.4
对于第一个例子,如果我们想得到正确结果,可以这样来
- BigDecimal d1 = new BigDecimal(0.6);
- BigDecimal d2 = new BigDecimal(0.4);
- BigDecimal d3 = d1.divide(d2, 1, BigDecimal.ROUND_HALF_UP);
- Double dd= Double.valueOf(s);
- BigDecimal bigD = new BigDecimal(dd);
- bigD = bigD.multiply(newBigDecimal(100)). divide(1, 1, BigDecimal.ROUND_HALF_UP);
- Long result = bigD.longValue();
枚举常量摘要
ROUND_CEILING
向正无限大方向舍入的舍入模式。
ROUND_DOWN
向零方向舍入的舍入模式。
ROUND_FLOOR
向负无限大方向舍入的舍入模式。
ROUND_HALF_DOWN
向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向下舍入。
ROUND_HALF_EVEN
向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。
ROUND_HALF_UP
向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向上舍入。
ROUND_UNNECESSARY
用于断言请求的操作具有精确结果的舍入模式,因此不需要舍入。(默认模式)
ROUND_UP
远离零方向舍入的舍入模式。
总结:
1:尽量避免传递double类型,有可能话,尽量使用int和String类型。
2:做乘除计算时,一定要设置精度和保留小数点位数。
3:BigDecimal计算时,单独放到try catch内。
转自:http://blog.csdn.net/ugg/article/details/8213666