Как эффективно сравнить знак двух значений с плавающей точкой при обработке отрицательных нулей - PullRequest
10 голосов
/ 27 мая 2010

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

  2. Эффективное расширение SameSign до трех значений .

Edit:

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

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

  • Наивный вариант: 15 тиков
  • Вариант битовой магии: 13 тиков
  • вариант Стивенса: 6 тиков

Ответы [ 3 ]

14 голосов
/ 27 мая 2010

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

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

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

4 голосов
/ 14 июня 2010

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

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;
}
0 голосов
/ 10 июня 2015

Небольшое примечание о signbit: макрос возвращает целое число, и страница man заявляет, что «возвращает ненулевое значение, если для значения x установлен бит знака». Это означает, что Spudd86 bool same_sign() не гарантированно будет работать, если signbit возвращает два разных ненулевых целых числа для двух разных отрицательных значений.

Сначала приведение к bool обеспечивает правильное возвращаемое значение:

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