Можно ли сказать, есть ли четное или нечетное количество битов «1» в двоичном представлении int в C с помощью одного теста? Если нет, то как это сделать быстрее всего?
Как узнать, есть ли четное или нечетное число 1 бит
Ответы (5)
Возможно, не самый быстрый, но он работает и довольно прост:
int evenNumberOfOnes(unsigned int num)
{
int n=0;
while(num > 0) {
n ^= num;
num = num >> 1;
}
return n & 1;
}
Спасибо @EricPostpischil за советы по улучшению.
1 + nOnes(num >> 1)
вы можете использовать 1 ^ nOnes(num >> 1)
, чтобы конечный результат был всего одним битом. (d) num >> 1
не определяется стандартом C, если num
отрицательное.
- person Eric Postpischil; 31.12.2017
if (num % 2) n++;
на n ^= num;
и return n;
на return n & 1;
.
- person Eric Postpischil; 31.12.2017
n ^= num
, вероятно, быстрее, чем предыдущий код (если только компилятор не оптимизирует предыдущий код довольно хорошо, но он отслеживает только четность, а не количество.
- person Eric Postpischil; 31.12.2017
Один из способов сделать это — подсчитать количество битов и проверить младший бит, чтобы увидеть, является ли значение нечетным или четным, 1
или 0
соответственно. Учитывая 32-битное целое число, мы можем подсчитать количество 1
в его двоичном представлении, Вес Хэмминга с помощью некоторых хитрых битовых манипуляций. Уже есть хороший ответ о том, как найти вес Хэмминга здесь, и это тоже говорит об эффективности.
Возьмем этот алгоритм нахождения веса Хэмминга путем сложения с боковым накоплением. hasOddCount
возвращает 1
, если x
имеет нечетное количество 1
s.
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;
}
Я уверен, что это не наименьший возможный код, но он короткий и понятный;
#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, я не проверял это, и завтра мне может быть неловко, если я неправильно понял эту простую задачу. Пожалуйста, не вводите этот код во что-либо, использующее термоядерное оружие или беспилотные автомобили.
РЕДАКТИРОВАТЬ: Увидев ответы других, которые также могут быть правильными &| сложный; Самый быстрый код действительно будет зависеть от архитектуры. Модуль очень быстр только на некоторых процессорах, на других он будет потреблять много циклов. Все смещается примерно за одни и те же несколько циклов.
%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
uint_fast32_t
был совершенно не важен для ответа (кто просил 32 бита?), поэтому вместо того, чтобы искать SCNuFAST32
(что я сделал сейчас, и это заняло добрых три минуты) и еще больше запутать ответ дополнительной сложностью (если вы хотите съешьте завершающие пробелы, теперь вам нужно написать что-то вроде scanf(SCNuFAST32 "\n", &x);
, поэтому теперь вам нужно объяснить, что это за макрос, зачем он нужен в первую очередь, а также вставку токена) Я просто использовал unsigned
, у которого есть хороший и общеизвестный спецификатор scanf
и это самый быстрый тип, который вы можете использовать.
- person Matteo Italia; 31.12.2017
scanf
было неправильным; Я оставался на ленивой стороне спектра, когда исправлял его, потому что дополнительная сложность правильного исправления, которое включало uint_fast32_t
IMO, на самом деле не стоило того, и приоритетом было иметь работающий и правильный ответ на месте, иллюстрирующий тривиальный алгоритм. Не стесняйтесь повторно вводить uint_fast32_t
, но если вы это сделаете, убедитесь, что вы используете правильный спецификатор scanf
.
- person Matteo Italia; 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");
}
}
Я публикую это как новый ответ, так как он немного хакерский и немного отличается от моего другого ответа.
int evenNumberOfOnesFast(unsigned int num)
{
unsigned char *b;
b = (unsigned char *)#
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, и на самом деле оно примерно в десять раз быстрее, чем мой быстрый вариант.
par
не выводятся никакие результаты. (Запись в него не имеет «наблюдаемых» эффектов.) Вы можете объявить par
как volatile int *par
, чтобы обойти оптимизацию компилятора.
- person Eric Postpischil; 31.12.2017
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