CodeRigor

FREE LESSON

Hexadecimal: nibbles, bytes & byte order

Read and write hex bytes, convert four-bit groups, and decode explicit little- and big-endian values without confusing display order with memory order.

Hexadecimal is base sixteen. Its digits are 0–9 and A–F, with A=10 through F=15. Each hex digit represents exactly four binary positions, called a nibble. You can translate each group independently because 16 is 2^4.

1. Group from the right

For an unsigned spelling, group binary positions in fours starting at the right. Pad the leftmost group with zeroes if necessary. The binary 101101 becomes 0010 1101: 2D in hex, 45 in decimal. The number 0x2D is the same value as 0b101101. Hexadecimal is a notation, not a signedness decision.

Hex Binary Hex Binary
0 0000 8 1000
1 0001 9 1001
2 0010 A 1010
3 0011 B 1011
4 0100 C 1100
5 0101 D 1101
6 0110 E 1110
7 0111 F 1111
Two nibbles make an eight-bit byte
Each hexadecimal digit preserves a group of four bits. Two hex digits describe an octet.
Two nibbles make an eight-bit byte
Each hexadecimal digit preserves a group of four bits. Two hex digits describe an octet.

An octet is exactly eight bits. A C++ byte is the size of char and has CHAR_BIT bits, at least eight. The course platform has eight-bit bytes and provides uint8_t. Keep this assumption explicit when describing files and protocols rather than claiming every possible C++ byte is an octet.

2. Separate digit order from byte order

The most-significant hex digit is written on the left, just as in decimal. That convention does not tell you how a multibyte value is stored in memory. The numeric value 0x1234 can be serialized as bytes {0x12,0x34} in big-endian order or {0x34,0x12} in little-endian order.

Interpret an ordered pair of bytes
For the same byte sequence, little-endian decoding instead yields 0x3412, or 13330. The input format chooses the interpretation.
Interpret an ordered pair of bytes
For the same byte sequence, little-endian decoding instead yields 0x3412, or 13330. The input format chooses the interpretation.

Endianness orders bytes, not the printed bits inside every digit. A hex dump shows a byte sequence; you need the file or protocol's word boundaries and byte-order contract to turn that sequence into integers. Printing an integer in hex does not reverse its digits on a little-endian machine.

C++
#include <array>
#include <cstdint>
#include <print>
int main() {
    const std::array<std::uint8_t, 2> bytes{0x12, 0x34};
    auto big = (std::uint64_t{bytes[0]} << 8) | bytes[1];
    auto little = (std::uint64_t{bytes[1]} << 8) | bytes[0];
    std::println("{:04X} {:04X}", big, little);
}

These arithmetic readers work independently of the host CPU's byte order. Avoid reinterpreting a pointer into an arbitrary byte buffer as an integer pointer: alignment, object lifetime and byte order are separate concerns.

3. Extract and combine nibbles

For a value in 0–255, byte >> 4 selects the high nibble, and byte & 0x0F selects the low one. To combine validated nibbles, shift the high one left four and OR in the low one. Validate each digit before that operation.

Decode one digit pair
The first digit supplies the high four bits. Reversing the two nibbles produces a different byte, 0x7A.
Decode one digit pair
The first digit supplies the high four bits. Reversing the two nibbles produces a different byte, 0x7A.
C++
#include <print>
int main() {
    constexpr unsigned byte = 0xA7;
    std::println("{} {}", byte >> 4, byte & 0x0F);
    std::println("{:02X}", (10u << 4) | 7u);
}

Use an unsigned value wide enough for the final shift before performing the shift. A uint8_t input often promotes to int; a large shift of that promoted int is not a 64-bit operation. For an eight-byte little-endian reader, uint64_t{bytes[i]} << (8*i) has the intended width, and the last count is 56.

4. Specify a text codec instead of guessing

The lab accepts complete pairs of hex digits, case-insensitively, and emits uppercase pairs. Empty byte text is valid. Odd length, non-hex digits, separators, spaces and the characters 0x are rejected. This wire-text API is not the C++ literal parser: its prefix and separator rules are intentional.

A leading zero byte must survive a round trip. For example 00AF10 denotes three bytes, even though an integer formatter might print only AF10. The word readers accept one through eight bytes and reject empty or longer input. Their explicit endian names tell callers how to interpret the sequence.

Test all 256 possible byte values, uppercase/lowercase input, zero bytes, malformed pairs, one-byte words and an eight-byte word whose high bit is set. Confirm that 12 34 decodes differently through the two word readers on the same machine. Then continue to one's complement.

Reading: Cambridge's number-system introduction connects bases and four-bit grouping. The number course keeps these lessons in prerequisite order.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

See why hexadecimal maps directly to four-bit groups and is useful for reading machine data.