关于Java中⽤Double型运算时精度丢失的问题
在使⽤Java,double 进⾏运算时,经常出现精度丢失的问题,总是在⼀个正确的结果左右偏0.0000**1。特别在实际项⽬中,通过⼀个公式校验该值是否⼤于0,如果⼤于0我们
会做⼀件事情,⼩于0我们⼜处理其他事情。这样的情况通过double计算出来的结果去和0⽐较⼤⼩,尤其是有⼩数点的时候,经常会因为精度丢失⽽导致程序处理流程出错。
BigDecimal
在《Effective Java》这本书中也提到这个原则,float和double只能⽤来做科学计算或者是⼯程计算,在商业计算中我们要⽤ java.math.BigDecimal。BigDecimal⼀共有4个够造
⽅法,我们不关⼼⽤BigInteger来够造的那两个,那么还有两个,它们是:
BigDecimal(double val)
Translates a double into a BigDecimal.
bigdecimal除法保留小数BigDecimal(String val)
Translates the String repre sentation of a BigDecimal into a BigDecimal.
上⾯的API简要描述相当的明确,⽽且通常情况下,上⾯的那⼀个使⽤起来要⽅便⼀些。我们可能想都不想就⽤上了,会有什么问题呢?等到出了问题的时候,才发现上⾯哪个
够造⽅法的详细说明中有这么⼀段:
Note: the results of this constructor can be somewhat unpredictable. One might assume that new BigDecimal(.1) is exactly equal to .1, but it is actually equal to .100000000000000005551115123125782 The (String) constructor, on the other hand, is perfectly predictable: new BigDecimal(".1") is exactly equal to .1, as one would expect. Therefore, it is generally recommended that the (String) constructor b 原来我们如果需要精确计算,⾮要⽤String来够造BigDecimal不可!在《Effective Java》⼀书中的例⼦是⽤String来够造BigDecimal的,但是书上却没有强调这⼀点,这也许是
⼀个⼩⼩的失误吧。
解决⽅案
现在我们已经可以解决这个问题了,原则是使⽤BigDecimal并且⼀定要⽤String来够造。
但是想像⼀下吧,如果我们要做⼀个加法运算,需要先将两个浮点数转为String,然后够造成BigDecimal,在其中⼀个上调⽤add⽅法,传⼊另⼀个作为参数,然后把运算的结
果(BigDecimal)再转换为浮点数。你能够忍受这么烦琐的过程吗?下⾯我们提供⼀个⼯具类Arith来简化操作。它提供以下静态⽅法,包括加减乘除和四舍五⼊:
public static double add(double v1,double v2)
public static double sub(double v1,double v2)
public static double mul(double v1,double v2)
public static double div(double v1,double v2)
public static double div(double v1,double v2,int scale)
public static double round(double v,int scale)
所以⼀般对double类型进⾏运算时,做好对结果进⾏处理,然后拿这个值去做其他事情。
使⽤如下:
1/**
2 * 对double数据进⾏取精度.
3 * @param value double数据.
4 * @param scale 精度位数(保留的⼩数位数).
5 * @param roundingMode 精度取值⽅式.
6 * @return精度计算后的数据.
7*/
8public static double round(double value, int scale,
9int roundingMode) {
10 BigDecimal bd = new BigDecimal(value);
11 bd = bd.setScale(scale, roundingMode);
12double d = bd.doubleValue();
13 bd = null;
14return d;
15 }
16
17
18/**
19 * double 相加
20 * @param d1
21 * @param d2
22 * @return
23*/
24public double sum(double d1,double d2){
25 BigDecimal bd1 = new String(d1));
26 BigDecimal bd2 = new String(d2));
27return bd1.add(bd2).doubleValue();
28 }
29
30
31/**
32 * double 相减
33 * @param d1
34 * @param d2
35 * @return
36*/
37public double sub(double d1,double d2){
38 BigDecimal bd1 = new String(d1));
39 BigDecimal bd2 = new String(d2));
40return bd1.subtract(bd2).doubleValue();
41 }
42
43/**
44 * double 乘法
45 * @param d1
46 * @param d2
47 * @return
48*/
49public double mul(double d1,double d2){
50 BigDecimal bd1 = new String(d1));
51 BigDecimal bd2 = new String(d2));
52return bd1.multiply(bd2).doubleValue();
53 }
54
55
56/**
57 * double 除法
58 * @param d1
59 * @param d2
60 * @param scale 四舍五⼊⼩数点位数
61 * @return
62*/
63public double div(double d1,double d2,int scale){
64// 当然在此之前,你要判断分母是否为0,
65// 为0你可以根据实际需求做相应的处理
66
67 BigDecimal bd1 = new String(d1));
68 BigDecimal bd2 = new String(d2));
69return bd1.divide
70 (bd2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
71 }
这样,计算double类型的数据计算问题就可以处理了。
另外补充⼀下 JavaScript 四舍五⼊的⽅法:
⼩数点问题
und(totalAmount*100)/100 (保留
2 位)
2
3function formatFloat(src, pos)
4 {
und(src*Math.pow(10, pos))/Math.pow(10, pos);
6 }
四舍五⼊是我们⼩学的数学问题,这个问题对于我们程序猿来说就类似于1到10的加减乘除那么简单了。在讲解之间我们先看如下⼀个经典的案例:
1public static void main(String[] args) {
2 System.out.println("12.5的四舍五⼊值:" + und(12.5));
3 System.out.println("-12.5的四舍五⼊值:" + und(-12.5));
4 }
Output:
12.5的四舍五⼊值:13
-12.5的四舍五⼊值:-12
这是四舍五⼊的经典案例,也是我们参加校招时候经常会遇到的(貌似我参加笔试的时候遇到过好多次)。从这⼉结果中我们发现这两个绝对值相同的数字,为何近似值会不同呢?其实这与und采⽤的四舍五⼊规则来决定。
四舍五⼊其实在⾦融⽅⾯运⽤的⾮常多,尤其是银⾏的利息。我们都知道银⾏的盈利渠道主要是利息差,它从储户⼿⾥收集资⾦,然后放贷出去,期间产⽣的利息差就是银⾏所获得的利润。如果我们采⽤平常四舍五⼊的规则话,这⾥采⽤每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 -----五后⽆数,若前位为奇数应进位
下⾯实例是使⽤银⾏家舍⼊法:
1public 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 }
Output:
季利息是:562.50
在上⾯简单地介绍了银⾏家舍⼊法,⽬前java⽀持7中舍⼊法:
1、 ROUND_UP:远离零⽅向舍⼊。向绝对值最⼤的⽅向舍⼊,只要舍弃位⾮0即进位。
2、 ROUND_DOWN:趋向零⽅向舍⼊。向绝对值最⼩的⽅向输⼊,所有的位都要舍弃,不存在进位情况。
3、 ROUND_CEILING:向正⽆穷⽅向舍⼊。向正最⼤⽅向靠拢。若是正数,舍⼊⾏为类似于ROUND_UP,若为负数,舍⼊⾏为类似于ROUND_DOWN。und()⽅法就是使⽤的此模式。
4、 ROUND_FLOOR:向负⽆穷⽅向舍⼊。向负⽆穷⽅向靠拢。若是正数,舍⼊⾏为类似于ROUND_DOWN;若为负数,舍⼊⾏为类似于ROUND_UP。
5、 HALF_UP:最近数字舍⼊(5进)。这是我们最经典的四舍五⼊。
6、 HALF_DOWN:最近数字舍⼊(5舍)。在这⾥5是要舍弃的。
7、 HAIL_EVEN:银⾏家舍⼊法。
提到四舍五⼊那么保留位就必不可少了,在java运算中我们可以使⽤多种⽅式来实现保留位。
保留位
⽅法⼀:四舍五⼊
1double f = 111231.5585;
2 BigDecimal b = new BigDecimal(f);
3double f1 = b.setScale(2, RoundingMode.HALF_UP).doubleValue();
在这⾥使⽤BigDecimal ,并且采⽤setScale⽅法来设置精确度,同时使⽤RoundingMode.HALF_UP表⽰使⽤最近数字舍⼊法则来近似计算。在这⾥我们可以看出BigDecimal和四舍五⼊是绝妙的搭配。
⽅式⼆:
DecimalFormat df =new DecimalFormat(”#.00″);
2 df.format(你要格式化的数字);
例:DecimalFormat(”#.00″).format(3.1415926)
#.00 表⽰两位⼩数 #.0000四位⼩数以此类推…
⽅式三:
1double d = 3.1415926;
2
3 String result = String .format(”%.2f”);
%.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表⽰保留的位数
BigDecimal.setScale()⽅法⽤于格式化⼩数点
setScale(1)表⽰保留⼀位⼩数,默认⽤四舍五⼊⽅式
setScale(1,BigDecimal.ROUND_DOWN)直接删除多余的⼩数位,如2.35会变成2.3
setScale(1,BigDecimal.ROUND_UP)进位处理,2.35变成2.4
setScale(1,BigDecimal.ROUND_HALF_UP)四舍五⼊,2.35变成2.4
setScaler(1,BigDecimal.ROUND_HALF_DOWN)四舍五⼊,2.35变成2.3,如果是5则向下舍
注释:
1:
scale指的是你⼩数点后的位数。⽐如123.456则score就是3.
score()就是BigDecimal类中的⽅法啊。
⽐如:BigDecimal b = new BigDecimal("123.456");
b.scale(),返回的就是3.
2:
roundingMode是⼩数的保留模式。它们都是BigDecimal中的常量字段,有很多种。
⽐如:BigDecimal.ROUND_HALF_UP表⽰的就是4舍5⼊。
3:
pubilc BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
的意思是说:我⽤⼀个BigDecimal对象除以divisor后的结果,并且要求这个结果保留有scale个⼩数位,roundingMode表⽰的就是保留模式是什么,是四舍五⼊啊还是其它的,你可以⾃⼰选!
4:对于⼀般add、subtract、multiply⽅法的⼩数位格式化如下:
BigDecimal mData = new BigDecimal("9.655").setScale(2, BigDecimal.ROUND_HALF_UP);
System.out.println("mData=" + mData);
----结果:----- mData=9.66
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论