Как я могу поменять местами биты ON в байте?

Я читал книгу Джоэла, где он предлагал в качестве интервью вопрос:

Напишите программу для инвертирования битов «ON» в данном байте.

Я могу думать только о решении, используя C.

Спрашивая здесь, чтобы вы могли показать мне, как это сделать не на C (если возможно)

Ответов (17)

Я бы изменил второй пример Палмси, устранив ошибку и устранив eval :

n = 0b11001100
n.to_s(2).rjust(8, '0').reverse.to_i(2)

Это rjust важно, если число, которое нужно побитно обратить, является битовым полем фиксированной длины - без него обратное значение 0b00101010 было бы 0b10101 скорее, чем правильным 0b01010100 . (Очевидно, 8 следует заменить на указанную длину.) Я просто сбился с пути из-за этого.

Поменять биты. Например, у нас есть номер 01101011. Теперь, если мы поменяем местами биты, это число станет 11010110. Теперь, чтобы добиться этого, вы должны сначала узнать, как поменять местами два бита в числе. Замена двух бит в числе: - Выполните XOR для обоих битов с одним и посмотрите, отличаются ли результаты. Если это не так, то оба бита одинаковы, в противном случае выполните XOR для обоих битов с помощью XOR и сохраните его в исходном номере; Теперь для изменения числа FOR I меньше, чем Numberofbits / 2 swap (Number, I, NumberOfBits-1-I);

На классической странице Bit Hacks есть несколько (на самом деле очень умных) способов сделать это, но все они на C. Любой язык, производный от синтаксиса C (особенно Java), скорее всего, будет иметь аналогичные методы. Я уверен, что в этой теме мы получим несколько версий Haskell;)

Что конкретно означает этот вопрос?

Означает ли обратное преобразование 1 в 0 и наоборот?

Или это означает 00001100 -> 00110000, где вы меняете их порядок в байтах? Или, возможно, просто поменять местами часть от первой 1 до последней 1? т.е. 00110101 -> 00101011?

Предполагая, что это означает изменение порядка битов во всем байте, вот версия ассемблера x86:

; al is input register
; bl is output register

xor bl, bl      ; clear output

; first bit
rcl al, 1       ; rotate al through carry
rcr bl, 1       ; rotate carry into bl

; duplicate above 2-line statements 7 more times for the other bits

не самое оптимальное решение, поиск по таблице выполняется быстрее.

byte ReverseByte(byte b) { return b ^ 0xff; }

Это работает, если ^ на вашем языке используется XOR, но не в том случае, если AND это часто бывает.

Изменение порядка битов в C# на противоположное:

byte ReverseByte(byte b)
{
    byte r = 0;
    for(int i=0; i<8; i++)
    {
        int mask = 1 << i;
        int bit = (b & mask) >> i;
        int reversedMask = bit << (7 - i);
        r |= (byte)reversedMask;
    }
    return r;
}

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

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

Что конкретно означает этот вопрос?

Хороший вопрос. Если реверсирование битов «ON» означает реверсирование только тех битов, которые находятся в положении «ON», тогда вы всегда будете получать 0, независимо от того, какой вход. Если это означает перестановку всех битов, то есть изменение всех единиц на 0 и всех 0 на 1, как я изначально это прочитал, то это просто побитовое НЕ или дополнение. В языках на основе C есть оператор дополнения,, ~ который делает это. Например:

unsigned char b = 102;      /* 0x66, 01100110 */
unsigned char reverse = ~b; /* 0x99, 10011001 */

Если вы говорите о переключении 1 на 0 и 0 на 1, используя Ruby:

n = 0b11001100
~n

Если вы имеете в виду обратный порядок:

n = 0b11001100
eval("0b" + n.to_s(2).reverse)

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

n = 123
count = 0
0.upto(8) { |i| count = count + n[i] }

♥ Рубин

Я, вероятно, неправильно помню, но я думал, что вопрос Джоэла был о том, чтобы считать « включенные » биты, а не менять их местами .

А вот версия, вырезанная и вставленная непосредственно из OpenJDK , что интересно, потому что в ней нет цикла. С другой стороны, в отличие от опубликованной мной версии схемы, эта версия работает только для 32-битных и 64-битных чисел. :-)

32-битная версия:

public static int reverse(int i) {
    // HD, Figure 7-1
    i = (i & 0x55555555) << 1 | (i >>> 1) & 0x55555555;
    i = (i & 0x33333333) << 2 | (i >>> 2) & 0x33333333;
    i = (i & 0x0f0f0f0f) << 4 | (i >>> 4) & 0x0f0f0f0f;
    i = (i << 24) | ((i & 0xff00) << 8) |
        ((i >>> 8) & 0xff00) | (i >>> 24);
    return i;
}

