Sunday, 23 June 2013

Armstrong numbers

Armstrong (or Narcissistic) numbers are a special set of integers.

An n-digit number is an Armstrong number if the sum of the n-th power of the digits is equal to the original number. For example, 371 is an Armstrong number because the sum of 3 cubed, 7 cubed and 1 cubed is equal to 371.

Here we compute all the Armstrong numbers of 2, 3 and 4 digits.

  • Power function
  • # this subroutine returns the passed digit to the passed power
    #
    # inputs
    #   r0 - digit
    #   r1 - power 
    #
    # outputs
    #   r0 - digit ** power
    #
    # locals
    #   r4
    .globl _power
    .align 2
            .text
    _power:
     nop
     stmfd sp!, {r4, lr}           @ save variables to stack
    
     subs r1, r1, #1               @ leave unless power > 1
     ble _power_end
    
     mov r4, r0                    @ copy digit
    _power_loop_start:
     mul r0, r4, r0                @ raise to next power
     subs r1, r1, #1  
     beq _power_end                @ leave when done
     b _power_loop_start           @ next iteration
    _power_end:
     ldmfd   sp!, {r4, pc}         @ restore state from stack and leave subroutime
    
  • Armstrong function
  • # inputs
    #   r0 - number
    #
    # outputs
    #   r0 - armstrong number
    #
    # local r4, r5, r6, r7, r8
    
    .equ ten,10
    .equ hundred,100
    .equ thousand,1000
    .equ ten_thousand,10000
    
    number .req r4
    width .req r5
    digit .req r6
    current .req r7
    armstrong .req r8
    
    .globl _armstrong
    .align 2
            .text
    _armstrong:
     nop
     stmfd   sp!, {r4, r5, r6, r7, r8, lr}          @ save variables to stack
    
     mov     number, r0                             @ copy passed parameter to working number
     cmp number, #ten                               @ exit unless number > 10
     blt _end
    
     ldr     current, =ten_thousand                 @ exit unless number < 10000
     cmp number, current
     bge _end
    
     mov width, #0                                  @ initialise
     mov digit, #0
     mov armstrong, #0
     ldr current, =thousand                         @ handle 1000 digit
    _thousand_start:
     cmp number, current
     blt _thousand_end                              @ exit thousand code if none left
     
     mov width, #4                                  @ width must be 4
     add current, current, #thousand                @ bump thousand counter
     add digit, digit, #1                           @ and corresponding digit count
     b _thousand_start                              @ and loop
    _thousand_end:
     add number, number, #thousand                  @ need number modulo thousand
     sub number, number, current
     mov r0, digit                                  @ push digit
     mov r1, width                                  @ and width
     bl _power                                      @ to compute digit **width
     add armstrong, r0, armstrong                   @ and update armstrong number with this value
    
     ldr current, =hundred                          @ then we do the hundreds as we did the thousands
     mov digit, #0
    _hundred_start:
     cmp number, current
     blt _hundred_end
     
     teq width, #0                                  @ and only set width if it is currently unset
     moveq width, #3
    _hundred_width:
     add current, current, #hundred                 @ yada yada as thousands above
     add digit, digit, #1
     b _hundred_start
    _hundred_end:
     add number, number, #hundred
     sub number, number, current
     mov r0, digit
     mov r1, width
     bl _power
     add armstrong, r0, armstrong
    
     ldr current, =ten                              @ then the tens as the hundred and thousands above
     mov digit, #0
    _ten_start:
     cmp number, current
     blt _ten_end
     
     teq width, #0
     moveq width, #2
    _ten_width:
     add current, current, #ten
     add digit, digit, #1
     b _ten_start
    _ten_end:
     add number, number, #ten
     sub number, number, current
     mov r0, digit
     mov r1, width
     bl _power
     add armstrong, r0, armstrong
    
     mov r0, number                                 @ then add in the trailing digits
     mov r1, width
     bl _power
     add armstrong, r0, armstrong
    
     mov r0, armstrong                              @ and copy the armstrong number back to r0 for return
    _end:
     ldmfd   sp!, {r4, r5, r6, r7, r8, pc}          @ restore state from stack and leave subroutine
    
    

    We improve the legibility of the code by e.g. number .req r4. This textual substitution does increase the semantic value of the code considerably.

  • Armstrong function with a macro to abstract repeated code
  • N.B. This is functionally equivalent but much shorter than the previous function. The variable \@ here is a magic variable, incremented each time the macro is instantiated. This enables the use of distinct labels, which we need here.

    # inputs
    #   r0 - number
    #
    # outputs
    #   r0 - armstrong number
    #
    # local r4, r5, r6, r7, r8
    
    .equ ten,10
    .equ hundred,100
    .equ thousand,1000
    .equ ten_thousand,10000
    
    number .req r4
    width .req r5
    digit .req r6
    current .req r7
    armstrong .req r8
    
    .macro armstrong_digit a, b
     ldr current, =\a
     mov digit, #0
    _start\@:
     cmp number, current
     blt _end\@
     
     teq width, #0                                  @ and only set width if it is currently unset
     moveq width, #\b
     add current, current, #\a
     add digit, digit, #1
     b _start\@
    _end\@:
     add number, number, #\a
     sub number, number, current
     mov r0, digit
     mov r1, width
     bl _power
     add armstrong, r0, armstrong
    .endm
    
    .globl _armstrong
    .align 2
            .text
    _armstrong:
     nop
     stmfd   sp!, {r4, r5, r6, r7, r8, lr}          @ save variables to stack
    
     mov     number, r0                             @ copy passed parameter to working number
     cmp number, #ten                               @ exit unless number > 10
     blt _end
    
     ldr     current, =ten_thousand                 @ exit unless number < 10000
     cmp number, current
     bge _end
    
     mov width, #0                                  @ initialise
     mov armstrong, #0
    
     armstrong_digit thousand 4
     armstrong_digit hundred 3
     armstrong_digit ten 2
    
     mov r0, number                                 @ then add in the trailing digits
     mov r1, width
     bl _power
     add armstrong, r0, armstrong
    
     mov r0, armstrong                              @ and copy the armstrong number back to r0 for return
    _end:
     ldmfd   sp!, {r4, r5, r6, r7, r8, pc}          @ restore state from stack and leave subroutine
    
    

    Here we introduce the use of the macro assembly functions of as Again this change leads to improved code - we eliminate duplication thus improving maintainability and legibility.

  • Armstrong_main function
  • .equ ten,10
    .equ ten_thousand,10000
    
    .section .rodata
     .align 2
    string:
     .asciz "armstrong number of %d is %d\n"
    .text
     .align 2
     .global main
     .type main, %function
    main:
     ldr r5, =ten
     ldr r6, =ten_thousand
    
     mov r4, r5            @ start with n = 10
    _main_loop:
     cmp r4, r6            @ leave if n = 10_000
     beq _main_end
    
     mov r0, r4            @ call the _armstrong function
     bl _armstrong
    
     teq r0, r4            @ if the armstong value = n print it
     bne _main_next        @ else skip
    
     mov r2, r0
     mov r1, r4
     ldr r0, =string       @ store address of start of string to r0
     bl printf             @ call the c function to display information
    _main_next:
     add r4, r4, #1
     b _main_loop
    _main_end:
     mov r7, #1            @ set r7 to 1 - the syscall for exit
     swi 0                 @ then invoke the syscall from linux
    
    
  • Result
  • bob@poland:~/src/problems_for_computer_solution/07_armstrong_numbers$ ./armstrong4macro 
    armstrong number of 153 is 153
    armstrong number of 370 is 370
    armstrong number of 371 is 371
    armstrong number of 407 is 407
    armstrong number of 1634 is 1634
    armstrong number of 8208 is 8208
    armstrong number of 9474 is 9474
    bob@poland:~/src/problems_for_computer_solution/07_armstrong_numbers$
    

No comments:

Post a Comment