Как эффективно сравнить знак двух значений с плавающей точкой при обработке отрицательных нулей

Учитывая два числа с плавающей запятой, я ищу эффективный способ проверить, есть ли у них тот же знак, учитывая, что, если любое из двух значений является нулем (+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

Наивная, но корректная реализация SameSign в C++ был бы:

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;
}

Я ищу две вещи:

  1. Более быстрое, более эффективное внедрение SameSign, с помощью разрядных приемов, приемов FPU или даже SSE intrinsics.

  2. Эффективное расширение SameSign к трем значениям.

Править:

Я сделал некоторые измерения производительности на трех вариантах SameSign (эти два варианта описаны в исходном вопросе плюс Stephen один). Каждая функция была выполнена 200-400 раз, на всех последовательных парах значений в массиве 101 плавания, заполненного наугад-1.0,-0.0, +0.0 и +1.0. Каждое измерение было повторено времена, 2000 года и минимальное время были сохранены (для избавлений от всех эффектов кэша и вызванного системой замедления). Код был скомпилирован с Visual C++, который включили 2 008 SP1 с максимальной оптимизацией и генерацией кода SSE2. Измерения были сделаны на Core 2 Duo P8600 2.4 Ghz.

Вот синхронизации, не считая издержки выборки входных значений от массива, вызывание функции и получение результата (которые составляют 6-7 тактов системных часов):

  • Наивный вариант: 15 галочек
  • Разрядный волшебный вариант: 13 галочек
  • Вариант Stephens: 6 галочек
10
задан CharlesB 4 May 2012 в 23:22
поделиться

2 ответа

Если вам не нужна поддержка бесконечностей, вы можете просто использовать:

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

, что на самом деле довольно быстро на большинстве современного оборудования и полностью переносимо. Однако это не работает должным образом в случае (ноль, бесконечность), потому что ноль * бесконечность - это NaN, и сравнение вернет false, независимо от знаков. Это также приведет к ненормальной остановке на некотором оборудовании, когда a и b оба крошечные.

15
ответ дан 3 December 2019 в 20:02
поделиться

, возможно, что-то вроде:

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

см. Справочную страницу для копирования для получения дополнительной информации о что он делает (также вы можете проверить, что -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;
}
4
ответ дан 3 December 2019 в 20:02
поделиться
Другие вопросы по тегам:

Похожие вопросы: