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

Posted

技术标签:

【中文标题】如何在处理负零时有效地比较两个浮点值的符号【英文标题】:How to efficiently compare the sign of two floating-point values while handling negative zeros 【发布时间】:2011-02-24 17:49:25 【问题描述】:

给定两个浮点数,我正在寻找一种有效的方法来检查它们是否具有相同的符号,如果两个值中的任何一个为零(+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

在 C++ 中 SameSign 的简单但正确的实现是:

bool SameSign(float a, float b)

    if (fabs(a) == 0.0f || fabs(b) == 0.0f)
        return true;

    return (a >= 0.0f) == (b >= 0.0f);

假设是 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;

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;

我正在寻找两件事:

    使用位技巧、FPU 技巧甚至 SSE 内在函数更快、更高效地实现 SameSign

    SameSign 到三个值的有效扩展

编辑:

我对@9​​87654330@ 的三个变体(原始问题中描述的两个变体,加上斯蒂芬的一个变体)进行了一些性能测量。每个函数运行 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 滴答声

【问题讨论】:

任何特定的语言/平台? 嘿,谢谢你的好问题 :) 最好是 x86 上的 C/C++。 comparing two floats to see if they're both negative, or both positive. 的可能重复项 【参考方案1】:

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

inline bool SameSign(float a, float b) 
    return a*b >= 0.0f;

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

【讨论】:

确实,这适用于两个值,并且具有正确的语义。我唯一担心的是,对于三个值的情况,它需要三个乘法(a * b >= 0.0f && a * c >= 0.0f && b * c >= 0.0f)。 @François:是的,三值案例是一个有趣的谜题。我得考虑一下。 准确吗?对我来说,这将是显而易见的解决方案,但无论舍入误差如何,我都需要得到准确的结果。在我看来,a*b 可能向上舍入为 0,然后此函数计算出错误的值。不过不确定。 知道了。这并不准确。 ab 的符号总是正确的,但由于结果可能会舍入为 -0,因此比较 >= 0 将返回 true,这将是错误的结果。请注意,我不是在谈论 SameSign(-0, 1),而是在谈论 ab 被舍入到 -0。让我提出另一个答案。 微优化,使用-O3编译。内联 bool SameSign(const float& a, const& float b) return !(a*b 【参考方案2】:

可能是这样的:

inline bool same_sign(float a, float b) 
    return copysignf(a,b) == a;

有关其作用的更多信息,请参见 copysign 的手册页(您也可能需要检查 -0 != +0)

如果你有 C99 函数,也可以这样做

inline bool same_sign(float a, float b) 
    return signbitf(a) == signbitf(b);

附带说明,在 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;

【讨论】:

【参考方案3】:

关于符号位的一个小说明:宏返回一个 int 并且手册页声明“如果 x 的值设置了符号位,它返回一个非零值。”这意味着 Spudd86 的 bool same_sign() 不能保证在符号位为两个不同的负值返回两个不同的非零整数的情况下工作。

首先转换为 bool 可确保返回值正确:

inline bool same_sign(float a, float b) 
    return (bool)signbitf(a) == (bool)signbitf(b);

【讨论】:

以上是关于如何在处理负零时有效地比较两个浮点值的符号的主要内容,如果未能解决你的问题,请参考以下文章

如何有效地逐项比较两个大 XML 文件?

如何在Micropython汇编语言中测试浮点寄存器的符号

如何将两个32位整数组合成一个64位整数?

如何仅针对键的子集有效地比较 C++ 中的两个字符串映射

如何有效地比较同一类的两个对象并检查哪些是不同的字段?

IEEE754二进制浮点数算术标准