为什么 0.1 + 0.2 != 0.3?怎么解决这个问题
为什么
在日常生活中我们习以为常地认为0.1 + 0.2应该等于0.3,但在计算机中却经常会得到不同的结果。
下面这段代码 0.3 与 a+b 比较结果输出的是 false,为什么?
float a = 0.1f;
float b = 0.2f;
System.out.println((0.3 == a+b)); // false
其实是因为计算机存储小数采用的浮点数,浮点数类似科学计数法不同点在于它们的基数不同,例如:
-
二进制数:1000.101 用浮点数表示:1.000101 x 23
-
二进制数:0.00101 用浮点数表示:1.01 x 2-3
-
十进制数:1230000 用科学计数法表示:1.23 x 106
-
十进制数:0.00123 用科学计数法表示:1.23 x 10-3
现在绝大多数计算机使用的浮点数,一般采用的是 IEEE 制定的国际标准,这种标准形式如下图:
我们尝试将 0.1 转为计算机中存储的浮点数(以单精度为例)
用乘2取整法将0.1转为二进制:
可以看到 0.1 转为二进制是无限循环的:0.00011001100…
转为浮点数:1.1001100… x 2-4
转为计算机存储的单精度浮点数
- 符号位:0
- 指数位:(-4+127(偏移量)) 0111 1011
- 尾数位:1001 1001 1001 1001 1001 101
再将浮点数转为十进制数:
尾数位转为十进制:1x2-1+0x2-2… = 0.600000023841858
(-1)符号位 x (1+尾数位) x 2 ^(指数位 - 127)= 1 x 1.600000023841858 x 2-4 = 0.100000001490116125
同理 0.2 = 0.20000000298023224
所以 计算机中 0.1+0.2 是不等于0.3的
解决这个问题
一种是提供一个误差值,如果它们的插值小于这个误差值就认为它们相等
// float
float epsilon = 1e-6f; // 定义一个较小的误差范围 10的负6次方
float a = 0.1f;
float b = 0.2f;
float c = a + b;
if (Math.abs(c - 0.3) < epsilon) {
System.out.println("相等");
} else {
System.out.println("不相等");
}
// double
double a = 0.1;
double b = 0.2;
double c = a + b;
double epsilon = 1e-10; // 定义一个很小的误差范围
if (Math.abs(0.3 - c) < epsilon) {
System.out.println("相等");
} else {
System.out.println("不相等");
}
float
类型的精度比 double
类型低,所以需要选择一个更大的误差范围
一种是使用BigDecimal类:
BigDecimal num1 = new BigDecimal("0.1");
BigDecimal num2 = new BigDecimal("0.2");
BigDecimal sum = num1.add(num2);
BigDecimal num3 = new BigDecimal("0.3");
if (sum.equals(num3)) {
System.out.println("相等");
} else {
System.out.println("不相等");
}
在上述的Java代码中,使用 BigDecimal
类来表示浮点数值时,将浮点数值作为字符串传递给构造函数的原因是为了确保精确性。
Java中的浮点数类型(float
和 double
)使用二进制浮点表示法,这种表示法对于一些十进制分数是无法精确表示的,因为它们需要无限位的二进制小数表示。由于浮点数的精度限制,直接使用浮点数字面量进行构造可能导致舍入误差。
通过将浮点数值作为字符串传递给 BigDecimal
构造函数,可以确保浮点数的精确性,因为 BigDecimal
使用基于十进制的表示法,不会引入与二进制浮点表示法相关的舍入误差。这是在需要高精度计算时常见的做法,尤其是在金融、科学计算和其他需要高精度的领域。
总之,将浮点数作为字符串传递给 BigDecimal
构造函数可以避免由于浮点数精度问题而导致的不精确性。这是一种安全的方式来处理浮点数精度问题。