Побитовые операции, эквивалентные большей, чем оператор

Я работаю над функцией, которая, по существу, увидит, какой из двух ints больше. Параметры, которые передаются в 2 32-битных Интс. Трюк - это единственные допустимые операторы ! ~ | & << >> ^ ! ~ | & << >> ^ ! ~ | & << >> ^ (без кастингов, других типов данных, кроме подписанных int, *,/, - и т.д.).

Моя идея до сих пор является ^ два бинарных файлов вместе, чтобы увидеть все позиции 1 значений, которые они не разделяют. То, что я хочу сделать, - это принять это значение и изолировать 1 дальше слева. Затем посмотрите, из какого из них оно имеет это значение. Тогда это значение будет больше. (Скажем, мы используем 8-битные int вместо 32-разрядных). Если два значения, переданные были 01011011 и 01101001 я ^ на них, чтобы получить 00100010. Затем я хочу сделать это 00100000 другими словами 01xxxxxx → 01000000 Тогда & это с первым номером !! результат и вернуть его. Если оно равно 1, то первый # больше.

Любые мысли о том, как 01xxxxxx → 01000000 или что-нибудь еще, чтобы помочь?

Забыл отметить: no ifs, whiles, fors и т.д....

Ответы

Ответ 1

Здесь версия без цикла, которая сравнивает целые числа без знака в операциях O (lg b), где b - размер слова машины. Обратите внимание, что OP не указывает другие типы данных, чем signed int, поэтому, похоже, верхняя часть этого ответа не соответствует спецификациям OP. (Версия спойлера как внизу).

Обратите внимание, что поведение, которое мы хотим захватить, - это когда наиболее значимое несоответствие бит 1 для a и 0 для b. Другой способ думать об этом - это любой бит в a, который больше, чем соответствующий бит в b означает, что a больше, чем b, если в a не было более раннего бита, меньше, чем соответствующий бит в b.

С этой целью мы вычисляем все биты в a больше, чем соответствующие биты в b, а также вычисляем все биты в a меньше, чем соответствующие биты в b. Теперь мы хотим замаскировать все "большие" биты, которые ниже любых "меньше" бит, поэтому мы берем все "меньше" бит и смазываем их все вправо, создавая маску: самый старший бит задает все путь вниз до младшего значащего бита теперь 1.

Теперь все, что нам нужно сделать, это удалить бит "больше", заданный с помощью простой логики маскировки бит.

Результирующее значение равно 0, если a <= b и отличное от нуля, если a > b. Если мы хотим, чтобы он был 1, в последнем случае мы можем сделать подобный мазальный трюк и просто взглянуть на младший значащий бит.

#include <stdio.h>

// Works for unsigned ints.
// Scroll down to the "actual algorithm" to see the interesting code.

// Utility function for displaying binary representation of an unsigned integer
void printBin(unsigned int x) {
    for (int i = 31; i >= 0; i--) printf("%i", (x >> i) & 1);
    printf("\n");
}
// Utility function to print out a separator
void printSep() {
    for (int i = 31; i>= 0; i--) printf("-");
    printf("\n");
}

int main()
{
    while (1)
    {
        unsigned int a, b;

        printf("Enter two unsigned integers separated by spaces: ");
        scanf("%u %u", &a, &b);
        getchar();

        printBin(a);
        printBin(b);
        printSep();

            /************ The actual algorithm starts here ************/

        // These are all the bits in a that are less than their corresponding bits in b.
        unsigned int ltb = ~a & b;

        // These are all the bits in a that are greater than their corresponding bits in b.
        unsigned int gtb = a & ~b;

        ltb |= ltb >> 1;
        ltb |= ltb >> 2;
        ltb |= ltb >> 4;
        ltb |= ltb >> 8;
        ltb |= ltb >> 16;

        // Nonzero if a > b
        // Zero if a <= b
        unsigned int isGt = gtb & ~ltb;

        // If you want to make this exactly '1' when nonzero do this part:
        isGt |= isGt >> 1;
        isGt |= isGt >> 2;
        isGt |= isGt >> 4;
        isGt |= isGt >> 8;
        isGt |= isGt >> 16;
        isGt &= 1;

            /************ The actual algorithm ends here ************/

        // Print out the results.
        printBin(ltb); // Debug info
        printBin(gtb); // Debug info
        printSep();
        printBin(isGt); // The actual result
    }
}

