用java实现浮点数的准确计较
副标题#e#
问题的提出:
假如我们编译运行下面这个措施会看到什么?
public class Test{
public static void main(String args[]){
System.out.println(0.05+0.01);
System.out.println(1.0-0.42);
System.out.println(4.015*100);
System.out.println(123.3/100);
}
};
你没有看错!功效确实是
0.060000000000000005
0.5800000000000001
401.49999999999994
1.2329999999999999
Java中的简朴浮点数范例float和double不可以或许举办运算。不仅是Java,在其它许多编程语言中也有这样的问题。在大大都环境下,计较的功效是精确的,可是多试屡次(可以做一个轮回)就可以试出雷同上面的错误。此刻终于领略为什么要有BCD码了。
这个问题相当严重,假如你有9.999999999999元,你的计较机是不会认为你可以购置10元的商品的。
在有的编程语言中提供了专门的钱币范例来处理惩罚这种环境,可是Java没有。此刻让我们看看如何办理这个问题。
四舍五入
我们的第一个回响是做四舍五入。Math类中的round要领不能配置保存几位小数,我们只能象这样(保存两位):
public double round(double value){
return Math.round(value*100)/100.0;
}
很是不幸,上面的代码并不能正常事情,给这个要领传入4.015它将返回4.01而不是4.02,如我们在上面看到的
4.015*100=401.49999999999994
因此假如我们要做到准确的四舍五入,不能操作简朴范例做任何运算
java.text.DecimalFormat也不能办理这个问题:
System.out.println(newjava.text.DecimalFormat("0.00").format(4.025));
输出是4.02
BigDecimal
在《EffectiveJava》这本书中也提到这个原则,float和double只能用来做科学计较可能是工程计较,在贸易计较中我们要用java.math.BigDecimal。BigDecimal一共有4个够造要领,我们不体贴用BigInteger来够造的那两个,那么尚有两个,它们是:
BigDecimal(doubleval)
TranslatesadoubleintoaBigDecimal.
BigDecimal(Stringval)
TranslatestheStringrepresentationofaBigDecimalintoaBigDecimal.
上面的API扼要描写相当的明晰,并且凡是环境下,上面的那一个利用起来要利便一些。
我们大概想都不想就用上了,会有什么问题呢?比及出了问题的时候,才发明上面哪个够造要领的具体说明中有这么一段:
Note:theresultsofthisconstructorcanbesomewhatunpredictable.Onemight
assumethatnewBigDecimal(.1)isexactlyequalto.1,butitisactually
equalto.1000000000000000055511151231257827021181583404541015625.Thisisso
because.1cannotberepresentedexactlyasadouble(or,forthatmatter,as
abinaryfractionofanyfinitelength).Thus,thelongvaluethatisbeing
passedintotheconstructorisnotexactlyequalto.1,appearances
nonwithstanding.
The(String)constructor,ontheotherhand,isperfectlypredictable:new
BigDecimal(".1")isexactlyequalto.1,asonewouldexpect.Therefore,itis
generallyrecommendedthatthe(String)constructorbeusedinpreferenceto
thisone.
本来我们假如需要准确计较,非要用String来够造BigDecimal不行!在《EffectiveJava》一书中的例子是用String来够造BigDecimal的,可是书上却没有强调这一点,这也许是一个小小的失误吧。
#p#副标题#e#
办理方案
此刻我们已经可以办理这个问题了,原则是利用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)
附录
源文件Arith.java:
import java.math.BigDecimal;
/**
* 由于Java的简朴范例不可以或许准确的对浮点数举办运算,这个东西类提供精
* 确的浮点数运算,包罗加减乘除和四舍五入。
*/
public class Arith{
//默认除法运算精度
private static final int DEF_DIV_SCALE = 10;
//这个类不能实例化
private Arith(){
}
/**
* 提供准确的加法运算。
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static double add(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}
/**
* 提供准确的减法运算。
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static double sub(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}
/**
* 提供准确的乘法运算。
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static double mul(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}
/**
* 提供(相对)准确的除法运算,当产生除不尽的环境时,准确到
* 小数点今后10位,今后的数字四舍五入。
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1,double v2){
return div(v1,v2,DEF_DIV_SCALE);
}
/**
* 提供(相对)准确的除法运算。当产生除不尽的环境时,由scale参数指
* 定精度,今后的数字四舍五入。
* @param v1 被除数
* @param v2 除数
* @param scale 暗示暗示需要准确到小数点今后几位。
* @return 两个参数的商
*/
public static double div(double v1,double v2,int scale){
if(scale<0){
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供准确的小数位四舍五入处理惩罚。
* @param v 需要四舍五入的数字
* @param scale 小数点后保存几位
* @return 四舍五入后的功效
*/
public static double round(double v,int scale){
if(scale<0){
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
BigDecimal one = new BigDecimal("1");
return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
}
};