Bits, Bytes, and AArch64 Integer Values

Assembly language becomes easier to read when the values in memory and registers have names you can reason about. This guide builds the vocabulary from bits and bytes up to the signed and unsigned integer ranges used by AArch64 general-purpose registers.

Bits, Bytes, and Binary

A bit is a single binary digit: 0 or 1. Each position to the left is worth twice the position before it. The rightmost position is the least significant bit (LSB); the leftmost position is the most significant bit (MSB).

Binary:  00  01  10  11
Decimal:  0   1   2   3

Eight bits make one byte. The positions in an eight-bit byte have these values:

128 64 32 16 8 4 2 1

Therefore 11111111 is 255 in decimal. The range of an unsigned byte is 0 through 255.

Hexadecimal Is a Compact View of Binary

Hexadecimal is base 16 and uses 0–9 followed by A–F. One hexadecimal digit represents four bits, so two hexadecimal digits represent one byte:

Binary:      0011 1110
Hexadecimal: 3    E
Decimal:     62

In portable C11, 0x introduces a hexadecimal literal, so 0x3e is a C value for decimal 62. C23 adds the 0b prefix for binary literals, and many compilers or assemblers accept it as an extension; check the language mode before using 0b00111110. The capitalization of hexadecimal digits is only a display choice.

Groups of Bits

The names below describe storage widths, not different kinds of arithmetic:

NameBitsHex digitsUnsigned maximum
Byte82255
Halfword16465,535
Word3284,294,967,295
Doubleword641618,446,744,073,709,551,615

Leading zeroes do not change a value. For example, 0x3e, 0x003e, and 0x000000000000003e all represent decimal 62; the padding shows the width being discussed.

AArch64 General-Purpose Registers

AArch64 has 31 general-purpose registers. The xN name refers to the full 64-bit register, while wN refers to its lower 32 bits. The register number stays the same:

x5  = all 64 bits of general-purpose register 5
w5  = the low 32 bits of general-purpose register 5

Writing a 32-bit result to a wN register also clears the upper 32 bits of the corresponding xN register. This is an architectural rule worth remembering when a 32-bit operation is followed by a 64-bit one.

Signed and Unsigned Ranges

The same bit pattern can be interpreted as either unsigned or signed. Unsigned values use every bit for magnitude. Signed two's-complement values use the MSB as the sign indicator, so an n-bit signed integer ranges from -2^(n-1) through 2^(n-1)-1.

WidthUnsigned rangeSigned range
32 bits0 to 4,294,967,295-2,147,483,648 to 2,147,483,647
64 bits0 to 18,446,744,073,709,551,615-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

For example, 0xffffffff is 4,294,967,295 when interpreted as an unsigned 32-bit value. The same 32 bits represent -1 when interpreted as a signed two's-complement value. The bits did not change; only the interpretation did.

Small AArch64 Experiment

Use a compiler or assembler to make the width visible. This example puts a 32-bit value in w0, then returns it from a C-style function:

// value.S
.text
.global value
.type value, %function
value:
    mov w0, #62
    ret

Assemble it and inspect the symbol with:

$ aarch64-linux-gnu-as -o value.o value.S
$ aarch64-linux-gnu-objdump -d value.o

On an AArch64 machine, the native tools are usually named as and objdump. On another architecture, use the matching cross-toolchain and do not assume that the instruction encoding or register names will be the same.

Takeaways

  • Bits are the individual values; bytes and larger groups describe width.
  • Hexadecimal makes binary easier to read because one digit maps to four bits.
  • xN names a 64-bit AArch64 register and wN names its low 32 bits.
  • Signedness changes how a bit pattern is interpreted; it does not change the bits.
  • Always state the width and signedness when debugging low-level code.

References