Ecco il link a un documento di un algoritmo che produce i valori e il codice che vedo con Visual Studio (nella maggior parte dei casi) e che presumo sia ancora usato in GCC per la divisione di un intero variabile per un intero costante.
http://gmplib.org/~tege/divcnst-pldi94.pdf
Nell'articolo, un uword ha N bit, un udword ha 2N bit, n = numeratore = dividendo, d = denominatore = divisore, ℓ è inizialmente impostato su ceil (log2 (d)), shpre è pre-shift (usato prima di moltiplicare ) = e = numero di zero zero finali in d, shpost è post-shift (usato dopo moltiplicare), prec è precisione = N - e = N - shpre. L'obiettivo è ottimizzare il calcolo di n / d usando un pre-turno, moltiplicare e post-turno.
Scorri verso il basso fino alla figura 6.2, che definisce come viene generato un moltiplicatore udword (la dimensione massima è N + 1 bit), ma non spiega chiaramente il processo. Spiegherò di seguito.
La Figura 4.2 e la Figura 6.2 mostrano come il moltiplicatore può essere ridotto a un N bit o meno moltiplicatore per la maggior parte dei divisori. L'equazione 4.5 spiega come è stata derivata la formula utilizzata per gestire i moltiplicatori di bit N + 1 nelle figure 4.1 e 4.2.
Nel caso del moderno X86 e di altri processori, il tempo di moltiplicazione è fisso, quindi il pre-shift non aiuta su questi processori, ma aiuta comunque a ridurre il moltiplicatore da N + 1 bit a N bit. Non so se GCC o Visual Studio abbiano eliminato il pre-shift per gli obiettivi X86.
Tornando alla Figura 6.2. Il numeratore (dividendo) per mlow e mhigh può essere maggiore di un udword solo quando denominatore (divisore)> 2 ^ (N-1) (quando ℓ == N => mlow = 2 ^ (2N)), in questo caso il la sostituzione ottimizzata per n / d è un confronto (se n> = d, q = 1, altrimenti q = 0), quindi non viene generato alcun moltiplicatore. I valori iniziali di mlow e mhigh saranno N + 1 bit e due divisioni udword / uword possono essere utilizzate per produrre ciascun valore N + 1 bit (mlow o mhigh). Usando X86 in modalità 64 bit come esempio:
; 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
Puoi provarlo con GCC. Hai già visto come viene gestita j = i / 5. Dai un'occhiata a come viene gestita j = i / 7 (che dovrebbe essere il caso del moltiplicatore N + 1 bit).
Sulla maggior parte dei processori attuali, moltiplicare ha una tempistica fissa, quindi non è necessario un pre-turno. Per X86, il risultato finale è una sequenza di due istruzioni per la maggior parte dei divisori e una sequenza di cinque istruzioni per divisori come 7 (per emulare un moltiplicatore N + 1 bit come mostrato nell'equazione 4.5 e nella figura 4.2 del file pdf). Esempio codice 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
; ...