Bytes, bits, hex and number bases
Binary and hex notation, byte ranges, converting between bases by hand and in code, and how to read a hex dump without confusing offsets with data.
Three notations for the same value
A byte is eight bits and holds 256 distinct values. Hex exists because four bits map exactly to one hex digit, so a byte is always exactly two hex characters — no arithmetic required to read it.
| Decimal | Binary | Hex | Context |
|---|---|---|---|
| 0 | 0000 0000 | 00 | Null byte, often a terminator |
| 10 | 0000 1010 | 0A | Line feed |
| 13 | 0000 1101 | 0D | Carriage return |
| 32 | 0010 0000 | 20 | Space |
| 65 | 0100 0001 | 41 | Uppercase A in ASCII |
| 127 | 0111 1111 | 7F | DEL, the end of 7-bit ASCII |
| 128 | 1000 0000 | 80 | First value outside ASCII |
| 255 | 1111 1111 | FF | Largest byte value |
n = 0x41 # hex literal, value 65
bin(n) # '0b1000001'
hex(n) # '0x41'
f"{n:08b}" # '01000001' eight bits, zero padded
f"{n:02X}" # '41' two hex digits, uppercase
int("ff", 16) # 255
int("11111111", 2) # 255
# shifting is how you pack and unpack bytes
high, low = (n >> 4) & 0xF, n & 0xF
packed = (high << 4) | low # 0x41 againReading a hex dump
00000000 50 4b 03 04 14 00 00 00 08 00 21 8a 4c 5d 19 3c |PK........!.L].<|
00000010 d4 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000020 68 65 6c 6c 6f 2e 74 78 74 0a 77 6f 72 6c 64 0a |hello.txt.world.|
^ ^
byte offset (hex) ASCII rendering- The left column is the offset of the first byte on the line, in hex — not part of the data.
- Each pair of hex digits is one byte; the ASCII column on the right is a convenience view.
- A dot in the ASCII column means the byte is not printable, not that the byte is a dot.
50 4b 03 04is the ZIP signaturePKfollowed by the local file header marker.
xxd file.bin | head
hexdump -C file.bin | head
xxd -s 0x20 -l 16 file.bin # 16 bytes starting at offset 32
printf 'hello' | xxd # 68656c6c6fByte order and where it bites
import struct
value = 0x01020304
struct.pack(">I", value) # b'\x01\x02\x03\x04' big endian
struct.pack("<I", value) # b'\x04\x03\x02\x01' little endian
# reading a fixed-width field from a binary header
length = struct.unpack_from("<I", data, 8)[0]💡
Network protocols are big endian by convention, most CPUs are little endian, and some file formats pick either. Always state the endianness when you read or write a multi-byte integer, and never assume the host order.
FAQ
Why do programmers use hex instead of decimal?
Because four bits map to exactly one hex digit, so any byte is two characters. Addresses, colours and magic numbers are far easier to read and byteswap in hex than in decimal.
What is a magic number?
A fixed byte sequence at the start of a file that identifies its format:
50 4b for ZIP, 89 50 4e 47 for PNG, ff d8 ff for JPEG. Tools use it to detect the type regardless of the file extension.Related
Character sets: ASCII, Latin-1 and Windows-1252 UTF-16, UTF-32, BOM and endianness
Last refreshed 2026-09-18.