为什么Java的double/floatMath.min()是这样实现的?
我正在查看 的源代码中的一些内容java.lang.Math,我注意到 while 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 版本不同?
回答
浮点数比整数值复杂得多。
对于这种特定情况,有两个区别很重要:
NaN是 和 的有效值float,double它代表“不是数字”并且表现得很奇怪。也就是说,它不等于它自己。- 浮点数可以区分 0.0 和 -0.0。当您计算某个函数的极限时,负零可能很有用。区分极限是从正方向还是从负方向接近 0 可能是有益的。
所以这部分:
if (a != a) {
return a;
}
确保NaN如果a是NaN(如果a不是NaN,但是b是,则稍后的“正常”检查将返回b,即NaN,因此在这种情况下不需要显式检查)。这是一种常见的模式:当计算任何输入为 时NaN,输出也将为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,则会正确返回。
- Might want to note that if it's `b` that's a NaN, the exact shape of the conditional in the last branch (`a <= b ? a : b`) matters. `b` has to be on the falsy side to be returned for a NaN there. Similarly if `a = -0`, and `b = +0`, the last conditional compares equal and returns `a`. Both together mean that only those two explicit tests are needed, not the full four.
- +1 but nitpick: "some error in the calculation (such as dividing a value by 0)". 0/0 is NaN, x/0 for other numbers is usually +/- Infinity.
- @EricDuminil: yes, it's a bit of an oversimplification, as ikkachu noted. I'll edit.
- I think `a == 0.0F && b == 0.0F` can be optimized to `a == b`. Maybe the idea is that `Float.floatToRawIntBits` is expensive, so they're trying to short-circuit it away in more cases? Perhaps `a==b && b == 0.0F`, then, to maybe avoid needing a constant in the common not-equal case. If that `==` is false, then on machines like x86, FLAGS will already be set according to an `a compare b` when the ternary is reached. (Which isn't helpful if you're going to use a branchless `minss` instruction for that...)
回答
我建议仔细阅读有关浮点数Math.min的数字比较运算符的文档。他们的行为完全不同。
相关部分来自Math.min:
如果任一值为 NaN,则结果为 NaN。与数值比较运算符不同,此方法认为负零严格小于正零。
来自 JLS §15.20.1“数值比较运算符 <、<=、> 和 >=”
由 IEEE 754 标准的规范确定的浮点比较的结果是:
如果任一操作数为 NaN,则结果为假。
正零和负零被认为是相等的。
如果任何参数为 NaN,则Math.min选择该参数,但如果任何操作数为 NaN,则<=计算结果为false。这就是为什么它必须检查是否a不等于自身 - 这意味着a是 NaN。如果a不是 NaN 而是 NaN b,则最后一种情况将涵盖它。
Math.min也认为-0.0是 "less than" +0.0,但数字比较运算符认为它们相等。这是第二次检查的目的。
回答
为了完整性/清晰性,让我们绘制一个所有可能结果的表格:
-
无论的
a和b可以是- 南,
- ?0 ,
- 0(即+0),或
- 其他一些非 NaN 非零值,标记为“(其他)”。
为了完整起见,写出这些的所有组合,并在某些情况下为清楚起见区分正数和负数,在下表中给出了 20 行,尽管它们中的大多数都很简单且没有问题。
-
题为“正确分钟”一栏是应该根据返回的正确值
IEEE 754标准
和Java文档Math.min,以及名为“天真分钟”栏目是,本来如果返回的值Math.min已经实现return a <= b ? a : b;,而不是.
| 一种 | 乙 | 正确的分钟 | 天真的分钟 | naive min 的注意事项 | 天真敏错了吗? |
|---|---|---|---|---|---|
| NaN | NaN | NaN | NaN | b,因为 NaN 比较给出错误。 | |
| NaN | ?0 | NaN | ?0 | b,因为 NaN 比较给出错误。 | 错误的 |
| NaN | 0 | NaN | 0 | b,因为 NaN 比较给出错误。 | 错误的 |
| NaN | (其他) | NaN | (其他) | b,因为 NaN 比较给出错误。 | 错误的 |
| ?0 | NaN | NaN | NaN | 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 | NaN | NaN | NaN | 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) | ||
| (其他) | NaN | NaN | NaN | b,因为 NaN 比较给出错误。 | |
| (其他<0) | ?0 | (其他<0) | (其他<0) | ||
| (其他>0) | ?0 | ?0 | ?0 | ||
| (其他<0) | 0 | (其他<0) | (其他<0) | ||
| (其他>0) | 0 | 0 | 0 | ||
| (其他) | (其他) | (其他) | (其他) |
[“Correct min”和“Naive min”的最后一行中的“(other)”表示正确的最小值,直截了当,不会因为 NaN 或 ?0 而混淆。]
所以你会看到上表中有四行,naive 函数会给出错误的答案:
-
其中三个
a是 NaN时的情况,但b不是。这就是函数中的第一个检查的目的。 -
另一种情况
Math.min(0, -0)是 Java 记录为返回 ?0 的情况,即使 IEEE 754 将 0 和 ?0 视为相等进行比较(因此比较“0 ? ?0”评估为真)。这就是函数中的第二个检查的目的。
回答
我可以帮你第一次比较if (a != a)。这显然只看a,那么在哪些情况下可能a是最小值而不管b?
float数不同于int由具有特殊值,例如NAN。的一个特殊属性NAN是比较总是错误的。因此,a如果每个比较运算符在 上都返回 false,则第一个条件返回a。
b在最后一行可以找到相同的条件。如果对上的比较b始终返回 false,则最后一行始终返回b。
在第二个条件下,我只能猜测这与“负零”和“正零”有关,另外两个特殊值float. 当然,负零小于正零。
-
I started writing the answer before the others appeared. So I only saw them after posting my answer.
Is this the reason why this answer got a downvote? - some people seem to downvote late or vague answers, at least that has been my experience
- Why did you enter an answer with guessing after two answers provided the same information without guessing?
- I appreciate you mentioning how b is checked for NaN.