Java double和float精度损失问题

问题

一般,我们会用float和double来存储有小数的数据,可能还会用两个浮点型数据进行计算,在某些情况下,float和double会出现精度损失,导致存储的数据或者计算结果出现误差。

举个例子

public class Test {

	public static void main(String[] args) {
		double a = 0.11;
		double b = 2.32;
		System.out.println(a + b);
		System.out.println(0.11 + 2.32);
		
		float c = 0.11f;
		float d = 2.32f;
		System.out.println(c + d);
	}
}

执行结果如下:

2.4299999999999997
2.4299999999999997
2.4299998

这个结果并不是我们预期的2.43。很明显,精度损失了,虽然仅比原来小了0.00000000000003,但是如果是我们银行卡里的金额,那可就出大问题了。
经过测试,还发现以下两种情况:

  1. 如果把上面的2.32换成2.02,double类型计算得到正确结果,而float类型依然计算错误:
2.13
2.13
2.1299999
  1. 如果把2.32换成1.02,居然反过来了,float类型计算正确,double类型计算错误:
1.1300000000000001
1.1300000000000001
1.13

气人啊!

原因

计算机用0和1来表示数据,并没有小数,浮点型数据采用IEEE754标准,浮点型数据如何存储可以参考这篇文章
《Effective Java》中写道:

float 和 double 类型主要用于科学计算和工程计算。它们执行二进制浮点运算,该算法经过精心设
计,能够在很大范围内快速提供精确的近似值。但是,它们不能提供准确的结果,也不应该在需要精确
结果的地方使用。float 和 double 类型特别不适合进行货币计算,因为不可能将 0.1(或 10 的任意负次
幂)精确地表示为 float 或 double。

所有的编程语言中应该都有这个问题,而不仅仅是Java。

解决方法

那么,如何解决这个问题呢?
用java.math.BigDecimal,并且根据《阿里Java开发规范》,应该使用BigDecimal(“0.01”)这种方式创建对象,而不能用BigDecimal(0.01)这种方式。

12.【强制】禁止使用构造方法 BigDecimal(double)的方式把 double 值转化为 BigDecimal 对象。
说明:BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。
如:BigDecimal g = new BigDecimal(0.1F); 实际的存储值为:0.10000000149
正例:优先推荐入参为 String 的构造方法,或使用 BigDecimal 的 valueOf 方法,此方法内部其实执行了
Double 的 toString,而 Double 的 toString 按 double 的实际能表达的精度对尾数进行了截断。
BigDecimal recommend1 = new BigDecimal(“0.1”);
BigDecimal recommend2 = BigDecimal.valueOf(0.1)

另外,在数据库中,也应该使用decimal,而不是float和double。

### Java 中 `double` `float` 类型的区别 #### 存储空间与精度差异 在 Java 中,`double` `float` 都用于表示浮点数值,但两者之间存在显著的不同之处。主要体现在存储空间以及所能达到的精度上。 - **float**: 占用 4 字节(32位),提供大约 6 到 7 位有效数字的精度[^1]。 - **double**: 使用 8 字节(64位)来保存数据,能够支持约 15 至 16 位的有效数字。 因此,在涉及高精度计算场景下推荐使用 `double` 而不是 `float` ,以减少舍入误差带来的影响。 #### 定义方式上的不同 当声明一个变量为 `float` 类型时,如果赋给它一个小数,默认情况下会被视为 `double` 型常量,这会导致编译错误除非显式换或添加特定后缀[f/F][^3]: ```java // 错误示范 float num = 1.3; // 正确做法之一:强制类型float correctNum1 = (float) 1.3; // 或者指定为 float 文字量 float correctNum2 = 1.3f; ``` 对于 `double` 来说,则不需要这样的特殊处理,可以直接初始化而无需额外标记[d/D],不过也可以加上作为习惯性的良好实践[^4]: ```java double piApproximation = 3.141592653589793d; ``` #### 输出展示特性 由于内部实现机制的原因,即使同一个原始值被赋予不同的浮点数类型,最终打印出来的结果也可能有所差别。例如下面的例子展示了如何因类型不同而导致显示效果的变化: ```java public class Main { public static void main(String[] args){ float fValue = 3.1415926999999F; double dValueFromFloat = fValue; double dValueDirectlyAssigned = 3.1415926999999; System.out.println("Float value: " + fValue); System.out.println("Double from float conversion: " + dValueFromFloat); System.out.println("Directly assigned double value: " + dValueDirectlyAssigned); } } ``` 这段程序将会输出如下内容: ``` Float value: 3.1415927 Double from float conversion: 3.1415927410125732 Directly assigned double value: 3.1415926999999 ``` 可以看到即使是相同的初始值,经过不同类型间的化之后其表现形式也会有所不同。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值