Как узнать, есть ли четное или нечетное число 1 бит

Можно ли сказать, есть ли четное или нечетное количество битов «1» в двоичном представлении int в C с помощью одного теста? Если нет, то как это сделать быстрее всего?


person RIAN    schedule 31.12.2017    source источник
comment
Комментарии не для расширенного обсуждения; этот разговор был перешел в чат.   -  person Bhargav Rao    schedule 31.12.2017
comment
Если вы введете новый вопрос вроде «Каков эффективный способ итерации с известной четностью» и объясните проблему более подробно, вы можете получить более релевантные ответы. Вот одна из возможностей: for (unsigned i = 0; i < Limit; i += 2) { unsigned i0 = i ^ i>>1; unsigned i1 = i0 ^ 1; DoEvenParityStuff(i0); DoOddParityStuff(i1); }. Для этого требуется, чтобы Limit был степенью двойки. Если это не так, можно внести изменения.   -  person Eric Postpischil    schedule 01.01.2018


Ответы (5)


Возможно, не самый быстрый, но он работает и довольно прост:

int evenNumberOfOnes(unsigned int num)
{
    int n=0;
    while(num > 0) {
        n ^= num;
        num = num >> 1;
    }
    return n & 1;
}

Спасибо @EricPostpischil за советы по улучшению.

