Быстрый признак целого числа в C

В C есть функция знака:

int sign(int x)
{
    if(x > 0) return 1;
    if(x < 0) return -1;
    return 0;
}

К сожалению, стоимость сравнения очень высока, таким образом, я должен изменить функцию в заказе, сокращают количество сравнений.

Я попробовал следующее:

int sign(int x)
{
    int result;
    result = (-1)*(((unsigned int)x)>>31);

    if (x > 0) return 1;

    return result;
}

В этом случае я получаю только одно сравнение.

Там какой-либо путь состоит в том, чтобы избежать сравнений вообще?

EDIT possible duplicate does not give an answer for a question as all answers are C++, uses comparison (that I supposed to avoid) or does not return -1, +1, 0.

7
Или более определенно, этот ответ: stackoverflow.com/a/1904074/253056
добавлено автор Paul R, источник
Хороший компилятор должен оптимизировать исходную версию к потоку команд без веток так или иначе.
добавлено автор Paul R, источник
"Стоимость сравнения очень высока": как так? Сравнение с нолем обычно дешевое или бесплатное, как на каждой архитектуре я знаю ЗНАК, и НУЛЕВЫЕ биты регистра состояния установлены автоматически.
добавлено автор Roddy, источник
@GManNickG можно ли, пожалуйста, сказать который?
добавлено автор Alex, источник
@Alex: Ваше требование о дубликате ложное. Это содержит (по крайней мере) два ответа К-компэтибла без отделений.
добавлено автор GManNickG, источник
@Alex: второй комментарий к вашему вопросу.
добавлено автор GManNickG, источник
добавлено автор xanatos, источник
В чем вы нуждаетесь, быстрое вычисление или что-то интересное, но не настолько быстро? Я не вижу никакой смысл избежать только одного (псевдо) comparition с 0 в фаворе к некоторому другому swith и пою изменение
добавлено автор qPCR4vir, источник
@Paul R: +1..., но только очень плохой compliler не оптимизирует это
добавлено автор qPCR4vir, источник
Я думаю после компиляции, оригинальный вариант будет намного быстрее только с одним тестом и двумя скачками condintional
добавлено автор qPCR4vir, источник
подпишитесь = +1 | (v>> (sizeof (интервал) * CHAR_BIT - 1));//, если v <0 тогда-1, еще +1 - От связи, данной @PaulR
добавлено автор gaganbm, источник

5 ответы

В первую очередь, сравнение целого числа очень дешевое. Это - переход , который может быть дорогим (из-за риска отделения mispredictions).

Я определил эффективность вашей функции на коробке Sandy Bridge, используя gcc 4.7.2, и требуется приблизительно 1.2 нс за требование.

Следующее приблизительно на 25% быстрее приблизительно в 0.9 нс за требование:

int sign(int x) {
    return (x > 0) - (x < 0);
}

Машинный код для вышеупомянутого абсолютно без веток:

_sign:
    xorl    %eax, %eax
    testl   %edi, %edi
    setg    %al
    shrl    $31, %edi
    subl    %edi, %eax
    ret

На две вещи стоит указать:

  1. основной уровень работы очень высок.
  2. переход Устранения улучшает работу здесь, но не существенно.
29
добавлено
int sign(int x)
{
   //assumes 32-bit int and 2s complement signed shifts work (implementation defined by C spec)
    return (x>>31) | ((unsigned)-x >> 31);
}

The first part (x>>32) gives you -1 for negative numbers and 0 for 0 or positive numbers. The second part gives you 1 if x > 0 or equal to INT_MIN, and 0 otherwise. Or gives you the right final answer.

There's also the canonical return (x > 0) - (x < 0);, but unfortunately most compilers will use branches to generate code for that, even though there are no visible branches. You can try to manually turn it into branchless code as:

int sign(int x)
{
   //assumes 32-bit int/unsigned
    return ((unsigned)-x >> 31) - ((unsigned)x >> 31);
}

то, которое возможно лучше, чем вышеупомянутое, поскольку оно не зависит от внедрения, определило поведение, но имеет тонкую ошибку, у которой оно возвратится 0 для INT_MIN.

3
добавлено
Это очень хорошо. Вы могли убежать от ошибки INT_MIN, поместив x | = x>> 1 перед возвращением. INT_MIN 10000... INT_MIN | INT_MIN>> 1 11000..., который может быть инвертирован безопасно.
добавлено автор dodo, источник
int sign(int x) {    
    return (x>>31)|(!!x);
}  
2
добавлено
Он использует bitwise оператор сдвига. На некоторых языках это - "Размножающий знак сдвиг вправо" (такой как в JavaScript), что означает, что знак не изменяется. Таким образом откладывание ВСЕХ частей 32-битного плавания, используя x>> 31 </ код> оставляет только знак. !! x преобразовывает в истину или ложь (1 или 0), тогда он bitwise "ИЛИ" s их вместе (объединяет биты).
добавлено автор James Wilkins, источник
Не только, что, но и предполагая то, что интервал составляет 32 бита, абсолютно непортативно - вы можете , вероятно , ожидают, что это, чтобы быть sizeof (интервал) * CHAR_BIT биты. Одна вещь вы can' t принимают, то, что у отрицательных чисел есть конкретное кодирование; величина знака, one' s дополнение и two' s дополнение известные возможности, но C doesn' t вынуждают внедрения быть одним из тех.
добавлено автор Toby Speight, источник
Можно ли также дать некоторое объяснение ответу... Поможет...
добавлено автор NREZ, источник
Правильные целые числа со знаком перемены - неопределенное поведение! Необходимо бросить его к типу целого без знака: возвращение ((неподписанный) x>> 31) | (!! x)
добавлено автор osvein, источник
int i = -10;
if((i & 1 << 31) == 0x80000000)sign = 0;else sign = 1;
//sign 1 = -ve, sign 0 = -ve 
1
добавлено
У этого есть переход.
добавлено автор Alex, источник

If s(x) is a function that returns the sign-bit of x (you implemented it by ((unsigned int)x)>>31), you can combine s(x) and s(-x) in some way. Here is a "truth table":

x > 0: s(x) = 0; s(-x) = 1; your function must return 1

x < 0: s(x) = 1; s(-x) = 0; your function must return -1

x = 0: s(x) = 0; s(-x) = 0; your function must return 0

Таким образом, можно объединить их следующим образом:

s(-x) - s(x)
0
добавлено