Код без веток, который отображает нуль, отрицательный, и положительный 0, 1, 2

Я соглашаюсь, AbstractFoo является достойным решением. Я пытаюсь выбрать имена, которым не нужны дополнительные прилагательные. Я уклонился бы от использования Основы.

14
задан Marc Eaddy 26 October 2009 в 18:21
поделиться

9 ответов

int Compare(int x, int y) {
     return (x < y) + (y < x) << 1;
}

Изменить: только побитовое? Угадай, <и> не в счет?

int Compare(int x, int y) {
    int diff = x - y;
    return (!!diff) | (!!(diff & 0x80000000) << 1);
}

Но вот надоедливое - .

Редактировать: Сдвинуть наоборот.

Мех, просто чтобы попробовать еще раз:

int Compare(int x, int y) {
    int diff = y - x;
    return (!!diff) << ((diff >> 31) & 1);
}

Но я полагаю, что для !! нет стандартной инструкции ASM. Кроме того, << можно заменить на + , в зависимости от того, что быстрее ...

Вращение битов - это весело!

Хм, я только что узнал о ] setnz .

Я не проверял вывод ассемблера (но на этот раз я проверил его немного), и, если повезет, он может сохранить целую инструкцию !:

В ТЕОРИИ. МОЙ СБОРНИК Ржавый

subl  %edi, %esi
setnz %eax
sarl  $31, %esi
andl  $1, %esi
sarl  %eax, %esi
mov   %esi, %eax
ret

Играть в азартные игры - это весело.

Мне нужно спать.

14
ответ дан 1 December 2019 в 05:55
поделиться

Предполагая дополнение до 2, арифметический сдвиг вправо и отсутствие переполнения при вычитании:

#define SHIFT (CHARBIT*sizeof(int) - 1)

int Compare(int x, int y)
{
    int diff = x - y;
    return -(diff >> SHIFT) - (((-diff) >> SHIFT) << 1);
}
10
ответ дан 1 December 2019 в 05:55
поделиться

Дополнение до двух:

#include <limits.h>
#define INT_BITS (CHAR_BITS * sizeof (int))

int Compare(int x, int y) {
    int d = y - x;
    int p = (d + INT_MAX) >> (INT_BITS - 1);
    d = d >> (INT_BITS - 2);
    return (d & 2) + (p & 1);
}

Предполагая, что компилятор работает нормально, это не вызовет аппаратное обеспечение сравнения вашей системы и не использует сравнение на языке. Чтобы проверить: если x == y, то d и p явно будут равны 0, поэтому окончательный результат будет равен нулю. Если (x - y)> 0, то ((x - y) + INT_MAX) установит старший бит целого числа, в противном случае он будет сброшен. Таким образом, младший бит p будет установлен тогда и только тогда, когда (x - y)> 0. Если (x - y) <0, тогда будет установлен его старший бит, а d установит второй младший бит.

8
ответ дан 1 December 2019 в 05:55
поделиться

Код без ответвлений (на уровне языка), который отображает отрицательные значения на -1, ноль на 0 и положительные на +1, выглядит следующим образом

int c = (n > 0) - (n < 0);

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

const int MAP[] = { 1, 0, 2 };
int c = MAP[(n > 0) - (n < 0) + 1];

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

int c = 2 * (n > 0) + (n < 0);

(очевидно, очень легко сгенерировать любое отображение из этого, пока 0 отображается в 0. И код вполне читабелен . Если 0 сопоставляется с чем-то другим, он становится более сложным и менее читаемым.)

В качестве дополнительного примечания: сравнение двух целых чисел путем вычитания одного из другого на уровне языка C является ошибочной техникой, так как обычно имеет тенденцию к переполнению . Прелесть вышеупомянутых методов в том, что их можно сразу же использовать для сравнений без вычитания, например

int c = 2 * (x > y) + (x < y);
21
ответ дан 1 December 2019 в 05:55
поделиться

Сравнение без знака, которое возвращает -1,0,1 (cmpu), - это один из случаев, на который проверяется GNU SuperOptimizer .

cmpu: compare (unsigned)
int cmpu(unsigned_word v0, unsigned_word v1)
{
    return ( (v0 > v1) ? 1 : ( (v0 < v1) ? -1 : 0) );
}

A SuperOptimizer тщательно ищет в пространстве инструкций наилучшую возможную комбинацию инструкций, которая будет реализовывать данную функцию. Предполагается, что компиляторы автоматически заменят указанные выше функции их супероптимизированными версиями (хотя не все компиляторы делают это). Например, в Руководстве по написанию компилятора PowerPC ( powerpc-cwg.pdf ) функция cmpu показана в Приложении D, стр. 204:

