Инвертировать n битов, начинающихся с позиции p, целого числа без знака

При попытке создать функцию, возвращающую x с n биты, которые начинаются с позиции p перевернутый, оставив остальные без изменений — следуя упражнению в K&R — я придумал следующий фрагмент кода.

Тем не менее, мне непонятно, с чего начать подсчет, т. Е. Результат был бы другим, если бы начинать справа. Я начинаю слева — бит первый — это первый бит слева.

unsigned invert(unsigned x, unsigned p, unsigned n)
{
  return x ^ ((~0U >> (p - 1 + (sizeof(int) * CHAR_BIT - p + 1 - n))) << (sizeof(int) * CHAR_BIT - p + 1 - n));
}

Что я бы затем упростил:

unsigned invert(unsigned x, unsigned p, unsigned n)
{
  return x ^ ((~0U >> (sizeof(int) * CHAR_BIT - n)) << (sizeof(int) * CHAR_BIT - p + 1 - n));
}

Я не выполняю никаких проверок границ.

Это хорошая практика? Есть ли более чистое решение?

1 ответ
1

Когда ты найдешь это «непонятно с чего начать подсчет«вы столкнулись с одной из величайших проблем в разработке программного обеспечения: получение полезных спецификаций от ваших заинтересованных сторон! В таких ситуациях, когда мы не можем просто спросить, тогда нам нужно выбрать интерпретацию, которая кажется наиболее полезной и разумной, и (это важный момент), будьте предельно ясны, что мы должны были сделать выбор, и то, что мы выбрали.

В этом случае я бы, вероятно, сделал несколько более конкретное имя, а также добавил бы пояснительный комментарий:

/*
 * Returns x with n bits inverted starting at bit p
 * (counting from the left)
 * E.g. invert_bits(0x0000, 4, 2) == 0x0c00 for 16-bit unsigned
 */
unsigned invert_bits(unsigned x, unsigned p, unsigned n)

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

Мне кажется странным, что мы работаем с unsigned пока мы используем sizeof (int) для расчета ширины. Хотя мы знаем, что int и unsigned int одинакового размера, понятнее быть последовательным. На самом деле, я бы просто использовал sizeof x, что затем упрощает повторное использование кода — например, если мы напишем версию для unsigned long.

Я думаю, что арифметику можно было бы упростить, если бы учесть, что маска с крайним левым p бит не установлен ~0u >> p. С помощью invert_bits(0x0000, 4, 2) опять же, как в комментарии, мы можем визуализировать то, что делаем:

      0000111111111111   mask_p = ~0u >> p;
      0000001111111111   mask_n = mask_p >> n;
      0000110000000000   mask_p ^ mask_n

В качестве завершенной функции мы можем повторно использовать один mask переменная для этого:

unsigned invert_bits(unsigned x, unsigned p, unsigned n)
{
    unsigned mask = ~0u >> p;
    mask ^= mask >> n;
    return x ^ mask;
}

Приятно то, что он автоматически адаптируется к размеру шрифта без необходимости sizeof вообще в вычислении.


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

  1. Напишите ту же функцию, но предполагая, что вы считаете биты из крайний правый (наименее значимый) бит.
  2. Напишите версию для unsigned long.
  3. Напишите версии, которые безоговорочно устанавливают и сбрасывают указанную группу битов.
  4. Написать main() который проверяет, работают ли вышеуказанные функции как рекламируемые. Не забудьте вернуться EXIT_SUCCESS или же EXIT_FAILURE по мере необходимости.

Каким кодом можно поделиться в ответах на эти вопросы?

  • Вы кажетесь очень скромным, спасибо за добрый и очень четкий отзыв!

    — j3141592653589793238

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *