Armstrong numbers in x86 assembly

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.

Improvements

Very much likely since I’m learning.


Posted

in

by

Tags:

Comments

Leave a comment

Design a site like this with WordPress.com
Get started