为什么Java的双/浮点Math.min()以这种方式实现?

2022-08-31 14:06:02

我正在查看 源代码中的一些内容,我注意到虽然(或其长对应物)是这样实现的:java.lang.MathMath.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;
   }
}

我完全傻眼了。与自身相比?第二次检查甚至是什么?为什么它没有以与int/long版本相同的方式实现?a


答案 1

浮点数比整数值复杂得多。

对于此特定情况,两个区别很重要:

  • NaN是 的有效值,表示“不是数字”,并且行为怪异。也就是说,它不与自身进行比较。floatdouble
  • 浮点数可以区分 0.0 和 -0.0。在计算某个函数的极限时,负零可能很有用。区分极限是从正方向还是负方向接近 0 可能是有益的。

所以这部分:

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

确保如果 是 返回 (如果不是 ,但是,则稍后的“正常”检查将返回 ,即 ,因此在这种情况下不需要显式检查)。这是一种常见的模式:当计算任何输入是的地方时,输出也将是 。由于通常表示计算中的一些错误(例如将0除以0),因此重要的是它“毒害”了所有进一步的计算,以确保错误不会被默默地吞噬。NaNaNaNaNaNbbNaNNaNNaNNaN

这部分:

if (a == 0.0F && b == 0.0F && (long)Float.floatToRawIntBits(b) == negativeZeroFloatBits) {
      return b;
}

确保如果比较两个零值浮点数且为负零,则返回负零(因为 -0.0 比 0.0“小”)。与正常检查类似,如果它是 -0.0 并且是 0.0,则将正确返回。bNaNab


答案 2

我建议仔细阅读 Math.min 的文档以及浮点数上的数值比较运算符。他们的行为是完全不同的。

相关零件来自:Math.min

如果任一值为 NaN,则结果为 NaN。与数值比较运算符不同,此方法认为负零严格小于正零。

摘自 JLS §15.20.1 “数值比较运算符<、<=、> 和 >=”

由 IEEE 754 标准规范确定的浮点比较结果为:

  • 如果任一操作数为 NaN,则结果为假。

  • 正零和负零被视为相等。

如果任何参数是 NaN,则选取该参数,但如果任何操作数为 NaN,则计算结果为 。这就是为什么它必须检查是否等于自身 - 这意味着是NaN。如果不是 NaN 而是,则最后一种情况将涵盖它。Math.min<=falseaaab

Math.min也认为是“小于”,但数值比较运算符认为它们是相等的。这是第二次检查的目的。-0.0+0.0