Java中的浮点数据(float、double)进行算术运算时出错的问题剖析

本文主题:

  • 对浮点数进行算术运算时,为何运算结果不正确?
  • 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));
	}

}

运行结果如下:

  

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值