Java中的数字基本运算和精度有关问题

Java中的数字基本运算和精度问题

 

. 精度

 

举例:double result = 1.0 - 0.9;

这个结果不用说了吧,都知道了,0.09999999999999998

floatdouble类型主要是为了科学计算和工程计算而设计的。他们执行二进制浮点运算,这是为了在广泛的数字范围上提供较为精确的快速近似计算而精心设计的。然而,它们并没有提供完全精确的结果,所以我们不应该用于精确计算的场合。floatdouble类型尤其不适合用于货币运算,因为要让一个floatdouble精确的表示0.1或者10的任何其他负数次方值是不可能的(其实道理很简单,十进制系统中能不能准确表示出1/3呢?同样二进制系统也无法准确表示1/10)。

浮点运算很少是精确的,只要是超过精度能表示的范围就会产生误差。往往产生误差不是因为数的大小,而是因为数的精度。因此,产生的结果接近但不等于想要的结果。尤其在使用 float double 作精确运算的时候要特别小心。

 

 

double result = 1.0 - 0.9;//坏代码
 

 

 

. 解决精度丢失有几种比较常用的方法

 

1. NumberFormat类来格式化计算结果,按照自己想要的结果进行格式化,缺点就是要手动去格式化,舍入方式不同结果不一定精确。

 

// 好代码
double result = 1.0 - 0.9;
NumberFormat nf = NumberFormat.getInstance();// 根据自己的需求格式化
String resultStr = nf.format(result);
 

 

 

2. 如果不介意自己记录十进制的小数点,而且数值不大,那么可以使用long int等基本类型,具体用int还是long要看涉及的数值范围大小,缺点是要自己处理十进制小数点,最明显的做法就是处理货币使用分来计算,而不用元(只涉及加减)。

 

// 好代码
int resultInt = 10 - 9;
double result = (double) resultInt / 100;//最终时候自己控制小数点
 

 

 

3. 使用BigDecimal来代替double,它能让你完全控制精度,结果会非常精确,加减乘除写起来也很方便,不过他有两个缺点:1,不是基本类型,与基本类型相比,操作起来不方便;2. 速度没有基本类型快,算是用速度换精度。相比第2个缺点并不要紧,但是第一个缺点会让你写起代码很不舒服。

 

// 好代码
String result = new BigDecimal("1").subtract(new BigDecimal("0.9")) .toString();
 

 

个人比较喜欢这个,而且BigDecimal还支持常用的格式化方法,如

BigDecimal num = new BigDecimal("384400000");

String str = new DecimalFormat("地球和月球的距离:#,##,###,###").format(num);

结果:地球和月球的距离:384,400,000

 

 

// 任意使用double或float进行数学运算,而忽略精度。// 坏代码
// 数学计算考虑数到了精度问题。// 好代码
 

 

 

. 浮点类型的比较

由于精度问题,double/float比较相等也不能直接使用==,但是比较大小可以用< >

 

 

double d1 = 0.1, d2 = 0.1;
if (d1 == d2) {}// 坏代码
if (Double.compare(d1, d2) == 0) {}// 好代码
if (Double.doubleToLongBits(d1) == Double.doubleToLongBits(d2)) {}// 好代码
if (Double.valueOf(d1).equals(d2)) {}// 好代码,1.5以上
 

 

 

. 合理使用第三方工具类

apachecommons-lang工具包中个math包,提供了常用的计算方法,数字处理方法,如果项目中涉及到的计算比较多,可以考虑使用commons-math包,这个包过于专业化,估计咱们用到的地方不多。

apachecommons-langmath包,主要有4大类功能

1. 处理分数的Fraction类,分数表示数字,更为精确。

 

Fraction fraction = Fraction.getFraction(10, 3);// 三分之十
System.out.println(fraction);// 10/3
System.out.println(fraction.floatValue());// 3.3333333
System.out.println(fraction.doubleValue());// 3.3333333333333335
System.out.println(fraction.toProperString());// 三又三分之一
System.out.println(fraction.reduce());// 约分,如2/4约分后1/2
 

 

2. 处理数值的NumberUtils类;这个比较简单,看看api就可以。封装了一些常用数字操作方法,如数字转换、比较,获取一个数字数组中最大值,最小值等。

3. 处理数值范围的RangeNumberRangeIntRangeLongRangeFloatRangeDoubleRange类;

 

// 拿int举例,其他类似
IntRange intRange = new IntRange(100, 200);// 创建一个范围
intRange.containsDouble(111.1);// 是否包含指定数字
int[] range = intRange.toArray();// 获取范围内的int
intRange.getMaximumDouble();// 获取最大值,转换成double
intRange.getMinimumDouble();// 获取最小值,转换成double
 

 

4. 处理随机数的JVMRandomRandomUtils类。这个也比较简单,看看api就可以,获取随机数时比较方便而已。

 

commons-math工具包是apache 上一个轻量级自容器的数学和统计计算方法包,包含大多数常用的数值算法,这个进行专业数学处理时会非常方便。

如:计算方法,方差和一组数的概率统计问题;

一套拟合曲线的数据点应用线性回归问题;

插值问题(可能包括线性、样条等插值);

用来进行参数拟合的最小二乘方法;

求解方程组值问题(例如,求方程组的根);

解决系统的线性方程组;

求解常微分方程;

求最小值问题的方法;

利用Commons Math包产生指定要求的随机数;

生成随机抽样,数据集;

进行统计测试;

繁杂的数学函数,如组二项式系数、特殊函数(gamma, beta functions)

个人认为这个包过于专业,如果用到了,再查询不迟,知道这个包能解决问题就行。

当然,这个包很专业,但是不代表只能解决专业数学问题,处理简单的计算也是很方便的。

 

总结:普通的计算最好使用BigDecimal类,这个类可以非常精确的计算出结果,而且你可以完全控制精度,不用额外其他操作,而且与基本类型转换都非常方便。