I started learning assembly today and I wrote a program to print the factorial of a number. It worked great for single-digit factorials but when I made some changes for it to work with larger numbers, I started getting an unexpected character at the beginning of the factorial. The character that the factorial begins with changes for every number.
Here is my code:
section .data
msg db 'Enter a number: ',0 ;content of user prompt
lenmsg equ $ - msg ;length of user prompt
disp db 'Factorial: ',0 ;content of output
lendisp equ $ - disp ;length of output
newline db 0xa ;newline char
section .bss
num resb 3 ;2 bytes for number , 1 byte for newline
fac resb 12 ;12 bytes for Factorial
section .text
global _start
_start:
;Display User Prompt
mov edx,lenmsg ;number of bytes to write
mov ecx,msg ;content to write
mov ebx,1 ;file descriptor : stdout
mov eax,4 ;system call : sys_write
int 0x80 ;call kernel
;Accept User Input
mov edx,2 ;number of bytes to read
mov ecx,num ;input stored at num
mov ebx,0 ;file descriptor : stdin
mov eax,3 ;system call : sys_read
int 0x80 ;call kernel
mov byte [num+eax-1],0 ;replace newline with null
;Calculate Factorial
movzx eax, byte [num] ;move num to eax, replace leading places with zeroes
sub eax,'0' ;convert ascii to integer
mov ecx,eax ;transfer to ecx , acts as counter
mov eax,1 ;eax takes value of 1, acts as factorial
cmp ecx,0 ;checks if input is 0
jz output ;if input is 0 it goes directly to output
loop:
mul ecx ;multiply
dec ecx ;decrease counter
jnz loop ;if ecx > 0, loop through
output:
;Convert integer to ASCII
mov ebx,fac+11 ;fac is 12 bytes long, fac+11 goes to end of fac
mov byte [ebx],0 ;add null character
xor ecx,ecx ;clear ecx , acts as counter
convert:
xor edx,edx ;clear edx
mov ecx,10 ;divisor
div ecx ;eax /= ecx, edx contains remainder
add dl,'0' ;last 8 bits of edx contains the digit, convert it into a string by adding ascii number of '0'
dec ebx ;move buffer pointer, we save each digit in a different position
mov [ebx],dl ;store character
inc ecx ;ecx works as a general purpose register AND as a counter at the SAME TIME
test eax,eax ;works like bitwise AND
mov [fac],bl ;store result to fac
jnz convert ;if eax != 0, continue looping
;Display Output Message
mov edx,lendisp ;length of output message
mov ecx,disp ;content of output message
mov ebx,1 ;file descriptor : stdout
mov eax,4 ;system call : sys_write
int 0x80 ;call kernel
;Display Factorial
mov edx,ecx ;length of factorial
mov ecx,fac ;content of factorial
mov ebx,1 ;file descriptor : stdout
mov eax,4 ;system call : sys_write
int 0x80 ;call kernel
;Display Newline
mov edx,1 ;length of newline
mov ecx,newline ;content of newline
mov ebx,1 ;file descriptor : stdout
mov eax,4 ;system call : sys_write
int 0x80 ;call kernel
;Exit Code
mov eax,1 ;system call : sys_exit
int 0x80 ;call kernel
This is the output that I got:
Enter a number: 2
Factorial: -2
Enter a number: 3
Factorial: -6
Enter a number: 4
Factorial: ,24
Enter a number: 5
Factorial: +120
Enter a number: 6
Factorial: +720
Enter a number: 7
Factorial: *5040
Enter a number: 8
Factorial: )40320
Enter a number: 9
Factorial: (362880
I am using NASM version 2.16.01 on Ubuntu 24.04.1 LTS.
Please help me.
When you're ready to display the factorial, you currently first display a message. This destroys the count that you think you have established in ECX during the conversion loop.
The conversion loop also has some meaningless content!
This is what you need:
;Display Output Message
mov edx, lendisp
mov ecx, disp
mov ebx, 1
mov eax, 4
int 0x80
;Convert integer to ASCII
mov ecx, fac+12 ; Better build address in ECX, ready for output
xor ebx, ebx ; Count in any temp register
mov edi, 10 ; CONST throughout the loop
convert:
xor edx, edx
div edi
add dl,'0'
dec ecx
mov [ecx], dl
inc ebx
test eax, eax
jnz convert
;Display Factorial
mov edx, ebx
mov ebx, 1
mov eax, 4
int 0x80
See that by putting the message before the conversion, you can more freely use the registers.
Since both 0! and 1! are 1, separate both at once. Then stop the loop earlier:
mov eax, 1
cmp ecx, eax
jbe output
loop:
imul eax, ecx ; Non-widening is better (you don't use EDX)
dec ecx
cmp ecx, 1
jne loop
output: