<!--order=1-->


<h1>Bits, Bytes, and AArch64 Integer Values</h1>
<p>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.</p>

<h2>Bits, Bytes, and Binary</h2>
<p>A bit is a single binary digit: <code>0</code> or <code>1</code>. 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).</p>
<pre>
Binary:  00  01  10  11
Decimal:  0   1   2   3
</pre>
<p>Eight bits make one byte. The positions in an eight-bit byte have these values:</p>
<pre>
128 64 32 16 8 4 2 1
</pre>
<p>Therefore <code>11111111</code> is <code>255</code> in decimal. The range of an unsigned byte is <code>0</code> through <code>255</code>.</p>

<h2>Hexadecimal Is a Compact View of Binary</h2>
<p>Hexadecimal is base 16 and uses <code>0–9</code> followed by <code>A–F</code>. One hexadecimal digit represents four bits, so two hexadecimal digits represent one byte:</p>
<pre>
Binary:      0011 1110
Hexadecimal: 3    E
Decimal:     62
</pre>
<p>In portable C11, <code>0x</code> introduces a hexadecimal literal, so <code>0x3e</code> is a C value for decimal 62. C23 adds the <code>0b</code> prefix for binary literals, and many compilers or assemblers accept it as an extension; check the language mode before using <code>0b00111110</code>. The capitalization of hexadecimal digits is only a display choice.</p>

<h2>Groups of Bits</h2>
<p>The names below describe storage widths, not different kinds of arithmetic:</p>
<table>
  <thead><tr><th>Name</th><th>Bits</th><th>Hex digits</th><th>Unsigned maximum</th></tr></thead>
  <tbody>
    <tr><td>Byte</td><td>8</td><td>2</td><td>255</td></tr>
    <tr><td>Halfword</td><td>16</td><td>4</td><td>65,535</td></tr>
    <tr><td>Word</td><td>32</td><td>8</td><td>4,294,967,295</td></tr>
    <tr><td>Doubleword</td><td>64</td><td>16</td><td>18,446,744,073,709,551,615</td></tr>
  </tbody>
</table>
<p>Leading zeroes do not change a value. For example, <code>0x3e</code>, <code>0x003e</code>, and <code>0x000000000000003e</code> all represent decimal <code>62</code>; the padding shows the width being discussed.</p>

<h2>AArch64 General-Purpose Registers</h2>
<p>AArch64 has 31 general-purpose registers. The <code>xN</code> name refers to the full 64-bit register, while <code>wN</code> refers to its lower 32 bits. The register number stays the same:</p>
<pre>
x5  = all 64 bits of general-purpose register 5
w5  = the low 32 bits of general-purpose register 5
</pre>
<p>Writing a 32-bit result to a <code>wN</code> register also clears the upper 32 bits of the corresponding <code>xN</code> register. This is an architectural rule worth remembering when a 32-bit operation is followed by a 64-bit one.</p>

<h2>Signed and Unsigned Ranges</h2>
<p>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 <code>n</code>-bit signed integer ranges from <code>-2^(n-1)</code> through <code>2^(n-1)-1</code>.</p>
<table>
  <thead><tr><th>Width</th><th>Unsigned range</th><th>Signed range</th></tr></thead>
  <tbody>
    <tr><td>32 bits</td><td>0 to 4,294,967,295</td><td>-2,147,483,648 to 2,147,483,647</td></tr>
    <tr><td>64 bits</td><td>0 to 18,446,744,073,709,551,615</td><td>-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807</td></tr>
  </tbody>
</table>
<p>For example, <code>0xffffffff</code> is <code>4,294,967,295</code> when interpreted as an unsigned 32-bit value. The same 32 bits represent <code>-1</code> when interpreted as a signed two's-complement value. The bits did not change; only the interpretation did.</p>

<h2>Small AArch64 Experiment</h2>
<p>Use a compiler or assembler to make the width visible. This example puts a 32-bit value in <code>w0</code>, then returns it from a C-style function:</p>
<pre>
// value.S
.text
.global value
.type value, %function
value:
    mov w0, #62
    ret
</pre>
<p>Assemble it and inspect the symbol with:</p>
<pre>
$ aarch64-linux-gnu-as -o value.o value.S
$ aarch64-linux-gnu-objdump -d value.o
</pre>
<p>On an AArch64 machine, the native tools are usually named <code>as</code> and <code>objdump</code>. On another architecture, use the matching cross-toolchain and do not assume that the instruction encoding or register names will be the same.</p>

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

<h2>References</h2>
<ul>
  <li><a href="https://developer.arm.com/documentation/102374/latest" target="_blank" rel="noopener">Arm A64 Instruction Set Architecture Guide ↗</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Two%27s_complement" target="_blank" rel="noopener">Two's complement ↗</a></li>
  <li><a href="https://learn.sparkfun.com/tutorials/binary/bits-nibbles-and-bytes" target="_blank" rel="noopener">SparkFun — Bits, Nibbles, and Bytes ↗</a></li>
</ul>