Het probleem komt waarschijnlijk omdat de
div operator zoals de intel x86 die heeft om (unsigned) integers te delen, als deeltal een 64-bit getal neemt (edx:eax), als deler een 32-bit getal en als resultaat een 32-bit quotient (eax) en een 32-bit rest (edx). Dit geeft een probleem als het resultaat niet in 32-bit past, je krijgt dan een overflow.
Omdat een standaard deling met 64-bits getallen zoals in jouw code ook gedaan wordt ook moet werken met quotienten groter dan 32-bit is er een library functie nodig om dit te realiseren, vandaar de aullrem call. aullrem is een functie die van twee 64-bits getallen de 64-bit deler bepaald.
Voor sommige gevallen, zoals een deler van < 32-bits (jouw geval bijvoorbeeld) zijn snelle oplossingen mogelijk, alleen moet aullrem dit eerst bepalen omdat die functie zo'n garantie natuurlijk niet heeft maar gewoon altijd z'n werk moet doen. Je kunt de snelle oplossing ook zelf maken, het scheelt je de check die aullrem doet om te kijken of dit mogelijk is (omdat je het zelf garandeert), en de functie call natuurlijk.
Het hele probleem komt dus neer op dat de div-opcode alleen een 64-bits getal kan delen waarvan het quotient in 32-bits past. Als de deler ook in 32-bits past is dit met een truc mogelijk met 2 div's.
Een 64-bits getal kun je opsplitsen in een hoog gedeelte (H) van 32-bits en een laag gedeelte (L) van evenveel bits. Je wilt de rest van dit getal hebben, dus modulo P:
rest = (H * 2^32 + L) mod P
Delen door P en de rest uitlezen kan soms niet omdat de waarde te groot is. Je kunt dit echter oplossen door H te vervangen door (H mod P), omdat dat modulo P gewoon hetzelfde getal is. Dus je neemt H2 = H % P en krijgt:
rest = (H2 * 2^32 + L) mod P
H2 is gegarandeerd kleiner dan P (het is immers de rest van een deling met P), wat betekent dat als je H2 * 2^32 deelt door P je ook nooit over de 2^32 grens heen kunt komen. Zelfs als L z'n maximale waarde heeft en erbij opgeteld wordt kun je na deling met P nooit een quotient groter dan 32-bit krijgen. De rest zal echter nog steeds hetzelfde zijn als bij de eerste formule, omdat H2 en H gelijk zijn binnen modulo P.
Tot zo ver het wiskundige gedoe

, misschien niet nodig om de implementatie te maken maar wel aardig om te weten lijkt me. De implementatie kun je in assembly op de volgende manier maken: neem het hoge gedeelte van het deeltal (H) als 32-bit getal, en bepaal de rest met P. Gebruik deze rest als nieuwe H. Nu kun je het 64-bits getal H:L veilig delen door P zonder overflow te krijgen.
GAS:
1
2
3
4
5
6
7
8
9
10
11
12
| ; edx:eax is het 64-bits getal waarvan
; de rest bepaald moet worden. [p] is de
; 32-bits deler.
mov ebx, [p] ; ebx = deler
mov ecx, eax ; ecx = low 32-bit van deeltal (L)
mov eax, edx ; eax = high 32-bit van deeltal (H)
xor edx, edx ; edx = 0 (edx:eax = eax)
div ebx ; H / P, eax = quotient, edx = deler (H2)
mov eax, ecx ; edx:eax = H2:L
div ebx ; H2:L / P, eax = quotient, edx = deler
; rest zit nu in edx |
Overigens gebruikt aullrem soortgelijke code, maar dus wel met een extra check of de deler 32-bits is.