首页 > 编程语言 >了解 Java 中的浮点精度问题

了解 Java 中的浮点精度问题

时间:2024-09-25 11:55:34浏览次数:8  
标签:Java 二进制 0.1 浮点数 浮点 转换 IEEE 我们 精度


Java 浮点数看起来很熟悉

在 Java 中,我们有两种类型的浮点数:和 .所有 Java 开发人员都知道它们,但无法回答以下模因中描述的简单问题:floatdouble

了解 Java 中的浮点精度问题_浮点

你够机器人吗?

您对 Float 和 Double 了解多少?

float并表示浮点数。 使用 32 位,而使用 64 位,可用于十进制或小数部分。但这实际上意味着什么呢?为了理解,让我们回顾一下以下示例:doublefloatdouble

了解 Java 中的浮点精度问题_浮点数_02

直觉结果是完全矛盾的,并且似乎包含错误:

了解 Java 中的浮点精度问题_Java_03

但这怎么可能呢?数字末尾的 4 和 2 从何而来?为了理解,让我们回顾一下这些数字实际上是如何创建的。

不要相信你的眼睛:回顾 0.1 如何转换为 IEEE 标准

float并遵循 IEEE 标准,该标准定义了如何使用 32/64 位来表示浮点数。但是像 0.1 这样的数字是如何转换为位数组的呢?无需过多深入研究细节,其逻辑类似于以下内容:double

首先将浮点 0.1 转换为位数组

在第一阶段,我们需要使用以下步骤将 0.1 的十进制表示形式转换为二进制:

  1. 将 0.1 乘以 2 并记下小数部分。
  2. 取小数部分,乘以 2,然后记下小数部分。
  3. 使用第二步中的馏分重复第一步。

因此,对于 0.1,我们得到以下结果:

操作

整数部分

分数

1

0.1 * 2

0

0.2

2

0.2 * 2

0

0.4

3

0.4 * 2

0

0.8

4

0.8 * 2

1

0.6

5

0.6 * 2

1

0.2

6

0.2 * 2

0

0.4

重复这些步骤后,我们得到一个二进制序列,如 0.0001100110011(实际上,它是一个重复的无限序列)。

将二进制数组转换为 IEEE 标准

在 / 中,我们不会保持二进制数组的原样。/ 遵循 IEEE 754 标准。该标准将数字分为三个部分:floatdoublefloatdouble

  1. 符号(0 表示正,1 表示负)
  2. Exponent (定义浮点的位置,偏移量为 127 或 1023floatdouble)
  3. 尾数(浮点数之后的部分,但受剩余位数的限制)

所以现在转换 0.0001100110011...对于 IEEE,我们得到:

  • 符号 0 表示阳性
  • 指数 : 考虑到前四个零 0.0001100110011 = -4 + 127 = 123(或 01111011)
  • 尾数 1100110011 (尾数忽略前 1),因此我们得到 100110011

所以最终的表示是:

了解 Java 中的浮点精度问题_Java_04

那又怎样?这些数字如何解释奇怪的结果?

在所有这些转换之后,由于两个因素,我们失去了精度:

  • 从无限二进制表示形式转换时,我们会失去精度(它具有重复的值,如 1100110011)。
  • 转换为 IEEE 格式时,我们会失去精度,因为我们只考虑前 32 位或 64 位。

这意味着我们的值 in 或 not 正好代表 0.1 如果我们将 IEEE 位数组从 float 转换为 “real float”,我们会得到不同的值。更准确地说,我们得到的是 0.100000001490116119384765625,而不是 0.1。floatdouble 

我们如何验证这一点?有几种方法。请看下面的代码:

了解 Java 中的浮点精度问题_Java_05

正如我们预期的那样,我们得到了以下结果:

了解 Java 中的浮点精度问题_浮点数_06

但是,如果我们想更深入,我们可以编写逆向工程代码:

了解 Java 中的浮点精度问题_浮点_07

正如预期的那样,它证实了我们的想法:

了解 Java 中的浮点精度问题_浮点数_08

回答问题并得出结论

现在我们知道在初始化时看到的值与实际存储在 / 中的值不同,我们期望左侧的值 (0.1),但相反,我们使用右侧的值 (0.100000001490116119384765625) 进行初始化:floatdouble

了解 Java 中的浮点精度问题_Java_09

因此,了解了这一点,很明显,当我们执行诸如添加或乘以值之类的操作时,这种差异会变得更加明显,直到在打印过程中变得明显。

以下是我们可以得出的结论:

  1. 不要使用浮点值进行精确计算,例如在金融、医学或复杂科学中。
  2. 不要直接比较两个值是否相等;相反,使用较小的 delta 检查它们之间的差异。例如:doubleboolean isEqual = Math.abs(a - b) < 0.0000001;
  3. 使用 或类似类进行精确计算。BigDecimal

我希望您现在明白为什么 0.1 + 0.2 会返回 0.300000000000004

标签:Java,二进制,0.1,浮点数,浮点,转换,IEEE,我们,精度
From: https://blog.51cto.com/u_16903194/12108121

相关文章