An Armstrong number is a n-digit number that is equal to the sum of each of its digits taken to the nth power.
An example using this four digit number 1234:
1 ^ 4 + 2 ^ 4 + 3 ^ 4 + 4 ^ 4 = 354, which means it is not an Armstrong number.
The calculation could also be expressed like this, where n = number of digits.
abcd = pow(a, n) + pow(b, n) + pow(c, n) + pow(d,n)
Another example using 153 – a three digit number:
1 ^ 3 + 5 ^ 3 + 3 ^ 3 = 153, which means that 153 is an Armstrong number.
Why assembly
I do this just for fun, and I think it’s a good way to learn division and multiplication in x86 64 assembly language.
The logic
First I count the number of digits in the number. I do this by counting how many times the number can be divided by 10. I need to do this step, since I later on have to raise each digit to the power of the number of digits.
Then I do division once again to start calculating. For example using the number 153. I divide by 10, which gives me 15 and the remainder 3 (in rax). So I take 3 and raise it with 3 (3 x 3 x 3), and save it in r10. And then I do it again, starting with 15, dividing with 10, and so on.
In the assembly code I created a function – power(), do do the calculations. It takes two parameters. The actual number in rdi and then the power in rsi.
I use r8 to keep the number of digits, r9 to keep the remainder from the division and r10 for the sum.
The assembly code
global armstrong:function
section .bss
section .rodata
section .data
section .text
armstrong:
push rbx
push rcx
push rdx
mov rax, rdi
xor rcx, rcx
xor r10, r10 ; r10 = sum
mov rbx, 10
a1b:
inc rcx
xor rdx, rdx
cmp rax, 9
jbe a1
idiv rbx
jmp a1b
a1:
mov rax, rcx
mov r8, rax ; r8 = number of digits
mov rax, rdi
power_and_add_numbers:
cmp rcx, 0
je armstrong_done
xor rdx, rdx
idiv rbx ;
push rdi
mov r9, rax ; r9 = remaining
mov rdi, rdx
mov rsi, r8
call power
add r10, rax ; sum
pop rdi
mov rax, r9
dec rcx
jmp power_and_add_numbers
armstrong_done:
mov rax, rdi
mov rdi, r10 ; [sum]
sub rax, rdi ; if 0 => armstrong
pop rdx
pop rcx
pop rbx
ret
; x ^ y
; RDI = argv[1], RSI = argv[2]
power:
push rbx
push rcx
push rdx
cmp rsi, 0
je p0 ; any number ^ 0 = 1
mov rax, rdi
xor rbx, rbx
mov rcx, rsi
dec rcx
p1:
mov rdx, rdi
cmp rcx, 0
jle power_exit
mul rdx
dec rcx
jmp p1
p0:
mov rax, 1
power_exit:
pop rdx
pop rcx
pop rbx
ret
The C code that calls the assembly routine
This code simply takes a command line argument, makes it an integer and calls the assembly routine. If return code is 0, the number is an Armstrong number.
#include <stdio.h>
#include <stdlib.h>
extern int armstrong(int);
int main(int argc, char *argv[]) {
if(argc == 2) {
int a = atoi(argv[1]);
unsigned int c = armstrong(a);
if(c == 0) {
printf("%d is an Armstrong number.\n", a);
} else {
printf("%d is not an Armstrong number.\n", a);
}
}
}
The output
Some examples running the code.
![](https://matspetterss.wordpress.com/wp-content/uploads/2023/07/armstrong.png?w=766)
Improvements
Very much likely since I’m learning.
Leave a comment