Вот ссылка на документ алгоритма, который создает значения и код, который я вижу в Visual Studio (в большинстве случаев), и который, как я полагаю, все еще используется в GCC для деления целого числа переменной на целое число константы.
http://gmplib.org/~tege/divcnst-pldi94.pdf
В этой статье uword имеет N битов, udword имеет 2N битов, n = числитель = дивиденд, d = знаменатель = делитель, initially изначально установлен в ceil (log2 (d)), shpre является предварительным сдвигом (используется перед умножением ) = e = количество завершающих нулевых битов в d, shpost - пост-сдвиг (используется после умножения), prec - точность = N - e = N - shpre. Цель состоит в том, чтобы оптимизировать расчет н / д с использованием до сдвига, умножения и постсдвига.
Прокрутите вниз до рисунка 6.2, который определяет, как генерируется множитель udword (максимальный размер N + 1 бит), но не дает четкого объяснения процесса. Я объясню это ниже.
Рисунок 4.2 и рисунок 6.2 показывают, как множитель может быть уменьшен до множителя N бит или меньше для большинства делителей. Уравнение 4.5 объясняет, как была получена формула, используемая для работы с N + 1 битовыми умножителями на рисунках 4.1 и 4.2.
В случае современных X86 и других процессоров время умножения является фиксированным, поэтому предварительное смещение не помогает этим процессорам, но все же помогает уменьшить множитель с N + 1 бит до N бит. Я не знаю, исключили ли GCC или Visual Studio предварительный сдвиг для целей X86.
Возвращаясь к рисунку 6.2. Числитель (дивиденд) для mlow и mhigh может быть больше, чем вымышленное слово, только когда знаменатель (делитель)> 2 ^ (N-1) (когда ℓ == N => mlow = 2 ^ (2N)), в этом случае Оптимизированная замена для n / d - это сравнение (если n> = d, q = 1, иначе q = 0), поэтому множитель не генерируется. Начальные значения mlow и mhigh будут составлять N + 1 бит, и для получения каждого значения N + 1 бита (mlow или mhigh) можно использовать два деления udword / uword. Используя X86 в 64-битном режиме в качестве примера:
; upper 8 bytes of dividend = 2^(ℓ) = (upper part of 2^(N+ℓ))
; lower 8 bytes of dividend for mlow = 0
; lower 8 bytes of dividend for mhigh = 2^(N+ℓ-prec) = 2^(ℓ+shpre) = 2^(ℓ+e)
dividend dq 2 dup(?) ;16 byte dividend
divisor dq 1 dup(?) ; 8 byte divisor
; ...
mov rcx,divisor
mov rdx,0
mov rax,dividend+8 ;upper 8 bytes of dividend
div rcx ;after div, rax == 1
mov rax,dividend ;lower 8 bytes of dividend
div rcx
mov rdx,1 ;rdx:rax = N+1 bit value = 65 bit value
Вы можете проверить это с GCC. Вы уже видели, как обрабатывается j = i / 5. Посмотрите, как обрабатывается j = i / 7 (это должен быть случай умножения N + 1).
На большинстве современных процессоров умножение имеет фиксированную синхронизацию, поэтому предварительная смена не требуется. Для X86 конечный результат представляет собой последовательность из двух команд для большинства делителей и последовательность из пяти команд для делителей, таких как 7 (для того, чтобы эмулировать множитель N + 1 бита, как показано в уравнении 4.5 и на рисунке 4.2 файла PDF). Пример кода X86-64:
; rax = dividend, rbx = 64 bit (or less) multiplier, rcx = post shift count
; two instruction sequence for most divisors:
mul rbx ;rdx = upper 64 bits of product
shr rdx,cl ;rdx = quotient
;
; five instruction sequence for divisors like 7
; to emulate 65 bit multiplier (rbx = lower 64 bits of multiplier)
mul rbx ;rdx = upper 64 bits of product
sub rbx,rdx ;rbx -= rdx
shr rbx,1 ;rbx >>= 1
add rdx,rbx ;rdx = upper 64 bits of corrected product
shr rdx,cl ;rdx = quotient
; ...