Kevin Boone

ARM assembly-language programming for the Raspberry Pi

15. A useable binary-to-decimal conversion

This example shows a more useful way to convert from binary to decimal, which could easily be extended to convert to other number bases. It introduces two new functions -- itoa2 which writes the converted number into memory, and print_num which uses itoa2 to print the result to the console. All the calculation will be carried out using memory allocated on the stack.

The example

The complete listing is rather long, so I'm only showing the new parts. The new functions rely on strlen, print_str, mod, and reverse are unchanged from previous examples.

/* =========================== itoa2 ========================================*/
// A better attempt at an itoa function. This version takes two arguments:
//   %r0 - the number to be converted
//   %r1 - address to store resulting digits
// This version also copes with negative numbers. It uses the reverse
//   function from the previous example to put the digits (and the sign)
//   in the proper order.

itoa2:
    push   {r0-r5, lr}

    mov    %r4, $0    // %r4 will remember whether the supplied number
                      //   was negative 
    mov    %r3, %r1   // %r3 contains the address to store the data,
                      //  originally passed in r1
    mov    %r5, %r1   // So does %r5. %r3 will be incremented when building
                      //  the number, so we need to track the original
                      //  start address when it comes time to reverse
                      //  the digits.

    mov    %r2, %r0   // Keep the running total in %r2
    mov    %r1, $10   // Keep the const 10 in r1

    cmp    %r0, $0    // Is the number to be converted less than zero?
    bge    itoa2_loop // If it isn't, skip the negation step

    neg    %r2, %r2   // If we have a negative number, make it positive 
    mov    %r4, $1    //   ... and keep track of the fact it was negative

itoa2_loop:
  
    mov    %r0, %r2
    bl     mod         // Divide running total by 10. mod is now in %r0

    add    %r0, $48    // Add '0' to make the number into an ASCII digit
    strb   %r0, [%r3]  // Store the digit at the address in %r3 
    add    %r3, $1     //   ... and then increment %r3 for the next digit

    sdiv   %r2, %r1    // Divide the running total by 10 
    cmp    %r2, $0     // If there's anything left, repeat the division
    bne    itoa2_loop    

    cmp    %r4, $1     // If %r4 = 1, we started with a negative number...
    bne    itoa2_pos

    mov    %r0, $45    //  ... so store a negative sign (char 45)
    strb   %r0, [%r3]  //  ... at the position %r3 now points
    add    %r3, $1     //  ... and increment %r3 so we can...
itoa2_pos:

    mov    %r0, $0     // Store a null to finish the number string
    strb   %r0, [%r3] 

    mov    %r0, %r5   
    bl     reverse     // Reverse the digits, including the sign. This is
                       //  why we wrote the sign on the end of the number.

    pop    {r0-r5, lr}
    bx     lr

/* =========================== print_num ====================================*/
print_num:
PRINTNUM_LOCAL = 16             // Allow space for the largest number,
                                //  plus the minus sign, plus the null;
                                //  Round up to nearest 8 bytes

    push   {r0, r1, fp, lr}     // Store the registers we will overwrite
    sub    sp, $PRINTNUM_LOCAL  // Move the stack _down_ to allow for our data
    mov    %fp, %sp             // %fp references the start of our work area

    mov    %r1, %fp             // For call to itoa2 we need:
    bl     itoa2                //   %r0 = number, %r1 = work area

    mov    %r0, %fp             // Set %r0 to point to the converted number
    bl     print_str            // Print it

    add    sp, $PRINTNUM_LOCAL  // Move the stack pointer over our data area
    pop    {r0, r1, fp, lr}     // and restore the registers
    bx     lr

/* =========================== start ========================================*/
_start:
    ldr    %r0, =12345      // Number to be converted goes in %r0
    bl     print_num        // Print it 

    ldr    %r0, =EOL        // Print a newline, to make the output clearer
    bl     print_str

    ldr    %r0, =-32720     // Let's try a negative number 
    bl     print_num        // Print it 

    ldr    %r0, =EOL        // Print a newline
    bl     print_str

    // exit
    mov    %r0, $0
    b      exit

The itoa2 function

This function takes two arguments -- the binary number to be converted in r0, and the address of an area of memory to write the result in r1. It is the caller's responsibility to allocate an area of memory large enough to hold the converted decimal string -- the itoa method does not do this check. Indeed, it can't check.

itoa2 implements a signed conversion -- that is, the number to be converted is treated as signed. If it is negative, then the function negates it (that is, makes it positive), and remembers that it needs to put a minus sign on the front of the number. Because the converion process gets the converted digits in the wrong order, we will need to use reverse to correct this. This has the added complication that we need to ensure that the minus sign ends up on the front of the number, not the end. We can do this by putting the minus sign on the end before reversing, or putting it at the front and reversing only the part of the string after the minus sign. I've chosen the first approach, but I don't think there's any particular advantage to one or the other.

The print_num function

This function takes a (signed) number in a register, and prints the decimal representation to the console. This function uses the stack technique we've discussed before to allocate temporary storage on the stack. This storage has to be large enough to hold the largest possible decimal number, plus the minus sign, plus the terminating null. Fortunately, it's not difficult to work out this size.

print_num calls itoa2 to do the conversion, passing the temporary storage area in the stack as the memory value in r1 This temporary memory will be lost when print_num returns, but that's fine -- the number has been printed, and we no longer care about the intermediate results.

Summary