Let's take the number x=27
. Divide by 10 and
get the quotient 2 and remainder 7. So 7 is the last digit
and we do the same with 2. Divide by 10 gives 0 — meaning
we are done — and remainder
2. So put 2 in front of 7 and we get 27
.
Looking for a integer divide with remainder in the
instruction set, but such a thing does not exist. We
have udiv
which
can get the quotient, but not the remainder.
When the quotient is 2 we know 27=2*10+r
, where r
is the remainder we want, but then r=27-2*10
, and by
magic there is exactly such an instruction msub
saying
multiply and subtract.
udiv
take 2 arguments, and below we have the 27 in x0
,
the 10 in x1
, and the result is then found in x2
.
msub
takes 3 arguments, and in
the code below we would have the 27 in x0
,
the 10 in x1
, and the 2 in x2
. The result is then found in x3
.
So integer divide with remainder is always two instructions.
_pdec: stp fp,lr,[sp, #-16]!
stp x0,x1,[sp, #-16]!
stp x2,x3,[sp, #-16]!
mov fp,sp
sub sp,sp,#0x20
mov x8,fp
mov x1,#10 /* line feed */
strb w1,[x8]
sub x8,x8,#1
mov x2,x0
mov x1,#10
loop: mov x0, x2
udiv x2, x0, x1 /* magic: finds remainder and */
msub x3, x2, x1, x0 /* magic: quotient divided by 10 */
/* convert binary to ascii digit */
add x3, x3, #48
strb w3, [X8]
sub x8, x8 ,#1
cmp x2, #0
bne loop
mov x0,1 /* print to stdout */
mov x1,x8 /* string */
add x1,x1,#1 /* we are one byte ahead of the start */
sub x2,fp,x8 /* length of string */
mov x8,0x40
svc 0
add sp,sp,#0x20 /* standard prologue */
ldp x2, x3, [sp], #16
ldp x0, x1, [sp], #16
ldp fp, lr, [sp], #16
ret
All the stuff around the udiv
and msub
are more or less standard
stuff: there is a string on the stack, x8
points to the address of
the next character in the string, running backwards. And we start by
putting a newline at the end of the string.
You should put this code in say lib4.s
&mdash as described before.
The call it a few times, e.g.:
mov x0,7913
bl _pdec
mov x0,0xFFFFFFFFFFFFFFFF
bl _pdec
add x0,x0,1
bl _pdec
add x0,x0,1
bl _pdec
This should print 7913, followed by the largest unsigned 64-bit integer possible. Adding 1 to that then gives 0, and adding 1 again gives 1.
By the way, that is a surprise: we can't just put a 64-bit address into
x0
in a single instruction. So the assembler is cheating us? Yeah, go
find out how. Turns out it sees this is as -1, and knows how.
mov x0,0xFFCFFCFFCFFCFFCF
gives an Error: immediate cannot be moved by a single instruction
But we can always:
ldr x0,bignum
and then somewhere close:
bignum: .dword 0xFFCFFCFFCFFCFFCF
How do we handle signed 64-bit integers. Don't ask me, as I don't know. Yet.
2025-07-05