Java 浮点数看起来很熟悉
在 Java 中,我们有两种类型的浮点数:和 .所有 Java 开发人员都知道它们,但无法回答以下模因中描述的简单问题:float
double
你够机器人吗?
您对 Float 和 Double 了解多少?
float
并表示浮点数。 使用 32 位,而使用 64 位,可用于十进制或小数部分。但这实际上意味着什么呢?为了理解,让我们回顾一下以下示例:double
float
double
直觉结果是完全矛盾的,并且似乎包含错误:
但这怎么可能呢?数字末尾的 4 和 2 从何而来?为了理解,让我们回顾一下这些数字实际上是如何创建的。
不要相信你的眼睛:回顾 0.1 如何转换为 IEEE 标准
float
并遵循 IEEE 标准,该标准定义了如何使用 32/64 位来表示浮点数。但是像 0.1 这样的数字是如何转换为位数组的呢?无需过多深入研究细节,其逻辑类似于以下内容:double
首先将浮点 0.1 转换为位数组
在第一阶段,我们需要使用以下步骤将 0.1 的十进制表示形式转换为二进制:
- 将 0.1 乘以 2 并记下小数部分。
- 取小数部分,乘以 2,然后记下小数部分。
- 使用第二步中的馏分重复第一步。
因此,对于 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 标准。该标准将数字分为三个部分:float
double
float
double
- 符号(0 表示正,1 表示负)
- Exponent (定义浮点的位置,偏移量为 127 或 1023
float
double
) - 尾数(浮点数之后的部分,但受剩余位数的限制)
所以现在转换 0.0001100110011...对于 IEEE,我们得到:
- 符号 0 表示阳性
- 指数 : 考虑到前四个零 0.0001100110011 = -4 + 127 = 123(或 01111011)
- 尾数 1100110011 (尾数忽略前 1),因此我们得到 100110011
所以最终的表示是:
那又怎样?这些数字如何解释奇怪的结果?
在所有这些转换之后,由于两个因素,我们失去了精度:
- 从无限二进制表示形式转换时,我们会失去精度(它具有重复的值,如 1100110011)。
- 转换为 IEEE 格式时,我们会失去精度,因为我们只考虑前 32 位或 64 位。
这意味着我们的值 in 或 not 正好代表 0.1。 如果我们将 IEEE 位数组从 float
转换为 “real float”,我们会得到不同的值。更准确地说,我们得到的是 0.100000001490116119384765625,而不是 0.1。float
double
我们如何验证这一点?有几种方法。请看下面的代码:
正如我们预期的那样,我们得到了以下结果:
但是,如果我们想更深入,我们可以编写逆向工程代码:
正如预期的那样,它证实了我们的想法:
回答问题并得出结论
现在我们知道在初始化时看到的值与实际存储在 / 中的值不同,我们期望左侧的值 (0.1),但相反,我们使用右侧的值 (0.100000001490116119384765625) 进行初始化:float
double
因此,了解了这一点,很明显,当我们执行诸如添加或乘以值之类的操作时,这种差异会变得更加明显,直到在打印过程中变得明显。
以下是我们可以得出的结论:
- 不要使用浮点值进行精确计算,例如在金融、医学或复杂科学中。
- 不要直接比较两个值是否相等;相反,使用较小的 delta 检查它们之间的差异。例如:
double
boolean isEqual = Math.abs(a - b) < 0.0000001;
- 使用 或类似类进行精确计算。
BigDecimal
我希望您现在明白为什么 0.1 + 0.2 会返回 0.300000000000004
标签:Java,二进制,0.1,浮点数,浮点,转换,IEEE,我们,精度 From: https://blog.51cto.com/u_16903194/12108121