Примечание. Это должно работать и для целых чисел со знаком, если вы переверните верхний бит на обоих входах, например. a ^= 0x80000000.

Спойлер

Если вам нужен ответ, отвечающий всем требованиям (включая 25 операторов или меньше):

int isGt(int a, int b)
{
    int diff = a ^ b;
    diff |= diff >> 1;
    diff |= diff >> 2;
    diff |= diff >> 4;
    diff |= diff >> 8;
    diff |= diff >> 16;

    diff &= ~(diff >> 1) | 0x80000000;
    diff &= (a ^ 0x80000000) & (b ^ 0x7fffffff);

    return !!diff;
}

Я оставлю объяснение, почему это работает с вами.

Ответ 2

An неподписанный вариант, учитывая, что можно использовать логические (& &, ||) и сравнение (! =, ==).

int u_isgt(unsigned int a, unsigned int b)
{
    return a != b && (    /* If a == b then a !> b and a !< b.             */
               b == 0 ||  /* Else if b == 0 a has to be > b (as a != 0).   */
               (a / b)    /* Else divide; integer division always truncate */
           );             /*              towards zero. Giving 0 if a < b. */
}

!= и == можно легко устранить, т.е.:

int u_isgt(unsigned int a, unsigned int b)
{
    return a ^ b && (
               !(b ^ 0) ||
               (a / b)
           );
}

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

int isgt(int a, int b)
{
    return
    (a != b) &&
    (
        (!(0x80000000 & a) && 0x80000000 & b) ||  /* if a >= 0 && b < 0  */
        (!(0x80000000 & a) && b == 0) ||
        /* Two more lines, can add them if you like, but as it is homework
         * I'll leave it up to you to decide. 
         * Hint: check on "both negative" and "both not negative". */
    )
    ;
}

Может быть более компактным/устранить операции. (по крайней мере, один), но для ясности сделайте это так.

Вместо 0x80000000 можно было бы сказать, что:

#include <limits.h>
static const int INT_NEG = (1 << ((sizeof(int) * CHAR_BIT) - 1));

Используя это для тестирования:

void test_isgt(int a, int b)
{
    fprintf(stdout,
        "%11d > %11d = %d : %d %s\n",
        a, b,
        isgt(a, b), (a > b),
        isgt(a, b) != (a>b) ? "BAD!" : "OK!");
}

Результат:

         33 >           0 = 1 : 1 OK!
        -33 >           0 = 0 : 0 OK!
          0 >          33 = 0 : 0 OK!
          0 >         -33 = 1 : 1 OK!
          0 >           0 = 0 : 0 OK!
         33 >          33 = 0 : 0 OK!
        -33 >         -33 = 0 : 0 OK!
         -5 >         -33 = 1 : 1 OK!
        -33 >          -5 = 0 : 0 OK!
-2147483647 >  2147483647 = 0 : 0 OK!
 2147483647 > -2147483647 = 1 : 1 OK!
 2147483647 >  2147483647 = 0 : 0 OK!
 2147483647 >           0 = 1 : 1 OK!
          0 >  2147483647 = 0 : 0 OK!

Ответ 3

Чтобы преобразовать 001xxxxx в 00100000, вы сначала выполните:

x |= x >> 4;
x |= x >> 2;
x |= x >> 1;

(это для 8 бит, чтобы расширить его до 32, добавить сдвиги на 8 и 16 в начале последовательности).

Это оставляет нас с 00111111 (этот метод иногда называют "размазыванием" ). Затем мы можем отбросить все, кроме первого 1 бит:

x ^= x >> 1;

оставив нас с 00100000.

Ответ 4

Полностью нераспространяемая версия функция меньшего размера gag Kaganar может выглядеть так:

int isGt(int a, int b)
{
    int diff = a ^ b;
    diff |= diff >> 1;
    diff |= diff >> 2;
    diff |= diff >> 4;
    diff |= diff >> 8;
    diff |= diff >> 16;

    //1+ on GT, 0 otherwise.
    diff &= ~(diff >> 1) | 0x80000000;
    diff &= (a ^ 0x80000000) & (b ^ 0x7fffffff);

    //flatten back to range of 0 or 1.
    diff |= diff >> 1;
    diff |= diff >> 2;
    diff |= diff >> 4;
    diff |= diff >> 8;
    diff |= diff >> 16;
    diff &= 1;

    return diff;
}

