[英]Why is Java's double/float Math.min() implemented this way?
我在java.lang.Math
的源代码中查看了一些东西,我注意到虽然Math.min(int, int)
(或其长对应物)是以这种方式实现的:
public static int min(int a, int b) {
return a <= b ? a : b;
}
这对我来说完全有意义,这和我会做的一样。 但是,双/浮点实现是这样的:
public static float min(float a, float b) {
if (a != a) {
return a;
} else if (a == 0.0F && b == 0.0F && (long)Float.floatToRawIntBits(b) == negativeZeroFloatBits) {
return b;
} else {
return a <= b ? a : b;
}
}
我完全傻眼了。 和自己a
? 第二次检查是为了什么? 为什么它不以与 int/long 版本相同的方式实现?
浮点数比 integer 值复杂得多。
对于这种特定情况,有两个区别很重要:
NaN
是float
和double
的有效值,它表示“不是数字”并且行为怪异。 即,它不等于自身。所以这部分:
if (a != a) {
return a;
}
确保在a
是NaN
时返回NaN
(如果a
不是NaN
,但b
是,那么稍后的“正常”检查将返回b
,即NaN
,因此这种情况不需要显式检查)。 这是一种常见模式:当计算一个输入为NaN
的任何内容时,output 也将是NaN
。 由于NaN
通常表示计算中的一些错误(例如将 0 除以 0),因此重要的是它会“毒化”所有进一步的计算以确保错误不会被无声地吞噬。
这部分:
if (a == 0.0F && b == 0.0F && (long)Float.floatToRawIntBits(b) == negativeZeroFloatBits) {
return b;
}
确保如果您比较两个零值浮点数并且b
为负零,则返回负零(因为 -0.0“小于”0.0)。 与NaN
类似,如果 a 为 -0.0 且b
为 0.0,则正常检查将正确返回a
。
我建议仔细阅读Math.min
的文档以及浮点数比较运算符。 他们的行为完全不同。
Math.min
的相关部分:
如果任一值为 NaN,则结果为 NaN。 与数值比较运算符不同,此方法认为负零严格小于正零。
并来自 JLS §15.20.1“数值比较运算符 <、<=、> 和 >=”
由 IEEE 754 标准的规范确定的浮点比较的结果是:
如果任一操作数为 NaN,则结果为假。
正零和负零被认为是相等的。
如果任何参数为 NaN, Math.min
将选择该参数,但如果任何操作数为 NaN,则<=
计算结果为false
。 这就是为什么它必须检查a
是否不等于自身 - 这意味着a
是 NaN。 如果a
不是 NaN 但b
是,则最后一种情况将涵盖它。
Math.min
还认为-0.0
是“小于” +0.0
,但数字比较运算符认为它们是相等的。 这是第二次检查的目的。
为了完整/清晰起见,让我们绘制一张所有可能结果的表格:
a
和b
中的任何一个都可以是
为了完整起见,写出所有这些组合,并在某些情况下区分正数和负数以清楚起见,给出了下表中的 20 行,尽管它们中的大多数都很简单且没有问题。
标题为“Correct min”的列是应该根据IEEE 754 标准和Math.min
的 Java 文档返回的正确值,标题为“Naive min”的列是如果 Math.min 将返回的值Math.min
已被实施为return a <= b? a: b;
return a <= b? a: b;
反而。
一个 | b | 正确的最小值 | 天真分钟 | 关于朴素分钟的注意事项 | 天真敏错了? |
---|---|---|---|---|---|
钠 | 钠 | 钠 | 钠 | b,因为 NaN 比较给出错误。 | |
钠 | -0 | 钠 | -0 | b,因为 NaN 比较给出错误。 | 错误的 |
钠 | 0 | 钠 | 0 | b,因为 NaN 比较给出错误。 | 错误的 |
钠 | (其他) | 钠 | (其他) | b,因为 NaN 比较给出错误。 | 错误的 |
-0 | 钠 | 钠 | 钠 | b,因为 NaN 比较给出错误。 | |
-0 | -0 | -0 | -0 | a,为 -0 ≤ -0。 | |
-0 | 0 | -0 | -0 | a,为 -0 ≤ 0。 | |
-0 | (其他>0) | -0 | -0 | ||
-0 | (其他<0) | (其他<0) | (其他<0) | ||
0 | 钠 | 钠 | 钠 | b,因为 NaN 比较给出错误。 | |
0 | -0 | -0 | 0 | a,按照 IEEE 754 为“0 ≤ -0”。 | 错误的 |
0 | 0 | 0 | 0 | a,当 0 ≤ 0。 | |
0 | (其他>0) | 0 | 0 | ||
0 | (其他<0) | (其他<0) | (其他<0) | ||
(其他) | 钠 | 钠 | 钠 | b,因为 NaN 比较给出错误。 | |
(其他<0) | -0 | (其他<0) | (其他<0) | ||
(其他>0) | -0 | -0 | -0 | ||
(其他<0) | 0 | (其他<0) | (其他<0) | ||
(其他>0) | 0 | 0 | 0 | ||
(其他) | (其他) | (其他) | (其他) |
[“正确最小值”和“朴素最小值”最后一行中的“(其他)”表示正确的最小值,在直接意义上,不会因为 NaN 或 -0 而造成任何混淆。]
因此,您会看到上表中有四行,其中天真的 function 会给出错误的答案:
其中三个是当a
是 NaN 而b
不是时的情况。 这就是 function 中的第一个检查的用途。
另一种情况是Math.min(0, -0)
由 Java 记录为返回 -0,即使 IEEE 754 将 0 和 -0 视为相等进行比较(因此比较“0 ≤ -0”评估为真的)。 这就是 function 中的第二次检查的用途。
if (a != a)
,我可以帮助您进行第一次比较。 这显然只看a
,那么在哪些情况下a
可能是最小值而不管b
?
float
与int
的不同之处在于具有特殊值,例如NAN
。 NAN
的一个特殊属性是比较总是错误的。 因此,如果每个比较运算符在a
a
b
的相同条件可以在最后一行中找到。 如果对b
的比较总是返回 false,则最后一行总是返回b
。
在第二个条件下,我只能猜测这与“负零”和“正零”有关,另外两个特殊值float
。 当然,负零小于正零。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.