Несколько дней назад член 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;
Отрицательное значение дополнения до двух преобразуется в отрицательное значение дополнения до одного путем вычитания одного. С другой стороны, отрицательное значение дополнения одного преобразуется в отрицательное значение дополнения двух путем добавления единицы. Приведенный выше код работает, потому что знаковый бит используется для преобразования из дополнения до двух в дополнение и наоборот . Только отрицательные значения будут иметь свои биты знака; следовательно, знак переменной будет равен нулю, когда а положителен.
С учетом вышесказанного, можете ли вы вспомнить другие хитроумные хаки, подобные приведенному выше, которые превратили его в ваш пакет фокусов? Какой твой любимый битый хак? Я всегда ищу новые ориентированные на производительность хиты.
источник
Ответы:
Мне нравится взлом Госпера (HAKMEM # 175), очень хитрый способ получения числа и получения следующего числа с таким же количеством битов. Это полезно, например, для генерации комбинаций
k
предметов изn
:источник
Быстрый обратный квадратный метод корня использует большинство методов причудливого на битовом уровне для вычисления обратного квадратного корня , который я когда - либо видел:
источник
Деление на 3 - без обращения к вызову библиотеки во время выполнения.
Оказывается, что деление на 3 (благодаря подсказке о переполнении стека) может быть аппроксимировано как:
Х / 3 = [(х / 4) + (х / 12)]
И X / 12 есть (x / 4) / 3. Здесь внезапно появляется элемент рекурсии.
Также оказывается, что если вы ограничите диапазон чисел, в которых вы играете, вы можете ограничить количество необходимых итераций.
И, таким образом, для целых чисел без знака <2000 следующее является быстрым и простым алгоритмом / 3. (Для больших чисел просто добавьте больше шагов). Компиляторы оптимизируют эту черту, чтобы она была быстрой и маленькой:
источник
.0101010101
(примерно 1/3). Совет для профессионалов: вы также можете умножить на.000100010001
и101
(который занимает всего 3 битных сдвига, но имеет лучшее приближение.010101010101
Если вы посмотрите на Erlang, есть целый DSL для выполнения битовых операций. Таким образом, вы можете просто разбить структуру данных на части, сказав что-то вроде этого:
<> = << 1,17,42: 16 >>.
Полная информация здесь: http://www.erlang.org/doc/reference_manual/expressions.html#id75782
источник