person klutt    schedule 31.12.2017
comment
(a) Считать биты легко. Вопрос требует скорости, что требует некоторых усилий. (b) Рекурсия — плохой способ сделать это. (c) Вместо 1 + nOnes(num >> 1) вы можете использовать 1 ^ nOnes(num >> 1), чтобы конечный результат был всего одним битом. (d) num >> 1 не определяется стандартом C, если num отрицательное. - person Eric Postpischil; 31.12.2017
comment
Поскольку OP не предлагает никакого решения самостоятельно, я не чувствую, что могу что-либо предположить, но вы правы насчет рекурсии. Я удалю эту версию. - person klutt; 31.12.2017
comment
Вы можете изменить if (num % 2) n++; на n ^= num; и return n; на return n & 1;. - person Eric Postpischil; 31.12.2017
comment
@EricPostpischil Хорошо. Фиксированный. - person klutt; 31.12.2017
comment
С этим изменением фраза «Это дает вам количество единиц» больше не соответствует действительности. Идея XOR заключается в том, что он предназначен только для вычисления четности (четности или нечетности количества установленных битов), а не для подсчета битов. n ^= num, вероятно, быстрее, чем предыдущий код (если только компилятор не оптимизирует предыдущий код довольно хорошо, но он отслеживает только четность, а не количество. - person Eric Postpischil; 31.12.2017
comment
@EricPostpischil Ты прав. Я не думал и просто вставил то, что вы написали. - person klutt; 31.12.2017
comment
Поскольку теперь вы заметили, что вы можете просто XOR для всех битов, вы также можете сделать следующий шаг и перетасовать XOR в более сбалансированное дерево, которое можно оценивать по уровням с побитовым параллелизмом. - person harold; 31.12.2017
comment
@harold Я сделал новую версию. Проверьте мой новый ответ. - person klutt; 31.12.2017
comment
Если установлен только старший бит, для его нахождения требуется 32 сдвига. Этот код stackoverflow.com/a/37558380/597607 занимает всего один раунд. - person Bo Persson; 31.12.2017

Один из способов сделать это — подсчитать количество битов и проверить младший бит, чтобы увидеть, является ли значение нечетным или четным, 1 или 0 соответственно. Учитывая 32-битное целое число, мы можем подсчитать количество 1 в его двоичном представлении, Вес Хэмминга с помощью некоторых хитрых битовых манипуляций. Уже есть хороший ответ о том, как найти вес Хэмминга здесь, и это тоже говорит об эффективности.

Возьмем этот алгоритм нахождения веса Хэмминга путем сложения с боковым накоплением. hasOddCount возвращает 1, если x имеет нечетное количество 1s.

int hasOddCount(int x) {
    int first = 0x11 | (0x11 << 8);
    int second = first | (first << 16);
    int mask = 0xf | (0xf << 8);
    int count = 0;
    int sum = second & x;
    sum = (x>>1 & second) + sum;
    sum = (x>>2 & second) + sum;
    sum = (x>>3 & second) + sum;

    sum = sum + (sum >> 16);

    sum = ((sum & mask) + (mask & (sum >> 4)));
    count = (((sum >> 8) + sum) & 0x3f);

    //count is the Hamming weight, & by 0x1 to see if it's odd
    return count & 0x1;
}
person Miket25    schedule 31.12.2017
comment
Это можно упростить, четность можно вычислить без вычисления всего веса Хэмминга. - person harold; 31.12.2017

Я уверен, что это не наименьший возможный код, но он короткий и понятный;

#include <stdio.h>

int main(int argc, const char * argv[]) {
    unsigned x;
    int count;

    scanf("%u\n",&x);

    for(count=0;x;x>>=1)
        if(x&1)
            ++count;

    puts(count&1?"odd":"even");

    return(0);
}

Кстати: я пишу код с уровнем алкоголя в крови 0,05, я не проверял это, и завтра мне может быть неловко, если я неправильно понял эту простую задачу. Пожалуйста, не вводите этот код во что-либо, использующее термоядерное оружие или беспилотные автомобили.

РЕДАКТИРОВАТЬ: Увидев ответы других, которые также могут быть правильными &| сложный; Самый быстрый код действительно будет зависеть от архитектуры. Модуль очень быстр только на некоторых процессорах, на других он будет потреблять много циклов. Все смещается примерно за одни и те же несколько циклов.

person EddingtonsMonkey    schedule 31.12.2017
comment
@Matteo Хотя ваши правки семантически правильны, они были ненужными и чрезмерно педантичными. У меня были веские причины для специфики моего s uint_fast32_t. Это был тонкий способ сказать очевидному программисту-новичку обратить внимание на архитектуру, а также знаковые и неподписанные. - person EddingtonsMonkey; 31.12.2017
comment
Они были нужны, и точка. %ud\n означает unsigned, за которым следует буквальное d и произвольный пробел; вам не нужен этот d, и если вы хотите использовать uint_fast32_t, вы должны использовать соответствующий макрос для scanf - SCNuFAST32, так как "%u" предназначен только для unsigned int, а сам смысл uint_fast32_t в том, что это может быть какой-то другой тип. Итак, как бы то ни было, это было одновременно запутанным (d) и неправильным (не только семантически - например, на Arduino это scanf привело бы к UB). - person Matteo Italia; 31.12.2017
comment
uint_fast32_t был совершенно не важен для ответа (кто просил 32 бита?), поэтому вместо того, чтобы искать SCNuFAST32 (что я сделал сейчас, и это заняло добрых три минуты) и еще больше запутать ответ дополнительной сложностью (если вы хотите съешьте завершающие пробелы, теперь вам нужно написать что-то вроде scanf(SCNuFAST32 "\n", &x);, поэтому теперь вам нужно объяснить, что это за макрос, зачем он нужен в первую очередь, а также вставку токена) Я просто использовал unsigned, у которого есть хороший и общеизвестный спецификатор scanf и это самый быстрый тип, который вы можете использовать. - person Matteo Italia; 31.12.2017
comment
TL;DR: scanf было неправильным; Я оставался на ленивой стороне спектра, когда исправлял его, потому что дополнительная сложность правильного исправления, которое включало uint_fast32_t IMO, на самом деле не стоило того, и приоритетом было иметь работающий и правильный ответ на месте, иллюстрирующий тривиальный алгоритм. Не стесняйтесь повторно вводить uint_fast32_t, но если вы это сделаете, убедитесь, что вы используете правильный спецификатор scanf. - person Matteo Italia; 31.12.2017
comment
Мои извенения. Я смирен. Я использовал uint_fast32_t, чтобы не проверять размер int. - person EddingtonsMonkey; 31.12.2017

int parity(int n) {
   int p = 0;

   for (;n;n>>=1) {
      p = (n&1) ? 1-p : p;
   }

   return p;
}

вернет ноль, если количество единиц в аргументе n четно. Простой тест:

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>

int main() {
   srand(time(NULL));
   for (int i=0;i<100;i++) {   
      int n = rand();
      printf("%d has an %s number of 1's \n", n, parity(n)  ? "odd" : "even");
   }
}
person Laszlo    schedule 31.12.2017

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

int evenNumberOfOnesFast(unsigned int num)
{
    unsigned char *b;
    b = (unsigned char *)&num;
    unsigned char par[4]={0};

    for(int i=0; i<8; i++) {
        for( int j=0; j<4; j++) {
            par[j]^=b[j];
            b[j]=b[j] >> 1;
        }
    }
    return (par[0] ^ par[1] ^ par[2] ^ par[3]) & 1;
}

Здесь я предполагаю, что int составляет 4 байта. Я надеялся, что это позволит процессору делать некоторые вещи параллельно в конвейере. Я не уверен, что это так, но это было примерно в два раза быстрее, чем моя предыдущая версия.

Вот основная функция для проверки:

#define N 10000000
int main()
{
    int * arr = malloc(N*sizeof(*arr));
    int * par = malloc(N*sizeof(*par));

    // Random values to prevent optimizer from just removing the code                                              
    srand(time(NULL));

    for(int i=0; i<N; i++)
        arr[i]=rand();

    // Correctness check                                                                                           
    for(int i=0; i<N; i++)
        if(evenNumberOfOnes(arr[i]) != evenNumberOfOnesFast(arr[i])) {
            perror ("Error");
            exit(EXIT_FAILURE);
        }

    clock_t begin, end;
    double time_spent;

    begin = clock();

    for(int i=0; i<N; i++)
        par[i]=evenNumberOfOnes(arr[i]);

    end = clock();
    time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
    printf(" Time: %f\n", time_spent);

    begin = clock();

    for(int i=0; i<N; i++)
        par[i]=evenNumberOfOnesFast(arr[i]);

    end = clock();
    time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
    printf(" Time: %f\n", time_spent);
}

И результат:

$ cc r.c -O3 -Wall -Wextra
$ ./a.out 
 Time: 0.201635
 Time: 0.093152

Тем не менее, я также протестировал решение hasOddCount, предоставленное @Miket25, и на самом деле оно примерно в десять раз быстрее, чем мой быстрый вариант.

person klutt    schedule 31.12.2017
comment
Вам нужен новый компилятор. Мой (Apple LLVM 9.0.0) оптимизирует почти всю программу, поскольку из массива par не выводятся никакие результаты. (Запись в него не имеет «наблюдаемых» эффектов.) Вы можете объявить par как volatile int *par, чтобы обойти оптимизацию компилятора. - person Eric Postpischil; 31.12.2017
comment
@EricPostpischil Возможно, но у меня это сработало. Это gcc 6.3.0 - person klutt; 31.12.2017