为什么 0.1 + 0.2 != 0.3?怎么解决这个问题

为什么 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 制定的国际标准,这种标准形式如下图:

image-20231010115612845

我们尝试将 0.1 转为计算机中存储的浮点数(以单精度为例)

用乘2取整法将0.1转为二进制:

image-20231010120709916

可以看到 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

image-20231010124302172

同理 0.2 = 0.20000000298023224

所以 计算机中 0.1+0.2 是不等于0.3的

image-20231010124536123

解决这个问题

一种是提供一个误差值,如果它们的插值小于这个误差值就认为它们相等

// 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中的浮点数类型(floatdouble)使用二进制浮点表示法,这种表示法对于一些十进制分数是无法精确表示的,因为它们需要无限位的二进制小数表示。由于浮点数的精度限制,直接使用浮点数字面量进行构造可能导致舍入误差。

通过将浮点数值作为字符串传递给 BigDecimal 构造函数,可以确保浮点数的精确性,因为 BigDecimal 使用基于十进制的表示法,不会引入与二进制浮点表示法相关的舍入误差。这是在需要高精度计算时常见的做法,尤其是在金融、科学计算和其他需要高精度的领域。

总之,将浮点数作为字符串传递给 BigDecimal 构造函数可以避免由于浮点数精度问题而导致的不精确性。这是一种安全的方式来处理浮点数精度问题。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值