AW Dev Rethought

✂️ Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away - Antoine de Saint-Exupéry

🔢 Python Information Lab — Bits, Bytes, and Encoding 🐍


Description:

The number 65 and the letter A can produce the same eight bits: 01000001. So what makes those bits a number or a letter?

The representation rules do. This lab follows values into bytes, displays their bits, and converts them back.


Bits and bytes

A bit has two possible values: 0 and 1. A byte contains eight bits, giving 2⁸ = 256 possible patterns. Read as an unsigned integer, those patterns represent 0–255.

For 01000001, the selected positional weights are 64 and 1:

Weights: 128 64 32 16  8  4  2  1
Bits:      0  1  0  0  0  0  0  1
Value:    64 + 1 = 65

Choose a representation for a number

value = 65
data = value.to_bytes(1, byteorder="big", signed=False)
print(data.hex())                    # 41
print(f"{data[0]:08b}")              # 01000001
print(int.from_bytes(data, "big"))   # 65

Here we explicitly choose one unsigned byte. The binary format string creates a readable text display; to_bytes creates the actual byte sequence.

The chosen width limits which values fit: encoding 256 in one unsigned byte raises OverflowError. The 08b formatting width, by contrast, is a minimum display width and does not enforce an eight-bit range.

These are serialized bytes, not a measurement of the Python integer object's memory footprint. See Python's integer conversion documentation.


Give the same byte a different meaning

letter_bytes = "A".encode("utf-8")
print(letter_bytes.hex())                   # 41
print(letter_bytes == data)                 # True
print(letter_bytes.decode("utf-8"))         # A

The byte is identical. Interpreting it as an unsigned integer yields 65; interpreting it as UTF-8 text yields A. A file format, protocol, or program establishes which interpretation is intended.


One code point does not imply one byte

Unicode assigns code points to characters. An encoding specifies how text becomes bytes. These are separate ideas.

text = "\u00e9"  # Precomposed é: U+00E9
encoded = text.encode("utf-8")

print(len(text))                                    # 1
print(len(encoded))                                 # 2
print(encoded.hex(" "))                             # c3 a9
print(" ".join(f"{byte:08b}" for byte in encoded))   # 11000011 10101001

len(text) counts Unicode code points; len(encoded) counts bytes. A visible character can also involve multiple code points, so code-point counts are not always counts of what a reader perceives as characters. This example deliberately uses the single, precomposed code point. See the Python Unicode HOWTO.


Wrong decoding can succeed

print(encoded.decode("utf-8"))    # é
print(encoded.decode("latin-1"))  # é
assert encoded.decode("utf-8") == text

Latin-1 interprets the two bytes as two separate code points: U+00C3 and U+00A9. It raises no error, but the result is not the intended text. Successful decoding does not prove that the chosen encoding was correct.


Multi-byte numbers need a byte order

number = 258
big = number.to_bytes(2, "big")
little = number.to_bytes(2, "little")

print(big.hex(" "))                  # 01 02
print(little.hex(" "))               # 02 01
print(int.from_bytes(big, "big"))     # 258
print(int.from_bytes(big, "little"))  # 513

Big-endian stores the most significant byte first. Little-endian stores it last. Byte order rearranges whole bytes; it does not reverse the bits within each byte.


Run the notebook

Open Bits_Bytes_and_Encoding.ipynb in Jupyter and run the cells in order. The examples need no additional packages or imports. Assertions verify the conversions and round trips.

Takeaway: agree on the representation before interpreting the bits. For integers, specify width, signedness, and byte order. For text, specify the encoding.


Link copied!

Comments

Add Your Comment

Comment Added!