Three weeks ago I posted Multiplying big numbers using Karatsuba's method where I made reference to my version of the classical long multiplication. That's what I'm posting today so people can compare for themselves. At the same time I'm hoping someone still sees ways to further improve this code!
The multi-precision mpMul procedure that I present below requires 4 parameters passed on the stack.
- The 1st param specifies the precision (length in bits) of both the inputs which needs to be a multiple of 64.
- The 2nd param points to the 1st input aka multiplicand.
- The 3rd param points to the 2nd input aka multiplier.
- The 4th param points to where the double length result needs to go.
The address of the double length product is returned in the EAX
register and the carry flag will be set if the result exceeds the precision.
I applied the following ideas:
I detect leading and trailing zeroes beforehand. All leading zeroes can simply be ignored. For each trailing zero in the multiplicand or multiplier, a new trailing zero is inserted in the result.
Since detecting embedded zeroes came at no apparant cost, appropriate shortcuts were installed.
I let the shorter number control the outer loop, thus reducing the number of times the costlier inner loop has to run.
I try to avoid having to enter the carry-loop as much as possible.
Computing MAX_UINT^2, the number of multiplications is \${4}^{(n-5)}\$ where \$n=\log_2\text{Precision}\$
; -------------------------------------
; Multiplying using long multiplication
; -------------------------------------
; IN () OUT (eax,CF)
mpMul: pushad
mov ebp, esp
mov edx, [ebp+32+4] ;IntSize {64, 128, 192, ...}
shr edx, 5 ;Bits -> dwords
mov edi, [ebp+32+4+12] ;BufferC (double length result)
mov [ebp+28], edi ;pushad.EAX
push edx edi ;(1)
imul eax, edx, 8
pxor xmm0, xmm0
.Wipe: sub eax, 16 ;Clear result buffer
movdqu [edi+eax], xmm0
jnz .Wipe
mov esi, [ebp+8+32+4+8] ;BufferB (default multiplier)
mov ebx, [ebp+8+32+4+4] ;BufferA (default multiplicand)
call .Prune ; -> ECX ESI EDI
xchg ebx, esi
mov ebp, ecx
call .Prune ; -> ECX ESI EDI
cmp ebp, ecx
jbe .MultiplierLoop ;Multiplier 'is shorter' Multiplicand
xchg esi, ebx
xchg ecx, ebp
; - - - - - - - - - - - - - - - - - -
.MultiplierLoop:
cmp dword [ebx], 0 ;Embedded zero in multiplier
je .Zero
push ecx esi edi ;(2)
.MultiplicandLoop:
add edi, 4
lods dword [esi]
test eax, eax ;Embedded zero in multiplicand
jz .Done
mul dword [ebx]
add [edi-4], eax
adc [edi], edx
jnc .Done
adc dword [edi+4], 0 ;(*) Avoid entering the carry-loop
jnc .Done
mov eax, edi
.Carry: add eax, 4 ;The carry-loop
add dword [eax+4], 1 ;(*) +4
jc .Carry
.Done: dec ecx
jnz .MultiplicandLoop
pop edi esi ecx ;(2)
.Zero: add edi, 4
add ebx, 4
dec ebp
jnz .MultiplierLoop
; - - - - - - - - - - - - - - - - - -
.END: pop edi ecx ;(1)
lea edi, [edi+ecx*4] ;Middle of double length result
xor eax, eax
repe scas dword [edi] ;Defines CF
popad
ret 16
; - - - - - - - - - - - - - - - - - -
; IN (eax=0,edx,esi,edi) OUT (ecx,esi,edi)
.Prune: mov ecx, edx ;Precision expressed as dwords (2+)
.Lead: dec ecx ;Skip leading zeroes
js .Quit ;Multiplier and/or Multiplicand is 0
cmp [esi+ecx*4], eax ;EAX=0
je .Lead
inc ecx ;Significant dwords
cmp [esi], eax ;EAX=0
jne .Ready
.Trail: dec ecx ;Skip trailing zeroes
add esi, 4 ;Skip low dwords that are zero
add edi, 4 ; by moving starting points upwards
cmp [esi], eax ;EAX=0
je .Trail
.Ready: ret
.Quit: pop eax ;Forget 'call .Prune'
jmp .END
; -----------------------------------