64-битная версия:

public static long reverse(long i) {
    // HD, Figure 7-1
    i = (i & 0x5555555555555555L) << 1 | (i >>> 1) & 0x5555555555555555L;
    i = (i & 0x3333333333333333L) << 2 | (i >>> 2) & 0x3333333333333333L;
    i = (i & 0x0f0f0f0f0f0f0f0fL) << 4 | (i >>> 4) & 0x0f0f0f0f0f0f0f0fL;
    i = (i & 0x00ff00ff00ff00ffL) << 8 | (i >>> 8) & 0x00ff00ff00ff00ffL;
    i = (i << 48) | ((i & 0xffff0000L) << 16) |
        ((i >>> 16) & 0xffff0000L) | (i >>> 48);
    return i;
}

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

(define (bit-reverse k n)
  (do ((m (if (negative? n) (lognot n) n) (arithmetic-shift m -1))
       (k (+ -1 k) (+ -1 k))
       (rvs 0 (logior (arithmetic-shift rvs 1) (logand 1 m))))
      ((negative? k) (if (negative? n) (lognot rvs) rvs))))

(define (reverse-bit-field n start end)
  (define width (- end start))
  (let ((mask (lognot (ash -1 width))))
    (define zn (logand mask (arithmetic-shift n (- start))))
    (logior (arithmetic-shift (bit-reverse width zn) start)
            (logand (lognot (ash mask start)) n))))

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

int
bit_reverse(int k, int n)
{
    int m = n < 0 ? ~n : n;
    int rvs = 0;
    while (--k >= 0) {
        rvs = (rvs << 1) | (m & 1);
        m >>= 1;
    }
    return n < 0 ? ~rvs : rvs;
}

int
reverse_bit_field(int n, int start, int end)
{
    int width = end - start;
    int mask = ~(-1 << width);
    int zn = mask & (n >> start);
    return (bit_reverse(width, zn) << start) | (~(mask << start) & n);
}

Я, вероятно, неправильно помню, но я думал, что вопрос Джоэла был о том, чтобы считать «включенные» биты, а не менять их местами.

Ну вот:

#include <stdio.h>

int countBits(unsigned char byte);

int main(){
  FILE* out = fopen( "bitcount.c" ,"w");

  int i;
  fprintf(out, "#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\n");

  fprintf(out, "int bitcount[256] = {");
  for(i=0;i<256;i++){
    fprintf(out, "%i", countBits((unsigned char)i));
    if( i < 255 ) fprintf(out, ", ");
  }
  fprintf(out, "};\n\n");

  fprintf(out, "int main(){\n");

  fprintf(out, "srand ( time(NULL) );\n");
  fprintf(out, "\tint num = rand() %% 256;\n");
  fprintf(out, "\tprintf(\"The byte %%i has %%i bits set to ON.\\n\", num, bitcount[num]);\n");

  fprintf(out, "\treturn 0;\n");
  fprintf(out, "}\n");
  fclose(out);

  return 0;
}

int countBits(unsigned char byte){
  unsigned char mask = 1;
  int count = 0;
  while(mask){
    if( mask&byte ) count++;
    mask <<= 1;
  }
  return count;
}

Вот обязательный Haskell soln для дополнения битов, он использует библиотечную функцию дополнить:

import Data.Bits
import Data.Int

i = 123::Int
i32 = 123::Int32
i64 = 123::Int64
var2 = 123::Integer

test1 = sho i
test2 = sho i32
test3 = sho i64
test4 = sho var2 -- Exception

sho i = putStrLn $ showBits i ++ "\n" ++ (showBits $complement i)
showBits  v = concatMap f (showBits2 v) where
   f False = "0"
   f True  = "1"
showBits2 v = map (testBit v) [0..(bitSize v - 1)]

псевдокод ..

while (Read())
  Write(0);

Если вопрос означает перевернуть все биты, и вам не разрешено использовать C-подобные операторы, такие как XOR и NOT, тогда это сработает:

bFlipped = -1 - bInput;

Я задаю вопрос с подвохом. :) Обращение всех битов означает триггер, но только те биты, которые включены, ясно означают:

return 0;

Спрашивая здесь, чтобы вы могли показать мне, как это сделать не на C (если возможно)

Скажем, у вас есть номер 10101010. Чтобы изменить единицы на нули (и наоборот), вы просто используете XOR:

 10101010
^11111111
 --------
 01010101

Выполнение этого вручную - это примерно столько же "Non C", сколько у вас получится.

Однако из формулировки вопроса на самом деле звучит так, будто он отключает только биты "ON" ... В этом случае ответ равен нулю (как уже упоминалось) (если, конечно, вопрос действительно не просит поменять порядок биты).