java中Double类型运算精度丢失问题小数点多出99999999999999x.docx
《java中Double类型运算精度丢失问题小数点多出99999999999999x.docx》由会员分享,可在线阅读,更多相关《java中Double类型运算精度丢失问题小数点多出99999999999999x.docx(10页珍藏版)》请在冰豆网上搜索。
java中Double类型运算精度丢失问题小数点多出99999999999999x
java中Double类型运算精度丢失问题,(小数点多出99999999999999)x
Java,double进行运算时,经常出现精度丢失的问题,总是在一个正确的结果左右偏0.0000**1。
特别在实际项目中,通过一个公式校验该值是否大于0,如果大于0我们会做一件事情,小于0我们又处理其他事情。
这样的情况通过double计算出来的结果去和0比较大小,尤其是有小数点的时候,经常会因为精度丢失而导致程序处理流程出错。
首先贴一个使用的代码:
/**
*将double类型数据转为字符串(如将18.4转为1840,如果需要1840.0,把int强转去掉即可)
*@paramd
*@return
*/
publicstaticStringdouble2String(doubled){
BigDecimalbg=newBigDecimal(d*100);
doubledoubleValue=bg.setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue();
return
String.valueOf((int)doubleValue);
}
BigDecimal在《EffectiveJava》这本书中也提到这个原则,float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用java.math.BigDecimal。
BigDecimal一共有4个够造方法,我们不关心用BigInteger来够造的那两个,那么还有两个,它们是:
BigDecimal(doubleval)
TranslatesadoubleintoaBigDecimal.
BigDecimal(Stringval)
TranslatestheStringrepresentationofaBigDecimalintoaBigDecimal.上面的API简要描述相当的明确,而且通常情况下,上面的那一个使用起来要方便一些。
我们可能想都不想就用上了,会有什么问题呢?
等到出了问题的时候,才发现上面哪个够造方法的详细说明中有这么一段:
Note:
theresultsofthisconstructorcanbesomewhatunpredictable.OnemightassumethatnewBigDecimal(.1)isexactlyequalto.1,butitisactuallyequalto.***-********-*****0**********7021********-*****.Thisissobecause.1cannotberepresentedexactlyasadouble(or,forthatmatter,asabinaryfractionof
anyfinitelength).Thus,thelongvaluethatisbeingpassedintotheconstructorisnotexactlyequalto.1,appearancesnonwithstanding.
The(String)constructor,ontheotherhand,isperfectlypredictable:
newBigDecimal(".1")isexactlyequalto.1,asonewouldexpect.Therefore,itisgenerallyrecommendedthatthe(String)constructorbeusedinpreferencetothisone.原来我们如果需要精确计算,非要用String来够造BigDecimal不可!
在《EffectiveJava》一书中的例子是用String来够造BigDecimal的,但是书上却没有强调这一点,这也许是一个小小的失误吧。
解决现在我们已经可以解决这个问题了,原则是使用BigDecimal并且一定要用String来够造。
但是想像一下吧,如果我们要做一个加法运算,需要先将两个浮点数转为String,然后够造成BigDecimal,在其中一个上调用add方法,传入另一个作为参数,然后把运算的结果(BigDecimal)再转换为浮点数。
你能够忍受这么烦琐的过程吗?
下面我们提供一个工具类Arith来简化操作。
它提供以下静态方法,包括加减乘除和四舍五入:
add(doublev1,doublev2)sub(doublev1,doublev2)mul(doublev1,doublev2))div(doublev1,doublev2,intscale)publicstaticdoubleround(doublev,intscale)
所以一般对double类型进行运算时,做好对结果进行处理,然后拿这个值去做其他事情。
使用如下:
/**
对double数据进行取精度.
value
double数据.
scale
精度位数(保留的小数位数).
paramroundingMode
精度取值方式.
*@return精度计算后的数据.
*/
publicstaticdoubleround(doublevalue,intscale,
introundingMode){
BigDecimalbd=newBigDecimal(value);
bd=bd.setScale(scale,roundingMode);
doubled=bd.doubleValue();
bd=null;
returnd;
}
/**
double相加
1
paramd2
*@return
*/
publicdoublesum(doubled1,doubled2){
11
BigDecimalbd2=newBigDecimal(Double.toString(d2));
returnbd1.add(bd2).doubleValue();
}
/**
double相减
1
paramd2
*@return
*/
publicdoublesub(doubled1,doubled2){
11
BigDecimalbd2=newBigDecimal(Double.toString(d2));
returnbd1.subtract(bd2).doubleValue();
}
/**
double乘法
1
paramd2
*@return
*/
publicdoublemul(doubled1,doubled2){
11
BigDecimalbd2=newBigDecimal(Double.toString(d2));
returnbd1.multiply(bd2).doubleValue();
}
/**
double除法
1
*@paramd2
paramscale四舍五入小数点位数
*@return
*/
publicdoublediv(doubled1,doubled2,intscale){
当然在此之前,你要判断分母是否为0,
//
为0你可以根据实际需求做相应的处理
11
BigDecimalbd2=newBigDecimal(Double.toString(d2));
returnbd1.divide
(bd2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
}
这样,计算double类型的数据计算问题就可以处理了。
另外补充一下JavaScript四舍五入的方法:
小数点问题
Math.round(totalAmount*100)/100(保留2位)
functionformatFloat(src,pos)
{
returnMath.round(src*Math.pow(10,pos))/Math.pow(10,pos);
}
四舍五入是我们小学的数学问题,这个问题对于我们程序猿来说就类似于1到10的加减乘除那么简单了。
在讲解之间我们先看如下一个经典的案例:
[java]viewplaincopyprint?
1publicstaticvoidmaiString[]args){
2
12.5的四舍五入值:
"+Math.round(12.5));
3
System.out.println("-12.5的四舍五入值:
"+Math.round(-12.5));
4
}
5Output:
612.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-----五后无数,若前位为奇数应进位
下面实例是使用银行家舍入法:
[java]viewplaincopyprint?
1publicstaticvoidmain(String[]args){
2
d=newBigDecimal(*****);
//存款
3.
BigDecimalr=newBigDecimal(0.001875*3);
//利息
4.
BigDecimali=d.multiply(r).setScale(2,RoundingMode.HALF_EVEN);
//使用银行家算法
5
6
System.out.println("季利息是:
"+i);
7
}
8Output:
9.季利息是:
562.50
在上面简单地介绍了银行家舍入法,目前java支持7中舍入法:
1、ROUND_UP:
远离零方向舍入。
向绝对值最大的方向舍入,只要舍弃位非0即进位。
2、ROUND_DOWN:
趋向零方向舍入。
向绝对值最小的方向输入,所有的位都要舍弃,不存在进位情况。
3、ROUND_*****:
向正无穷方向舍入。
向正最大方向靠拢。
若是正数,舍入行为类似于ROUND_UP,若为负数,舍入行为类似于ROUND_DOWN。
Math.round()方法就是使用的此模式。
4、ROUND_FLOOR:
向负无穷方向舍入。
向负无穷方向靠拢。
若是正数,舍入行为类似于ROUND_DOWN;若为负数,舍入行为类似于ROUND_UP。
5、HALF_UP:
最近数字舍入(5进)。
这是我们最经典的四舍五入。
6、HALF_DOWN:
最近数字舍入(5舍)。
在这里5是要舍弃的。
7、HAIL_EVEN:
银行家舍入法。
提到四舍五入那么保留位就必不可少了,在java运算中我们可以使用多种方式来实现保留位。
保留位
方法一:
四舍五入方法一:
四舍五入
[java]viewplaincopyprint?
1double
f
=
*****.5585;
2BigDecimal
b
=
new
BigDecimal(f);
3.double
f1
=
b.setScale(2,
RoundingMode.HALF_UP).doubleValue();
在这里使用BigDecimal,并且采用setScale方法来设置精确度,同时使用RoundingMode.HALF_UP表示使用最近数字舍入法则来近似计算。
在这里我们可以看出BigDecimal和四舍五入是绝妙的搭配。
方式二:
方式二:
[java]viewplaincopyprint?
1java.text.DecimalFormat
df
=new
java.text.DecimalFormat(”#.00″);
2.df.format(你要格式化的数字);
例:
newjava.text.DecimalFormat(”#.00″).format(3.***-*****)
#.00表示两位小数#.0000四位小数以此类推…
方式三:
方式三:
[java]viewplaincopyprint?
1doubled=3.***-*****;
2
3Stringresult=String.format(”%.2f”);
4
5.%.2f%.表示小数点前任意位数
2表示两位小数格式后的结果为f表示浮点型。
方式四:
方式四:
此外如果使用struts标签做输出的话,有个format属性,设置为format="0.00"就是保留两位小数
例如:
[java]viewplaincopyprint?
1bean:
writename="entity"property="dkhAFSumPl"
format="0.00"/
2
3或者
4
5.fmt:
formatNumbertype="number"value="${*****.22/100}"maxFractionDigits="0"/
6
7.maxFractionDigits表示保留的位数
BigDecimal.setScale处理java小数点BigDecimal.setScale()方法用于格式化小数点setScale
(1)表示保留一位小数,默认用四舍五入方式
DOWN)直接删除多余的小数位,如2.35会变成2.3
UP)进位处理,2.35变成2.4
(1,BigDecimal.ROUND_HALF_UP)四舍五入,2.35变成2.4setScaler(1,BigDecimal.ROUND_HALF_DOWN)四舍五入,2.35变成2.3,如果是5则向下舍
注释:
注释:
1:
ale指的是你小数点后的位数。
比如123.456则score就是3.score()就是BigDecimal类中的方法啊。
比如:
BigDecimalb=newBigDecimal("123.456");b.scale(),返回的就是3.
2:
:
roundingMode是小数的保留模式。
它们都是BigDecimal中的常量字段,有很多种。
比如:
BigDecimal.ROUND_HALF_UP表示的就是4舍5入。
3:
pubilcBigDecimaldivide(BigDecimaldivisor,intscale,introundingMode)的意思是说:
我用一个BigDecimal对象除以divisor后的结果,并且要求这个结果保留有scale个小数位,roundingMode表示的就是保留模式是什么,是四舍五入啊还是其它的,你可以自己选!
4:
对于一般:
对于一般add、subtract、multiply方法的小数位格式化如下:
方法的小数位格式化如下:
BigDecimalmData=newBigDecimal("9.655").setScale(2,BigDecimal.ROUND_HALF_UP);
System.out.println("mData="+mData);
----结果:
结果:
-----mData=9.66