Учитывая два числа с плавающей запятой, я ищу эффективный способ проверить, есть ли у них тот же знак, учитывая, что, если любое из двух значений является нулем (+0.0 или-0.0), у них, как должны полагать, есть тот же знак.
Например,
Наивная, но корректная реализация 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;
}
Я ищу две вещи:
Более быстрое, более эффективное внедрение SameSign
, с помощью разрядных приемов, приемов FPU или даже SSE intrinsics.
Эффективное расширение 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 тактов системных часов):
Если вам не нужна поддержка бесконечностей, вы можете просто использовать:
inline bool SameSign(float a, float b) {
return a*b >= 0.0f;
}
, что на самом деле довольно быстро на большинстве современного оборудования и полностью переносимо. Однако это не работает должным образом в случае (ноль, бесконечность), потому что ноль * бесконечность - это NaN, и сравнение вернет false, независимо от знаков. Это также приведет к ненормальной остановке на некотором оборудовании, когда a и b оба крошечные.
, возможно, что-то вроде:
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;
}