Yon*_*har 70 java floating-point
我正在查看 的源代码中的一些内容java.lang.Math
,我注意到 while Math.min(int, int)
(或其长版本)是这样实现的:
public static int min(int a, int b) {
return a <= b ? a : b;
}
Run Code Online (Sandbox Code Playgroud)
这对我来说完全有意义,这和我要做的一样。但是,双/浮点实现是这样的:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
我完全傻眼了。相比a
于自己?第二次检查是为了什么?为什么它的实现方式与 int/long 版本不同?
Joa*_*uer 103
浮点数比整数值复杂得多。
对于这种特定情况,有两个区别很重要:
NaN
是 和 的有效值float
,double
它代表“不是数字”并且表现得很奇怪。也就是说,它不等于它自己。所以这部分:
if (a != a) {
return a;
}
Run Code Online (Sandbox Code Playgroud)
确保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;
}
Run Code Online (Sandbox Code Playgroud)
确保如果您比较两个零值浮点数并且b
是负零,则返回负零(因为 -0.0 “小于”0.0)。与NaN
正常检查类似,a
如果它是 -0.0 并且b
是 0.0,则会正确返回。
Swe*_*per 40
我建议仔细阅读有关浮点数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
,但数字比较运算符认为它们相等。这是第二次检查的目的。
Shr*_*saR 18
为了完整性/清晰性,让我们绘制一个所有可能结果的表格:
无论的a
和b
可以是
为了完整起见,写出这些的所有组合,并在某些情况下为清楚起见区分正数和负数,在下表中给出了 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”评估为真)。这就是函数中的第二个检查的目的。
Mat*_*ias 11
我可以帮你第一次比较if (a != a)
。这显然只看a
,那么在哪些情况下可能a
是最小值而不管b
?
float
数不同于int
由具有特殊值,例如NAN
。的一个特殊属性NAN
是比较总是错误的。因此,a
如果每个比较运算符在 上都返回 false,则第一个条件返回a
。
b
在最后一行可以找到相同的条件。如果对上的比较b
始终返回 false,则最后一行始终返回b
。
在第二个条件下,我只能猜测这与“负零”和“正零”有关,另外两个特殊值float
. 当然,负零小于正零。
归档时间: |
|
查看次数: |
5070 次 |
最近记录: |