# Decimal to binary conversion in ARM assembly

I'm "playing" with ARM assembly on a Raspberry Pi, reading various tutorials and posts to help - no, it's not a college assignment, I'm too old for college! I'm pleased with getting the following code to take a decimal value (between 0 and 255) and output the binary string equivalent to the screen using scanf() and printf() but curious to know though whether those more experienced would consider this a "naive" solution? Are there any "gotchas"? Any better ways of tackling the problem?

I reckon there should be a better way of declaring the string to hold the output and how the string is built from the LSB (using R9)

/* dectobin2.s                                                  */
/* ------------------------------------------------------------ */
/* Converts a decimal number to binary and outputs to screen    */
/* ============================================================ */

.data
.balign 4
value:      .word   0
binStr:     .asciz  "00000000"
output:     .asciz  "%d in binary is %s\n"
title:      .asciz  "DECIMAL TO BINARY CONVERTER\n\n"
lr_temp:    .word   0
lr_local:   .word   0
prompt:     .asciz  "Enter a positive value (0-255) > "
pattern:    .asciz  "%d"

.text
@ function to convert given value into a binary string
decToBin:
ldr     r2, =lr_local
str     lr, [r2]
@ set data values
mov     r4, r0              @ value in r4 for processing
mov     r9, #7              @ LSB position in output string
_loop:
@ repeated division by 2 2
movs    r4, r4, lsr #1      @ divide by 2, set carry
bcs     _odd                @ if carry set, remainder is 1
_even:
mov     r5, #0              @ remainder of 0
b       _toString
_odd:
mov     r5, #1              @ remainder of 1
_toString:
add     r5, #48             @ convert remainder to ASCII
ldr     r1, =binStr         @ address of binary string
strb    r5, [r1, r9]        @ store remainder in string at pos in r9
sub     r9, r9, #1          @ move to next position in output string
cmp     r4, #0              @ reached zero?
ble     _endloop
b       _loop

_endloop:
ldr     lr, =lr_local
ldr     lr, [lr]
bx      lr

.global main

main:
ldr     r1, =lr_temp
str     lr, [r1]

@ print a title
ldr     r0, =title
bl      printf
_getValue:
@ get the value to convert
ldr     r0, =prompt         @ prep the arguments for scanf
bl      printf
ldr     r0, =pattern
ldr     r1, =value
bl      scanf

@ check for valid input (0-255)
ldr     r0, =value
ldr     r0, [r0]
cmp     r0, #255            @ must be <= 255
bgt     _getValue
cmp     r0, #0              @ must be >= 0
blt     _getValue

_isValid:
@ call the function, value is in R0
bl      decToBin

@ display output
ldr     r0, =output         @ get address of output string
ldr     r1, =value          @ get the original value
ldr     r1, [r1]            @ for first parameter
ldr     r2, =binStr         @ get the binary string
bl      printf              @ print the result

@ restore the link register and exit
ldr     lr, =lr_temp
ldr     lr, [lr]
bx      lr

.global printf
.global scanf
$$$$


## Understand the calling convention

The procedure call standard for ARM says that the registers r0, r1, r2, and r3 can be used and not restored by a called function. Further, if we don't alter lr, there's no need to preserve and then restore it. These are all clues for making your program more efficient.

The code currently contains these lines:

    movs    r4, r4, lsr #1      @ divide by 2, set carry
bcs     _odd                @ if carry set, remainder is 1
_even:
mov     r5, #0              @ remainder of 0
b       _toString
_odd:
mov     r5, #1              @ remainder of 1
_toString:


That is terribly inefficient. We can simplify considerably:

    mov     r5, #48             @ start with ASCII '0'
movs    r4, r4, lsr #1      @ divide by 2, set carry



## Minimize branching

The code ends with this curious combination:

    ble     _endloop
b       _loop
_endloop:


First, the comment suggests that we're looking for exactly zero and so we should be looking only at the zero flag (beq or bne) rather than doing a signed comparison with ble. Second, why not simply use a single instruction?

    beq     _loop


However, better, see the next suggestion.

## Create reusable code

The code currently exits if the shifted value becomes zero. That works, but only once. In other words, if you use the routine to decode 255 and then again to decode the value 3, you will likely be surprised that your routine will falsely give the same binary string for both. The problem is that it relies on the binStr buffer to always contain a string of zeroes. A better approach is to explicitly set each byte of the buffer and instead use the counter you already have in r9. This shortens the code and makes it reusable.

    subs    r9, r9, #1
bpl     _loop


## Move loop invariants outside the loop

Every iteration through the loop we load the r1 register with the address of binStr even though r1 is never changed within the loop. A more efficient approach would be to load r1 once outside the loop.

## Document register usage

One of the keys to being a good assembly language programmer is to carefully manage register use. The most basic requirement is to keep track of how you're using registers, and a good way to do that, both for yourself and future readers of the code is to document the use. Based on the observations in the first suggestion above, here's what I used:

@ Register usage:
@   r0 - passed value
@   r1 - pointer to current string digit
@   r2 - decrementing digit offset
@   r3 - the current digit


## Results

Using all of these suggestions, the resulting routine is only nine instructions.

decToBin:
ldr     r1, =binStr         @ output string location
_loop:
movs    r0, r0, lsr #1      @ divide by 2, set carry
strb    r3, [r1, r2]        @ store remainder in string at pos in r9
subs    r2, r2, #1          @ decrement counter
bpl     _loop               @ end if it has gone negative
bx      lr


Armed with these suggestions (pun intended!) see if you can perform a similar cleanup on the rest of the code. In particular, there is a much more efficient way to check if the input value is in the appropriate range (hint: movs r1, r0, lsr #8).

Also, just for fun, this answer was composed and tested entirely on a Raspberry Pi running on battery power.

## Other ideas

A more generally useful routine would have the caller pass a buffer pointer and buffer length into which the program would put its result rather than using a fixed location. See if you can figure out how to modify the code to do that, and don't forget to place the terminating \0` character.

• Thank you Edward for the detailed comments, it's much appreciated. As I've done some further reading since posting I've now encountered the conditionals that can be suffixed to any of the instructions to remove the need to branch, as you've suggested - they're a nice touch! I shall revisit with your other tips - thank you. Commented Feb 22, 2021 at 16:19