本文主题:
- 对浮点数进行算术运算时,为何运算结果不正确?
- BigDecimal类型、常用方法的讲解。
- 简单的浮点数算术运算工具类的设计。
在Java前面讲解float、double两种基本浮点类型时已经指出,这两个基本类型的浮点数容易引起精度丢失。其实,不仅是Java,很多编程语言也存在这个问题。先看如下程序:
public class DoubleTest {
public static void main(String[] args) {
System.out.println("以下为double数据类型的运算结果:");
System.out.println("0.05 + 0.01 = " + (0.05+0.01));
System.out.println("1.00 - 0.42 = " + (1.0-0.42));
System.out.println("4.015 * 100 = " + (4.015*100));
System.out.println("123.3 / 100 = " + (123.3/100));
}
}
程序运行结果为:
上面的程序运行结果表明,Java的double类型会发生精度丢失,尤其在进行算术运算时更容易发生这种情况。为了能精确表示、计算浮点数,Java提供了BigDecimal类,该类提供了大量的构造器用于创建BigDecimal对象,包括把所有的基本数值型变量转换成一个BigDecimal对象,也包括利用数字字符串、数字字符数组来创建BigDecimal对象。
查看BigDecimal类的BigDecimal(double val)构造器的详细说明时,可以看到不推荐使用该构造器的说明,主要是因为使用该构造器时有一定的不可预知性。当程序使用BigDecmal bd = new BigDecimal(0.1); 来创建一个BigDecimal对象时,参数的值并不一定是0.1,它实际上是一个近似0.1的数。这是因为double类型无法准确/精确的表示出0.1这个浮点数,所以传入BigDecimal构造器的值不会正好等于0.1(虽然表面上看貌似等于该值)。
而如果使用BigDecimal(String val)构造器来创建一个对象,那么它的结果是可预知的----写入BigDecmal bd = new BigDecimal("0.1"); 将创建一个BigDecimal,它正好等于预期的0.1。因此通常建议优先使用基于String的构造器。
如果必须使用double浮点数作为BigDecimal构造器的参数时,不要直接将该double浮点数作为构造器参数创建BigDecimal对象,而是应该通过BigDecmal bd = BigDecimal.valueOf(double val); 这一静态方法来创建BigDecimal对象。
BigDecimal类提供了add()、substract()、multiply()、divide()、pow()等方法对精确浮点数进行常规算术运算。下面的程序示范了BigDecimal的基本运算。
import java.math.BigDecimal;
public class BigDecimalDemo {
public static void main(String[] args) {
// TODO 自动生成的方法存根
BigDecimal f1 = new BigDecimal("0.05");
BigDecimal f2 = new BigDecimal(0.01);
BigDecimal f3 = BigDecimal.valueOf(0.01);// BigDecimal.valueOf(0.01);
System.out.println("使用String作为BigDecimal构造器参数:");
System.out.println("0.05 + 0.01 = "+f1.add(f3));
System.out.println("0.05 - 0.01 = "+f1.subtract(f3));
System.out.println("0.05 * 0.01 = "+f1.multiply(f3));
System.out.println("0.05 / 0.01 = "+f1.divide(f3));
System.out.println("使用double作为BigDecimal构造器参数:");
System.out.println("0.05 + 0.01 = "+f2.add(f3));
System.out.println("0.05 - 0.01 = "+f2.subtract(f3));
System.out.println("0.05 * 0.01 = "+f2.multiply(f3));
System.out.println("0.05 / 0.01 = "+f2.divide(f3));
}
}
程序运行结果为:
从上面的运行结果可以看出BigDecimal进行算术运算的效果,而且可以看出创建BigDecimal对象时,一定要使用String对象作为构造器的参数,而不是直接使用double数字。
如果我们经常要进行一些浮点数的算术运算,可以考虑以BigDecimal为基础定义一个算术运算的工具类----Arith,该工具的代码如下。
/**
* 文件名:Arith.java
* 功能描述:Arith工具类,是一个无法实例化的类,它只有静态方法
*/
import java.math.BigDecimal;
public class Arith {
// 默认除法运算精度
private static final int DEF_DIV_SCALE = 10;
// 构造器私有,让这个类不能实例化
private Arith() {
}
// 提供精确的加法运算
public static double add(double v1,double v2) {
BigDecimal b1 = BigDecimal.valueOf(v1);
BigDecimal b2 = BigDecimal.valueOf(v2);
return b1.add(b2).doubleValue();
}
// 提供精确的减法运算
public static double substract(double v1,double v2) {
BigDecimal b1 = BigDecimal.valueOf(v1);
BigDecimal b2 = BigDecimal.valueOf(v2);
return b1.subtract(b2).doubleValue();
}
// 提供精确的乘法运算
public static double multiply(double v1,double v2) {
BigDecimal b1 = BigDecimal.valueOf(v1);
BigDecimal b2 = BigDecimal.valueOf(v2);
return b1.multiply(b2).doubleValue();
}
// 提供精确的除法运算
public static double divide(double v1,double v2) {
BigDecimal b1 = BigDecimal.valueOf(v1);
BigDecimal b2 = BigDecimal.valueOf(v2);
return b1.divide(b2).doubleValue();
}
}
ArithTest.java测试文件:
/**
* 文件名:ArithTest.java
* 功能描述:Arith工具测试
*/
public class ArithTest {
public static void main(String[] args) {
System.out.println("使用Arith工具类进行浮点数的算术运算:");
System.out.println("0.05 + 0.01 = "+Arith.add(0.05,0.01));
System.out.println("0.05 - 0.01 = "+Arith.substract(0.05,0.01));
System.out.println("4.015 * 100 = "+Arith.multiply(4.015,100));
System.out.println("123.5 / 100 = "+Arith.divide(123.5,100));
}
}
运行结果如下: