Несколько дней назад член StackExchange Анто спросил о допустимых применениях для побитовых операторов. Я заявил, что сдвиг быстрее, чем умножение и деление целых чисел на степени двух. Члену StackExchange Daemin ответили, заявив, что смещение вправо представляет проблемы с отрицательными числами.
В тот момент я никогда не задумывался об использовании операторов сдвига со знаковыми целыми числами. Я прежде всего использовал эту технику в разработке программного обеспечения низкого уровня; поэтому я всегда использовал целые числа без знака. C выполняет логические сдвиги на целых числах без знака. При выполнении логического сдвига вправо не учитывается знаковый бит. Свободные биты заполнены нулями. Однако C выполняет операцию арифметического сдвига при смещении целого числа со знаком вправо. Освобожденные биты заполняются знаковым битом. Эта разница приводит к тому, что отрицательное значение округляется до бесконечности, а не усекается до нуля, что отличается от целочисленного деления со знаком.
Несколько минут размышлений привели к решению первого порядка. Решение условно преобразует отрицательные значения в положительные значения перед сдвигом. Значение условно преобразуется обратно в его отрицательную форму после выполнения операции сдвига.
int a = -5;
int n = 1;
int negative = q < 0;
a = negative ? -a : a;
a >>= n;
a = negative ? -a : a;
Проблема этого решения состоит в том, что операторы условного присваивания обычно транслируются по меньшей мере в одну инструкцию перехода, и инструкции перехода могут быть дорогостоящими на процессорах, которые не декодируют оба пути команд. Необходимость повторной заливки конвейера инструкций дважды оказывает влияние на любой выигрыш в производительности, полученный при переключении на деление.
С учетом вышесказанного я проснулся в субботу с ответом на проблему условного назначения. Проблема округления, с которой мы сталкиваемся при выполнении операции арифметического сдвига, возникает только при работе с представлением дополнения до двух. Это не происходит с представлением своего дополнения. Решение проблемы включает в себя преобразование значения дополнения до двух в значение дополнения до выполнения операции сдвига. Затем мы должны преобразовать значение дополнения до значения дополнения до двух. Удивительно, но мы можем выполнить этот набор операций без условного преобразования отрицательных значений перед выполнением операции сдвига.
int a = -5;
int n = 1;
register int sign = (a >> INT_SIZE_MINUS_1) & 1
a = (a - sign) >> n + sign;
Отрицательное значение дополнения до двух преобразуется в отрицательное значение дополнения до одного путем вычитания одного. С другой стороны, отрицательное значение дополнения одного преобразуется в отрицательное значение дополнения двух путем добавления единицы. Приведенный выше код работает, потому что знаковый бит используется для преобразования из дополнения до двух в дополнение и наоборот . Только отрицательные значения будут иметь свои биты знака; следовательно, знак переменной будет равен нулю, когда а положителен.
С учетом вышесказанного, можете ли вы вспомнить другие хитроумные хаки, подобные приведенному выше, которые превратили его в ваш пакет фокусов? Какой твой любимый битый хак? Я всегда ищу новые ориентированные на производительность хиты.