有效检查两个浮点值是否具有不同的符号
Efficient check that two floating point values have distinct signs
我需要找出两个有限浮点值 A
和 B
是否有不同的符号或其中之一为零。
在很多代码示例中我看到测试如下:
if ( (A <= 0 && B >= 0) || (A >= 0 && B <= 0) )
它工作正常但对我来说看起来效率低下,因为这里验证了许多条件并且每个条件分支对于现代 CPU 来说都是一个缓慢的操作。
另一种选择是使用浮点值的乘法:
if ( A * B <= 0 )
即使在溢出的情况下它也应该工作,因为无穷大值保留适当的符号。
执行检查的最有效方法是什么(这两种方法之一或其他方法)?
It works fine but looks inefficient to me since many conditions are verified here and each condition branch is a slow operation for modern CPUs.
优化编译器不会产生不必要的分支。除非您处于非常紧密的热循环中,否则不要担心过早的优化。在我试过的编译器中,你的第一个片段被编译成 x86-64 和 ARM64 中的 2 个分支。它也可以编译成无分支版本。当然,它可能仍然比简单的 A * B <= 0
慢,但如果没有适当的基准测试
,就无法确定这一点
如果你不关心 NaN 那么你可以简单地做一些按位运算:
auto a = std::bit_cast<uint64_t>(A);
auto b = std::bit_cast<uint64_t>(B);
const auto sign_mask = 1ULL << 63;
return ((a ^ b) & sign_mask) || (((a | b) & ~sign_mask) == 0);
如果 A 和 B 的符号不同,那么它将被 (a ^ b) & sign_mask
匹配。如果它们具有相同的符号,则它们都必须为零,这将被后一种情况捕获。但这适用于整数,因此当将值从 float 移动到 int 域时可能会导致
如果 std::bit_cast
不可用,则只需替换为 memcpy(&a, &A, sizeof A)
再次进行基准测试以确定最适合您的目标。没有一种解决方案在所有可用的微体系结构上都是最快的。如果你真的 运行 在一个循环中很多次那么你应该使用 SIMD instead to check for multiple values at the same time. You should also use profile-guided optimization 以便编译器知道分支的位置和时间
Efficient check that two floating point values have distinct signs
当 A
或 B
属于集合 -0.0, +0.0
. 时,if ( A * B <= 0 )
无法区分符号
if (signbit(A) == signbit(B)) {
; // same sign
} else {
; // distinct signs
}
相信编译器可以生成高效的代码 - 或者使用更好的编译器。
OP 然后形成了一个不同的目标:“ether 两个有限浮点值 A 和 B 具有不同的符号或其中一个为零。”
当然 if (signbit(A) != signbit(B) || A == 0.0 || B == 0.0)
满足这个更新的 功能 而无需四舍五入到 0.0 问题 of if ( A * B <= 0 )
,但可能不满足模糊最有效方式的要求。
形成高效代码并避免premature optimization really the root of all evil的最佳方法是查看更大的上下文。
我需要找出两个有限浮点值 A
和 B
是否有不同的符号或其中之一为零。
在很多代码示例中我看到测试如下:
if ( (A <= 0 && B >= 0) || (A >= 0 && B <= 0) )
它工作正常但对我来说看起来效率低下,因为这里验证了许多条件并且每个条件分支对于现代 CPU 来说都是一个缓慢的操作。
另一种选择是使用浮点值的乘法:
if ( A * B <= 0 )
即使在溢出的情况下它也应该工作,因为无穷大值保留适当的符号。
执行检查的最有效方法是什么(这两种方法之一或其他方法)?
It works fine but looks inefficient to me since many conditions are verified here and each condition branch is a slow operation for modern CPUs.
优化编译器不会产生不必要的分支。除非您处于非常紧密的热循环中,否则不要担心过早的优化。在我试过的编译器中,你的第一个片段被编译成 x86-64 和 ARM64 中的 2 个分支。它也可以编译成无分支版本。当然,它可能仍然比简单的 A * B <= 0
慢,但如果没有适当的基准测试
如果你不关心 NaN 那么你可以简单地做一些按位运算:
auto a = std::bit_cast<uint64_t>(A);
auto b = std::bit_cast<uint64_t>(B);
const auto sign_mask = 1ULL << 63;
return ((a ^ b) & sign_mask) || (((a | b) & ~sign_mask) == 0);
如果 A 和 B 的符号不同,那么它将被 (a ^ b) & sign_mask
匹配。如果它们具有相同的符号,则它们都必须为零,这将被后一种情况捕获。但这适用于整数,因此当将值从 float 移动到 int 域时可能会导致
如果 std::bit_cast
不可用,则只需替换为 memcpy(&a, &A, sizeof A)
再次进行基准测试以确定最适合您的目标。没有一种解决方案在所有可用的微体系结构上都是最快的。如果你真的 运行 在一个循环中很多次那么你应该使用 SIMD instead to check for multiple values at the same time. You should also use profile-guided optimization 以便编译器知道分支的位置和时间
当Efficient check that two floating point values have distinct signs
A
或 B
属于集合 -0.0, +0.0
. 时,if ( A * B <= 0 )
无法区分符号
if (signbit(A) == signbit(B)) {
; // same sign
} else {
; // distinct signs
}
相信编译器可以生成高效的代码 - 或者使用更好的编译器。
OP 然后形成了一个不同的目标:“ether 两个有限浮点值 A 和 B 具有不同的符号或其中一个为零。”
当然 if (signbit(A) != signbit(B) || A == 0.0 || B == 0.0)
满足这个更新的 功能 而无需四舍五入到 0.0 问题 if ( A * B <= 0 )
,但可能不满足模糊最有效方式的要求。
形成高效代码并避免premature optimization really the root of all evil的最佳方法是查看更大的上下文。