为什么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是 和 的有效值floatdouble它代表“不是数字”并且表现得很奇怪。也就是说,它不等于它自己。
  • 浮点数可以区分 0.0 和 -0.0。当您计算某个函数的极限时,负零可能很有用。区分极限是从正方向还是从负方向接近 0 可能是有益的。

所以这部分:

if (a != a) {
      return a;
}

确保NaN如果aNaN(如果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,但数字比较运算符认为它们相等。这是第二次检查的目的。


回答

为了完整性/清晰性,让我们绘制一个所有可能结果的表格:

  • 无论的ab可以是

    • 南,
    • ?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.

以上是为什么Java的double/floatMath.min()是这样实现的?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>