Print a number in any base without help

48879 printed three times, as BEEF, as 48879 and as 1011111011101111, by a subroutine that turns a number into characters itself. Task 15 does the same job in one request; this is what it does inside, and it is the program every language writes once and then hides in a library.

The only task this program uses is the one that prints a string. Everything between a number in a register and characters in a buffer is written out here.

    move.l #48879, d0       ; n = 48879
    move.l #16, d1          ; in hexadecimal
    bsr print_in_base
    move.l #48879, d0
    move.l #10, d1          ; in decimal
    bsr print_in_base
    move.l #48879, d0
    move.l #2, d1           ; in binary
    bsr print_in_base
    move.b #9, d0
    trap #15

* print_in_base(n, base): n in d0, base in d1
print_in_base:
    lea buffer_end, a1      ; build the text backwards from the end
    clr.b -(a1)             ; the terminator goes down first
digit:
    move.l d0, d2
    divu d1, d2             ; d2 = n / base, with n % base above it
    move.l d2, d3
    swap d3
    andi.l #$FFFF, d3       ; the digit
    andi.l #$FFFF, d2       ; n = n / base
    cmp.b #9, d3
    bhi letter
    add.b #'0', d3          ; 0 to 9 become '0' to '9'
    bra store
letter:
    add.b #'A'-10, d3       ; 10 and up become 'A' and up
store:
    move.b d3, -(a1)        ; in front of the digits we have already
    move.l d2, d0           ; n = n / base, and the move sets Z
    bne digit               ; until nothing is left of it
    move.b #13, d0          ; task 13: print the string at a1 and a new line
    trap #15
    rts

    org $2000
buffer:     ds.b 34         ; 32 binary digits, the terminator and a spare byte
buffer_end:

Dividing by the base and keeping the remainder gives you one digit, and it is the lowest one: 48879 divided by 16 is 3054 with 15 left over, and 15 is the F at the right hand end of BEEF. So the digits arrive in the opposite order to the one they are printed in, and the two ways round that are to reverse the buffer afterwards or to write it backwards in the first place. -(a1) does the second one for nothing: it subtracts 1 from a1 and then writes, so every digit lands in front of the ones already there and a1 is left pointing at the first character.

A digit is a number from 0 to base - 1 and it has to become a character. '0' is $30, so adding it turns 0 to 9 into '0' to '9'; 'A' is $41 and a 10 has to become that, so the amount added is 'A'-10, worked out by the assembler while it assembles. cmp.b #9, d3 and bhi pick between the two, which is what makes a base up to 36 work.

move.l d2, d0 is the loop's condition as well as its assignment: move sets Z from what it moved, so bne under it means "if there is anything left of n, go round again". The buffer has 34 bytes because the longest answer is a 32 bit number in base 2, and after the binary run a1 comes out at 00002011, seventeen bytes down from buffer_end.

36 is as far as the digits reach, since after 9 there are only 26 letters to carry on with.