如何在处理负零时有效地比较两个浮点值的符号

Fra*_*une 10 floating-point compare sign zero negative-number

给定两个浮点数,我正在寻找一种有效的方法来检查它们是否具有相同的符号,假设如果这两个值中的任何一个为零(+0.0或-0.0),则应认为它们具有相同的符号标志.

例如,

  • SameSign(1.0,2.0)应该返回true
  • SameSign(-1.0,-2.0)应该返回true
  • SameSign(-1.0,2.0)应该返回false
  • SameSign(0.0,1.0)应该返回true
  • SameSign(0.0,-1.0)应该返回true
  • SameSign(-0.0,1.0)应该返回true
  • SameSign(-0.0,-1.0)应该返回true

SameSignC++中一个天真但正确的实现是:

bool SameSign(float a, float b)
{
    if (fabs(a) == 0.0f || fabs(b) == 0.0f)
        return true;

    return (a >= 0.0f) == (b >= 0.0f);
}
Run Code Online (Sandbox Code Playgroud)

假设IEEE浮点模型,这里的变体SameSign编译为无分支代码(至少使用Visual C++ 2008):

bool SameSign(float a, float b)
{
    int ia = binary_cast<int>(a);
    int ib = binary_cast<int>(b);

    int az = (ia & 0x7FFFFFFF) == 0;
    int bz = (ib & 0x7FFFFFFF) == 0;
    int ab = (ia ^ ib) >= 0;

    return (az | bz | ab) != 0;
}
Run Code Online (Sandbox Code Playgroud)

binary_cast定义如下:

template <typename Target, typename Source>
inline Target binary_cast(Source s)
{
    union
    {
        Source  m_source;
        Target  m_target;
    } u;
    u.m_source = s;
    return u.m_target;
}
Run Code Online (Sandbox Code Playgroud)

我正在寻找两件事:

  1. SameSign使用比特技巧,FPU技巧甚至SSE内在函数更快,更有效地实现.

  2. 有效扩展SameSign到三个值.

编辑:

我已经对三个变体SameSign(原始问题中描述的两个变体,加上Stephen的变体)进行了一些性能测量.每个函数运行200-400次,在101个浮点数组中的所有连续值对上随机填充-1.0,-0.0,+ 0.0和+1.0.每次测量重复2000次并保持最小时间(以消除所有高速缓存效应和系统引起的减速).代码是使用Visual C++ 2008 SP1编译的,具有最大优化和启用SSE2代码生成.测量在Core 2 Duo P8600 2.4 Ghz上进行.

下面是时间,不计算从数组中获取输入值的开销,调用函数并检索结果(相当于6-7个时钟信号):

  • 天真变种:15个蜱虫
  • 位神奇变体:13个刻度
  • 斯蒂芬斯的变体:6个蜱虫

Ste*_*non 15

如果您不需要支持无穷大,您可以使用:

inline bool SameSign(float a, float b) {
    return a*b >= 0.0f;
}
Run Code Online (Sandbox Code Playgroud)

这在大多数现代硬件上实际上非常快,并且完全可移植.然而,它在(零,无穷大)情况下无法正常工作,因为零*无穷大是NaN,并且无论符号如何,比较都将返回false.当a和b都很小时,它也会在某些硬件上产生非正常停顿.

  • 这是准确的吗?对我来说,这将是显而易见的解决方案,但无论舍入误差如何,我也需要得到准确的结果。在我看来,a*b 可能向上舍入为 0,然后该函数计算出错误的值。但不确定。 (2认同)

Spu*_*d86 5

也许是这样的:

inline bool same_sign(float a, float b) {
    return copysignf(a,b) == a;
}
Run Code Online (Sandbox Code Playgroud)

请参阅 copysign 的手册页以获取有关其功能的更多信息(您也可能需要检查 -0 != +0)

或者如果您有 C99 函数,则可能是这样

inline bool same_sign(float a, float b) {
    return signbitf(a) == signbitf(b);
}
Run Code Online (Sandbox Code Playgroud)

作为旁注,在 gcc 上,至少 copysign 和 signbit 都是内置函数,因此它们应该很快,如果您想确保使用内置版本,您可以执行 __builtin_signbitf(a)

编辑:这也应该很容易扩展到 3 值情况(实际上这两个都应该......)

inline bool same_sign(float a, float b, float c) {
    return copysignf(a,b) == a && copysignf(a,c) == a;
}

// trust the compiler to do common sub-expression elimination
inline bool same_sign(float a, float b, float c) {
    return signbitf(a) == signbitf(b) && signbitf(a) == signbitf(c);
}

// the manpages do not say that signbit returns 1 for negative... however
// if it does this should be good, (no branches for one thing...)
inline bool same_sign(float a, float b, float c) {
    int s = signbitf(a) + signbitf(b) + signbitf(c);
    return !s || s==3;
}
Run Code Online (Sandbox Code Playgroud)