Integer and floating point maths
Integer maths
Integer maths is easily done using the x registers. The ARM64 instruction set provides "add", "sub", "mul", and "div" to perform basic integer maths. Note that while adding, subtracting and multiplying integers always results in an integer result, division can also result in a floating point result.
That is, 8 ÷ 4 = 2 while 8 ÷ 3 = 2.25 which would be truncated to 2.
This can be tested with the following code:
// maths_operations.s
.global main
.extern printf
.data
add_out:
.asciz "%d + %d = %d\n"
subt_out:
.asciz "%d - %d = %d\n"
mult_out:
.asciz "%d x %d = %d\n"
div_out:
.asciz "%d / %d = %d\n"
num1:
.quad 8
num2:
.quad 3
.text
main:
// Prolog
stp x29, x30, [sp, -16]!
mov x29, sp
// Main code
ldr x0, =add_out // Load x0 with output string
ldr x1, =num1 // Load x1 with addr of num1
ldr x1, [x1] // Load x1 with value of num1
ldr x2, =num2 // Load x2 with addr of num2
ldr x2, [x2] // load x2 with value of num2
add x3, x1, x2 // x3 = x1 + x2
bl printf
ldr x0, =subt_out
ldr x1, =num1
ldr x1, [x1]
ldr x2, =num2
ldr x2, [x2]
sub x3, x1, x2 // x3 = x1 - x2
bl printf
ldr x0, =mult_out
ldr x1, =num1
ldr x1, [x1]
ldr x2, =num2
ldr x2, [x2]
mul x3, x1, x2 // x3 = x1 * x2
bl printf
ldr x0, =div_out
ldr x1, =num1
ldr x1, [x1]
ldr x2, =num2
ldr x2, [x2]
sdiv x3, x1, x2 // x3 = x1 / x2, both operands are signed
bl printf
ldr x0, =div_out
ldr x1, =num1
ldr x1, [x1]
ldr x2, =num2
ldr x2, [x2]
udiv x3, x1, x2 // x3 = x1 / x2, both operands are unsigned
bl printf
// Cleanup
mov x0, #0
ldp x29, x30, [sp], 16
RET
The only thing to note is the two different divisions used at the end.
They are "sdiv" and "udiv". Sdiv is signed division where the signs of the operands are considered, udiv is unsigned division where the operands are treated as unsigned integers.
In the code shown we expect both to produce the same output.
andrew@master:~/assm2 $ ./maths_operations_2
8 + 3 = 11
8 - 3 = 5
8 x 3 = 24
8 / 3 = 2
8 / 3 = 2
The output is as expected.
In the next example, 8 will be replaced with -8. Observe the output of the final division.
andrew@master:~/assm2 $ ./maths_operations_2
-8 + 3 = -5
-8 - 3 = -11
-8 x 3 = -24
-8 / 3 = -2
-8 / 3 = 1431655762
The result for the final calculation is explained as the processor interpreting -8 as an unsigned 64 bit integer. There is an issue here in that 8 as a 64 bit signed integer would be
0 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 000
Therefore the 64 bit signed representation of -8 would be:
1 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1110 111
As an unsigned 64 bit integer this would be 18,446,744,073,709,551,613.
If this is divided by 3 the result is 6,148,914,691,236,516,864 which is not what is shown in the output. The output shown is too small and I don't know why.
Floating point maths
The only operation that needs to be demonstrated is division but "everything in pairs" so multiplicaiton will also be demonstrated.
// maths_with_floats
.global main
.extern printf
.section .data
output_mult:
.asciz "%f x %f = %f\n"
output_div:
.asciz "%f / %f = %f\n"
num1:
.double 3.125
num2:
.double 2.667
.section .text
main:
// Prolog
stp x29, x30, [sp, -16]!
mov x29, sp
// Main code
ldr x0, =output_mult // Output string in x0
ldr x1, =num1 // Addr of num1
ldr d0, [x1] // value at addr into d0
ldr x1, =num2 // Addr of num2
ldr d1, [x1] // Value at addr into d1
fmul d2, d1, d0 // d2 = d0 * d1
bl printf
ldr x0, =output_div
ldr x1, =num1
ldr d0, [x1]
ldr x1, =num2
ldr d1, [x1]
fdiv d2, d0, d1 // d2 = d0 / d1
bl printf
// Cleanup
mov x0, #0
ldp x29, x30, [sp], 16
RET
Note the use of the d or double precision registers. The addresses are loaded into standard 64 bit registers and the d registers are loaded with the values stored at those addresses.