cmpu: compare (unsigned)
PowerPC SuperOptimized Version
subf  R5,R4,R3
subfc R6,R3,R4
subfe R7,R4,R3
subfe R8,R7,R5

Это очень хорошо, не правда ли ... просто четыре вычитания (и с переносом и / или расширенными версиями). Не говоря уже о том, что это действительно без ветвей на уровне кода операции . Вероятно, существует эквивалентная последовательность для ПК / Intel X86, которая так же коротка, поскольку GNU Superoptimizer работает как для X86, так и для PowerPC.

Обратите внимание, что сравнение без знака (cmpu) может быть преобразовано в сравнение со знаком (cmps) на 32-битной системе. сравните, добавив 0x80000000 к обоим входам со знаком перед передачей его в cmpu.

cmps: compare (signed)
int cmps(signed_word v0, signed_word v1)
{
    signed_word offset=0x80000000;
    return ( (unsigned_word) (v0 + signed_word),
        (unsigned_word) (v1 + signed_word) );
}

Это всего лишь один вариант ... SuperOptimizer может найти более короткий cmps, который не должен добавлять смещения и вызывать cmpu.

Чтобы получите запрошенную версию, которая возвращает ваши значения {1,0,2}, а не {-1,0,1}, используйте следующий код, который использует функцию SuperOptimized cmps.

int Compare(int x, int y)
{
    static const int retvals[]={1,0,2};
    return (retvals[cmps(x,y)+1]);
}
6
ответ дан 1 December 2019 в 05:55
поделиться

Объединение ответов Стивена Кэнона и Тордека:

int Compare(int x, int y)
{
    int diff = x - y; 
    return -(diff >> 31) + (2 & (-diff >> 30));
} 

Результат: (g ++ -O3)

subl     %esi,%edi 
movl     %edi,%eax
sarl     $31,%edi
negl     %eax
sarl     $30,%eax
andl     $2,%eax
subl     %edi,%eax 
ret 

Точно! Однако версия Пола Хси содержит еще меньше инструкций:

subl     %esi,%edi
leal     0x7fffffff(%rdi),%eax
sarl     $30,%edi
andl     $2,%edi
shrl     $31,%eax
leal     (%rdi,%rax,1),%eax
ret
0
ответ дан 1 December 2019 в 05:55
поделиться
int Compare(int x, int y)
{
    int diff = x - y;

    int absdiff = 0x7fffffff & diff; // diff with sign bit 0
    int absdiff_not_zero = (int) (0 != udiff);

    return
        (absdiff_not_zero << 1)      // 2 iff abs(diff) > 0
        -
        ((0x80000000 & diff) >> 31); // 1 iff diff < 0
}
0
ответ дан 1 December 2019 в 05:55
поделиться

Я присоединяюсь к исходному ответу Тордека:

int compare(int x, int y) {
    return (x < y) + 2*(y < x);
}

Компиляция с gcc -O3 -march = pentium4 приводит к получению кода без ветвей, который использует условные инструкции setg и setl (см. это объяснение инструкций x86 ).

push   %ebp
mov    %esp,%ebp
mov    %eax,%ecx
xor    %eax,%eax
cmp    %edx,%ecx
setg   %al
add    %eax,%eax
cmp    %edx,%ecx
setl   %dl
movzbl %dl,%edx
add    %edx,%eax
pop    %ebp
ret 
4
ответ дан 1 December 2019 в 05:55
поделиться

Good god, this has haunted me.

Whatever, I think I squeezed out a last drop of performance:

int compare(int a, int b) {
    return (a != b) << (a > b);
}

Although, compiling with -O3 in GCC will give (bear with me I'm doing it from memory)

xorl  %eax, %eax
cmpl  %esi, %edi
setne %al
cmpl  %esi, %edi
setgt %dl
sall  %dl, %eax
ret

But the second comparison seems (according to a tiny bit of testing; I suck at ASM) to be redundant, leaving the small and beautiful

xorl  %eax, %eax
cmpl  %esi, %edi
setne %al
setgt %dl
sall  %dl, %eax
ret

(Sall may totally not be an ASM instruction, but I don't remember exactly)

So... if you don't mind running your benchmark once more, I'd love to hear the results (mine gave a 3% improvement, but it may be wrong).

3
ответ дан 1 December 2019 в 05:55
поделиться
Другие вопросы по тегам:

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