Đây là liên kết đến một tài liệu về thuật toán tạo ra các giá trị và mã mà tôi thấy với Visual Studio (trong hầu hết các trường hợp) và tôi giả sử vẫn được sử dụng trong GCC để chia một số nguyên biến cho một số nguyên không đổi.
http://gmplib.org/~tege/divcnst-pldi94.pdf
Trong bài viết, một uword có N bit, một udword có 2 bit, n = tử số = cổ tức, d = denominator = divisor, initially ban đầu được đặt thành ceil (log2 (d)), shpre là dịch chuyển trước (được sử dụng trước khi nhân ) = e = số bit zero trailing trong d, shpost là post-shift (được sử dụng sau khi nhân), pre là precision = N - e = N - shpre. Mục tiêu là để tối ưu hóa tính toán của n / d bằng cách sử dụng trước ca, nhân và sau ca.
Cuộn xuống hình 6.2, định nghĩa cách tạo một số nhân udword (kích thước tối đa là N + 1 bit), nhưng không giải thích rõ ràng quy trình. Tôi sẽ giải thích điều này dưới đây.
Hình 4.2 và hình 6.2 cho thấy cách nhân số nhân có thể giảm xuống một số nhân N hoặc ít hơn cho hầu hết các ước số. Công thức 4.5 giải thích cách thức công thức được sử dụng để xử lý các bội số bit N + 1 trong hình 4.1 và 4.2.
Trong trường hợp X86 hiện đại và các bộ xử lý khác, thời gian nhân được cố định, do đó, dịch chuyển trước không giúp ích gì cho các bộ xử lý này, nhưng nó vẫn giúp giảm hệ số nhân từ bit N + 1 xuống N bit. Tôi không biết liệu GCC hoặc Visual Studio đã loại bỏ dịch chuyển trước cho các mục tiêu X86.
Quay trở lại Hình 6.2. Tử số (cổ tức) cho mlow và mhigh có thể lớn hơn một từ chỉ khi mẫu số (ước số)> 2 ^ (N-1) (khi == N => mlow = 2 ^ (2N)), trong trường hợp này thay thế tối ưu cho n / d là so sánh (nếu n> = d, q = 1, khác q = 0), do đó không có số nhân nào được tạo. Các giá trị ban đầu của mlow và mhigh sẽ là N + 1 bit và hai phép chia udword / uword có thể được sử dụng để tạo ra mỗi giá trị bit N + 1 (mlow hoặc mhigh). Sử dụng X86 ở chế độ 64 bit làm ví dụ:
; 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
Bạn có thể kiểm tra điều này với GCC. Bạn đã thấy cách xử lý j = i / 5. Hãy xem cách xử lý j = i / 7 (nên là trường hợp số nhân N + 1 bit).
Trên hầu hết các bộ xử lý hiện tại, bội số có thời gian cố định, do đó không cần dịch chuyển trước. Đối với X86, kết quả cuối cùng là một chuỗi hai lệnh cho hầu hết các ước và một chuỗi năm lệnh cho các ước như 7 (để mô phỏng hệ số nhân N + 1 bit như trong phương trình 4.5 và hình 4.2 của tệp pdf). Mã 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
; ...