Aqui está o link para um documento de um algoritmo que produz os valores e o código que eu vejo no Visual Studio (na maioria dos casos) e que suponho que ainda seja usado no GCC para dividir um número inteiro variável por um número inteiro constante.
http://gmplib.org/~tege/divcnst-pldi94.pdf
No artigo, uma palavra-chave tem N bits, uma palavra-chave tem 2N bits, n = numerador = dividendo, d = denominador = divisor, ℓ é definido inicialmente como teto (log2 (d)), shpre é pré-deslocamento (usado antes da multiplicação ) = e = número de bits zero à direita em d, shpost é pós-deslocamento (usado após multiplicar), prec é precisão = N - e = N - shpre. O objetivo é otimizar o cálculo de n / d usando um pré-turno, multiplicação e pós-turno.
Role para baixo até a figura 6.2, que define como um multiplicador de udword (o tamanho máximo é de N + 1 bits) é gerado, mas não explica claramente o processo. Vou explicar isso abaixo.
A Figura 4.2 e a Figura 6.2 mostram como o multiplicador pode ser reduzido a um multiplicador de N bits ou menos para a maioria dos divisores. A Equação 4.5 explica como a fórmula usada para lidar com multiplicadores de bits N + 1 na figura 4.1 e 4.2 foi derivada.
No caso do X86 moderno e de outros processadores, o tempo de multiplicação é fixo, portanto a pré-mudança não ajuda nesses processadores, mas ainda ajuda a reduzir o multiplicador de N + 1 bits para N bits. Não sei se o GCC ou o Visual Studio eliminaram a pré-mudança para destinos X86.
Voltando à Figura 6.2. O numerador (dividendo) para mlow e mhigh pode ser maior que uma palavra-chave somente quando denominador (divisor)> 2 ^ (N-1) (quando ℓ == N => mlow = 2 ^ (2N)), nesse caso, o a substituição otimizada para n / d é uma comparação (se n> = d, q = 1, senão q = 0), portanto, nenhum multiplicador é gerado. Os valores iniciais de mlow e mhigh serão N + 1 bits, e duas divisões udword / uword podem ser usadas para produzir cada valor de N + 1 bit (mlow ou mhigh). Usando o X86 no modo de 64 bits como exemplo:
; 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
Você pode testar isso com o GCC. Você já viu como j = i / 5 é tratado. Veja como j = i / 7 é tratado (que deve ser o caso multiplicador de N + 1 bit).
Na maioria dos processadores atuais, a multiplicação tem um tempo fixo, portanto não é necessário um pré-turno. Para X86, o resultado final é uma sequência de duas instruções para a maioria dos divisores e uma sequência de cinco instruções para divisores como 7 (para emular um multiplicador de N + 1 bit, como mostrado na equação 4.5 e na figura 4.2 do arquivo pdf). Exemplo de código 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
; ...