Aquí hay un enlace a un documento de un algoritmo que produce los valores y el código que veo con Visual Studio (en la mayoría de los casos) y que supongo que todavía se usa en GCC para la división de un entero variable por un entero constante.
http://gmplib.org/~tege/divcnst-pldi94.pdf
En el artículo, una uword tiene N bits, una udword tiene 2N bits, n = numerador = dividendo, d = denominador = divisor, ℓ se establece inicialmente en ceil (log2 (d)), shpre es pre-shift (usado antes de multiplicar ) = e = número de bits cero finales en d, shpost es posterior al desplazamiento (utilizado después de la multiplicación), prec es precisión = N - e = N - shpre. El objetivo es optimizar el cálculo de n / d usando un pre-turno, multiplicación y post-turno.
Desplácese hasta la figura 6.2, que define cómo se genera un multiplicador de udwords (el tamaño máximo es N + 1 bits), pero no explica claramente el proceso. Explicaré esto a continuación.
La Figura 4.2 y la Figura 6.2 muestran cómo el multiplicador puede reducirse a un N bit o menos multiplicador para la mayoría de los divisores. La ecuación 4.5 explica cómo se obtuvo la fórmula utilizada para tratar con multiplicadores de N + 1 bit en las figuras 4.1 y 4.2.
En el caso de los procesadores X86 modernos y otros, el tiempo de multiplicación es fijo, por lo que el cambio previo no ayuda en estos procesadores, pero sí ayuda a reducir el multiplicador de N + 1 bits a N bits. No sé si GCC o Visual Studio han eliminado el cambio previo para los objetivos X86.
Volviendo a la Figura 6.2. El numerador (dividendo) para mlow y mhigh puede ser mayor que una udword solo cuando denominador (divisor)> 2 ^ (N-1) (cuando ℓ == N => mlow = 2 ^ (2N)), en este caso el El reemplazo optimizado para n / d es una comparación (si n> = d, q = 1, sino q = 0), por lo que no se genera un multiplicador. Los valores iniciales de mlow y mhigh serán N + 1 bits, y se pueden usar dos divisiones udword / uword para producir cada valor de N + 1 bit (mlow o mhigh). Usando X86 en modo de 64 bits como ejemplo:
; 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
Puedes probar esto con GCC. Ya has visto cómo se maneja j = i / 5. Eche un vistazo a cómo se maneja j = i / 7 (que debería ser el caso del multiplicador de N + 1 bit).
En la mayoría de los procesadores actuales, multiplicar tiene un tiempo fijo, por lo que no es necesario un cambio previo. Para X86, el resultado final es una secuencia de dos instrucciones para la mayoría de los divisores, y una secuencia de cinco instrucciones para divisores como 7 (para emular un multiplicador de N + 1 bit como se muestra en la ecuación 4.5 y la figura 4.2 del archivo pdf). Código de ejemplo 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
; ...