Почему такой сложный код испущен для деления целого числа со знаком на два?
Когда я компилирую этот код с VС++ 10:
DWORD ran = rand();
return ran / 4096;
Я получаю эту разборку:
299: {
300: DWORD ran = rand();
00403940 call dword ptr [__imp__rand (4050C0h)]
301: return ran / 4096;
00403946 shr eax,0Ch
302: }
00403949 ret
который является чистым и кратким и заменяет деление на две силы с логическим сдвигом вправо.
Но когда я компилирую этот код:
int ran = rand();
return ran / 4096;
Я получаю эту разборку:
299: {
300: int ran = rand();
00403940 call dword ptr [__imp__rand (4050C0h)]
301: return ran / 4096;
00403946 cdq
00403947 and edx,0FFFh
0040394D add eax,edx
0040394F sar eax,0Ch
302: }
00403952 ret
который выполняет некоторые манипуляции, прежде чем делать правильный арифметический сдвиг.
Какая потребность в этих дополнительных манипуляциях? Почему недостаточно арифметического сдвига?
Ответы
Ответ 1
Причина в том, что беззнаковое деление на 2 ^ n может быть реализовано очень просто, тогда как подписанное деление несколько сложнее.
unsigned int u;
int v;
u / 4096
эквивалентен u >> 12
для всех возможных значений u
.
v / 4096
НЕ эквивалентен v >> 12
- он ломается, когда v < 0
, так как направление округления отличается для переключения по отношению к делению, когда задействованы отрицательные числа.
Ответ 2
"дополнительные манипуляции" компенсируют тот факт, что арифметический сдвиг вправо округляет результат к отрицательной бесконечности, тогда как деление округляет результат к нулю.
Например, -1 >> 1
- -1
, тогда как -1/2
- 0
.
Ответ 3
Из стандарта C:
Когда целые числа делятся, результатом оператора/является алгебраическое отношение с любой дробной частью, отброшенной .105) Если фактор a/b является представимым, выражение (a/b) * b + a% b должно равен a; в противном случае поведение как a/b, так и a% b undefined.
Не трудно думать о примерах, где отрицательные значения для а не следуют этому правилу с чистым арифметическим сдвигом. Например.
(-8191) / 4096 -> -1
(-8191) % 4096 -> -4095
которая удовлетворяет уравнению, тогда как
(-8191) >> 12 -> -2 (assuming arithmetic shifting)
не является делением с усечением, поэтому -2 * 4096 - 4095
, безусловно, не равно -8191.
Обратите внимание, что сдвиг отрицательных чисел на самом деле определяется реализацией, поэтому выражение C (-8191) >> 12
не имеет в целом правильного результата в соответствии со стандартом.