天天记录 - Java 精确计算避免使用float和double


一  问题描述

    float和double类型不能用于精确计算,其主要目的是为了科学计算和工程计算,它们执行二进制浮点原酸,目的是为了广泛的数值范围上提供较为精确的快速近似计算而精心设计的。但是如果设计钱币之类的计算需要很精确,所以这种情况不能使用float和double,因为要让其精确表示0.1 或者 10的任何负数次方值是不可能的。


二 眼见为实,举例证明:

		// float与double 无法精确表示0.1 或者 10的负次方
		System.out.println( 0.15 - 0.05 ); // 0.09999999999999999
		System.out.println( 2.08f - 3.7f );// -1.6200001


三 举一个现实中会遇到的情况:

     一个实际的例子,口袋里面有10元钱,买0.9元的商品十个 , 收银员需要找零1元。计算如下:

10 - 0.9 * 10 = 1
     代码实现:

		float myMoney = 10.00f;
		float price = 0.9f;
		for (int i = 0; i < 10; i++) {
			myMoney -= price;
		}
		System.out.println( myMoney ); // 1.0000002

    结果竟然输出的是1.0000002而非1,可能很多人会认为这也无所谓啊,毕竟仅偏差了0.0000002,但问题是如果前提是进行了1亿笔交易呢?难道你愿意仅仅因为使用了计算机计算这些数据,而损失一部分钱吗?



四 解决办法

1. 使用int 和 long类型,如果小数点以后的值可以忽略不计可以只用这两种类型

2. 对计算结果进行四舍五入

3. 使用BigDecimal类型进行浮点运算


先来看一段代码

		int intValue = 10;
		float floatValue = 0.99999f;
		
		int count1 = (int) (intValue + floatValue);
		System.out.println("10 + 0.99999 = " +  count1); // output 10
		
		int count2 = (int) (intValue - floatValue);
		System.out.println("10 - 0.99999 = " +  count2); // output 9

原本按我个人的理解 10 + 0.99999 = 10.99999 然后转为int类型会进行四舍五入 值为11,而实际结果为10



更深入的分析可以查看


如何解决float和double误差问题呢?可以使用Java中提供的java.math.BigDecimal,不过此类型是不可变的,每次计算都必须新创建一个对象,不适合大量计算


再来看一个舍入误差会出现的问题

                // 10.1 + 0.99*10 = 20
		float f1 = 10.1f;
		float f2 = 0.99f;
		for (int i = 0; i < 10; i++) {
			f1 += f2;
		}
		System.out.println(f1); // 19.999998

解决办法,使用BigDecimal


		BigDecimal count = new BigDecimal("10.1");
		for (int i = 0; i < 10; i++) {
			count = count.add(new BigDecimal("1"));
		}
		
		System.out.println(count.intValue()); // 20





参考资料:

1. Java 理论与实践: 您的小数点到哪里去了?

2. 《Effective Java》 第2版 第48条 如果需要精确的答案,请避免使用float和double



你可能感兴趣的:(天天记录 - Java 精确计算避免使用float和double)