Это время составляет около 60 инструкций для фактического вычисления (компилятор MSVC 2010, в архитектуре x86), плюс дополнительные 10 стековых операций или около того для функции proog/epilog.

Ответ 5

EDIT:

Хорошо, были некоторые проблемы с кодом, но я пересмотрел его, и следующие работы.

Эта вспомогательная функция сравнивает цифры n-й значащей цифры:

int compare ( int a, int b, int n )
{
    int digit = (0x1 << n-1);
    if ( (a & digit) && (b & digit) )
       return 0; //the digit is the same

    if ( (a & digit) && !(b & digit) )
       return 1; //a is greater than b

    if ( !(a & digit) && (b & digit) )
       return -1; //b is greater than a
}

Ниже следует рекурсивно возвращать большее число:

int larger ( int a, int b )
{
    for ( int i = 8*sizeof(a) - 1 ; i >= 0 ; i-- )
    {
       if ( int k = compare ( a, b, i ) )
       {
           return (k == 1) ? a : b;
       }
    }
    return 0; //equal
}

Ответ 6

Насколько я не хочу заниматься кем-то другим домашним заданием, я не мог устоять перед этим...:) Я уверен, что другие могут подумать о более компактном... но здесь мое... хорошо работает, в том числе отрицательные числа.

Изменить: есть парочка ошибок. Я оставлю его в OP, чтобы найти его и исправить.

    #include<unistd.h>
    #include<stdio.h>
    int a, b, i, ma, mb, a_neg, b_neg, stop;

    int flipnum(int *num, int *is_neg) {
        *num = ~(*num) + 1;
        *is_neg = 1;

        return 0;
    }

    int print_num1() {
        return ((a_neg && printf("bigger number %d\n", mb)) ||
             printf("bigger number %d\n", ma));
    }

    int print_num2() {
        return ((b_neg && printf("bigger number %d\n", ma)) ||
             printf("bigger number %d\n", mb));
    }

    int check_num1(int j) {
        return ((a & j) && print_num1());
    }

    int check_num2(int j) {
        return ((b & j) && print_num2());
    }

    int recursive_check (int j) {
        ((a & j) ^ (b & j)) && (check_num1(j) || check_num2(j))  && (stop = 1, j = 0);

        return(!stop && (j = j >> 1) && recursive_check(j));
    }

    int main() {
        int j;
        scanf("%d%d", &a, &b);
        ma = a; mb = b;

        i = (sizeof (int) * 8) - 1;
        j = 1 << i;

        ((a & j) && flipnum(&a, &a_neg));

        ((b & j) && flipnum(&b, &b_neg));

        j = 1 << (i - 1);

        recursive_check(j);

        (!stop && printf("numbers are same..\n"));
    }

Ответ 7

Я думаю, что у меня есть решение с тремя операциями:

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

Я не уверен на 100%, если это правильно. То есть вам, возможно, не нужно добавлять 1, и я не знаю, можно ли проверить переполнение (если не тогда просто зарезервировать последний бит и проверить, если он 1 в конце.)

Ответ 8

EDIT: ограничения делают простой подход ниже. Я добавляю функцию бинарного поиска и окончательное сравнение для определения большего значения:

unsigned long greater(unsigned long a, unsigned long b) {
    unsigned long x = a;
    unsigned long y = b;
    unsigned long t = a ^ b;
    if (t & 0xFFFF0000) {
        x >>= 16;
        y >>= 16;
        t >>= 16;
    }
    if (t & 0xFF00) {
        x >>= 8;
        y >>= 8;
        t >>= 8;
    }
    if (t & 0xf0) {
        x >>= 4;
        y >>= 4;
        t >>= 4;
    }
    if ( t & 0xc) {
        x >>= 2;
        y >>= 2;
        t >>= 2;
    }
    if ( t & 0x2) {
        x >>= 1;
        y >>= 1;
        t >>= 1;
    }
    return (x & 1) ? a : b;
}

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

Я не добавил здесь одинаковый случай.


Старый ответ:

Самый простой метод, вероятно, лучший для домашней работы. После того, как вы получили несоответствующее значение бита, вы начинаете с другой маски на 0x80000000 (или любую подходящую максимальную позицию бита для вашего размера слова) и сохраняйте право сдвигаться до тех пор, пока не нажмете бит, который установлен в вашем значении несоответствия. Если ваша правая смена заканчивается 0, то значение несоответствия равно 0.

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