Ось посилання на документ алгоритму, який створює значення та код, які я бачу з Visual Studio (у більшості випадків), і який я припускаю, що все ще використовується в GCC для поділу змінного цілого числа на постійне ціле число.
http://gmplib.org/~tege/divcnst-pldi94.pdf
У статті uword має N біт, udword має 2N біт, n = чисельник = дивіденд, d = знаменник = дільник, initially спочатку встановлюється на ceil (log2 (d)), shpre є попередньою зміною (використовується перед множенням ) = e = кількість кінцевих нульових бітів у d, шпост - післязсувний (використовується після множення), прецизія - точність = N - е = N - шпре. Мета - оптимізувати обчислення n / d за допомогою попереднього зсуву, множення та післязсуву.
Прокрутіть униз до рисунка 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 біт, і два поділи udword / uword можна використовувати для отримання кожного значення N + 1 біт (mlow або mhigh). Використання 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
; ...