快速比较IEEE浮点数是否大于零的“作弊”方法

3

我正在处理一个平台,在比较浮点数和零时会出现可怕的停顿。作为优化,我看到了以下代码的使用:

inline bool GreaterThanZero( float value )
{
   const int value_as_int = *(int*)&value;
   return ( value_as_int > 0 );
}

看生成的汇编代码,停顿已经消失了,函数性能更好了。
这样做有用吗?我很困惑,因为所有针对IEEE技巧的优化都使用SIGNMASKS和大量的AND/OR操作(例如:https://www.lomont.org/papers/2005/CompareFloat.pdf)。将其强制转换为有符号整数是否有帮助?在简单的测试中没有发现问题。
任何见解都会很好。

1
基本上,诀窍在于检查高位比特。在二进制补码表示中,非负整数的高位比特为0,负整数的高位比特为1。巧合的是,在IEEE浮点表示中,同一比特也用作符号位。 - Igor Tandetnik
它能工作,但编译器对于像 f > 0 这样简单的东西真的不会做最优化处理吗? - Barry
使用问题中的代码,一个安静的NAN将被视为> 0...有点可疑,但对于许多应用程序可能是可以接受的。在wikipedia上有许多关于位布局的细节。 - Tony Delroy
忽略严格别名的问题,这个几乎可以工作。你实现了比较 !(x <= 0) 而不是 x > 0 - tmyklebu
1
@MarkRansom:它们不是。如果至少有一个参数是NaN,则<<=>>===始终返回false。(实际上,再思考十秒钟,他也没有实现比较!(x <= 0)。对于有限的x,它是x> 0,对于某些NaN是true,但对于其他NaN则不是。 - tmyklebu
显示剩余6条评论
1个回答

8
表达式*(int*)&value > 0测试value是否为任何正浮点数,从最小的正非规格化数(其表示与0x00000001相同)到最大的有限浮点数(其表示为0x7f7fffff)和+inf(其表示与0x7f800000相同)。这种技巧可以检测出许多NaN表示中的正数,但不是全部(NaN表示在0x7f800001以上)。如果您不关心一些值使测试为真,则无妨。
这一切都因为IEEE 754格式的表示而起作用。
你在文献中看到的位运算函数旨在模拟 IEEE 754 操作,可能会追求完美的仿真,考虑到 NaN 和有符号零的特定行为。例如,变化 *(int*)&value >= 0 不等同于 value >= 0.0f,因为表示为无符号整数的 -0.0f(即0x80000000)也表示为带符号整数的 -0x80000000,使得后者条件为 true,而前者条件为 false。这可能使这样的函数非常复杂。

将其转换为有符号 int 是否有帮助?

嗯,是的,因为 floatint 的符号位在同一位置,并且当未设置时都表示正数。但是,条件 value > 0.0f 也可以通过重新解释 value 为无符号整数来实现。
注意:将value的地址转换为int*会违反严格别名规则,但如果您的编译器保证使用这些程序具有意义(可能需要命令行选项),则可以接受此操作。

转换为 unsigned int 并检查是否小于 0x80000001u 怎么样?此外,我认为更清晰、更高效的方法是让一个函数接收一个既有 float 成员又有 int 成员的 union,并使用一个宏来强制转换浮点操作数并调用该函数,因为在许多平台上,float 参数是通过 FPU 寄存器传递的,但这里需要将值作为整数传递。 - supercat
@supercat,我不明白“将其转换为无符号整数并检查是否小于0x80000001u”如何接近于将使f> 0成立的浮点数与那些不成立的浮点数分开。 - Pascal Cuoq
它会将负零视为大于等于零;是否也会捕捉NaN值取决于平台,但我认为在许多情况下,负零会带来更大的问题。 - supercat
@supercat,原始问题是关于 f > 0 的。我在回答中提到 f >= 0 只是一个备注。 - Pascal Cuoq
1
明白了。对于严格大于零的测试,怎么样使用 ((unsigned)i-1u) < (0x7F800000-1u))?我认为这将把所有负数以及两个零和所有NaN都视为不大于零。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接