Почему (% 256) отличается от (a & 0xFF)?
Я всегда предполагал, что при выполнении (a % 256)
оптимизатор, естественно, будет использовать эффективную побитовую операцию, как если бы я написал (a & 0xFF)
.
При тестировании в компиляторе gcc-6.2 (-O3):
// Type your code here, or load an example.
int mod(int num) {
return num % 256;
}
mod(int):
mov edx, edi
sar edx, 31
shr edx, 24
lea eax, [rdi+rdx]
movzx eax, al
sub eax, edx
ret
И при попытке другого кода:
// Type your code here, or load an example.
int mod(int num) {
return num & 0xFF;
}
mod(int):
movzx eax, dil
ret
Похоже, я полностью что-то пропустил.
Любые идеи?
Ответы
Ответ 1
Это не то же самое. Попробуйте num = -79
, и вы получите разные результаты от обеих операций. (-79) % 256 = -79
, а (-79) & 0xff
- некоторое положительное число.
Используя unsigned int
, операции будут одинаковыми, и код, вероятно, будет таким же.
PS - Кто-то прокомментировал
Они не должны быть одинаковыми, a % b
определяется как a - b * floor (a / b)
.
Это не так, как это определено в C, С++, Objective-C (т.е. все языки, на которых компилируется код в вопросе).
Ответ 2
Короткий ответ
-1 % 256
дает -1
, а не 255
, который равен -1 & 0xFF
. Поэтому оптимизация была бы неверной.
Длинный ответ
С++ имеет соглашение, что (a/b)*b + a%b == a
, что кажется вполне естественным. a/b
всегда возвращает результат арифметики без дробной части (усечение в сторону 0). Как следствие, a%b
имеет тот же знак, что и a
или равен 0.
Деление -1/256
дает 0
и, следовательно, -1%256
должно быть -1
, чтобы удовлетворить вышеуказанному условию ((-1%256)*256 + -1%256 == -1
). Это, очевидно, отличается от -1&0xFF
, который равен 0xFF
. Поэтому компилятор не может оптимизировать то, как вы хотите.
Соответствующий раздел в С++ standard [expr.mul §4] от N4606 гласит:
Для интегральных операндов оператор /
дает алгебраическое отношение с любой дробной частью, отброшенной; если частное выражение a/b
представимо в типе результата, (a/b)*b + a%b
равно a
[...].
Включение оптимизации
Однако с использованием типов unsigned
оптимизация будет полностью корректной, удовлетворяющей приведенному выше соглашению:
unsigned(-1)%256 == 0xFF
См. также this.
Другие языки
Это очень сильно отличается от разных языков программирования, так как вы можете найти Wikipedia.
Ответ 3
Так как С++ 11, num % 256
должен быть неположительным, если num
отрицательный.
Таким образом, бит-шаблон будет зависеть от реализации подписанных типов в вашей системе: для отрицательного первого аргумента результат не является извлечением наименее значимых 8 бит.
Было бы иначе, если num
в вашем случае был unsigned
: в наши дни я почти ожидал, что компилятор сделает оптимизацию, которую вы цитируете.
Ответ 4
У меня нет телепатического представления о рассуждениях компилятора, но в случае %
существует необходимость иметь дело с отрицательными значениями (и округлением до нуля), тогда как при &
результат всегда равен более низкие 8 бит.
Инструкция sar
звучит для меня как "арифметическое право сдвига", заполняя освобожденные биты знаковым значением знака.
Ответ 5
Математически, по модулю определяется следующее:
a% b = a - b * floor (a/b)
Это право здесь должно очистить его для вас. Мы можем исключить слово для целых чисел, потому что целочисленное деление эквивалентно полу (a/b). Однако, если компилятор должен использовать общий трюк, как вы предлагаете, он должен работать для всех a и всех b. К сожалению, это не так. Математически говоря, ваш трюк на 100% правильный для целых чисел без знака (я вижу, что число слов, объявленных целыми целыми разрядами ответа, прерывается, но я могу подтвердить или опровергнуть это, поскольку -a% b должен быть положительным). Однако, можете ли вы сделать этот трюк для всех b? Возможно нет. Вот почему компилятор этого не делает. В конце концов, если modulo были легко записаны как одна побитовая операция, мы бы просто добавили модульную схему, такую как добавление и другие операции.