Exercise 3: A Negative Integer, and Why Signedness Matters — Possible Solution ==================================================================== THE TEST ------------------------------ schema5 = Schema([('balance', 'INTEGER')]) row5 = [-123456789] encoded5 = encode_row(schema5, row5) decoded5 = decode_row(schema5, encoded5) RESULT ------------------------------ decoded5 == [-123456789] -- exact match WHY THE NEGATIVE VALUE ROUND-TRIPS CORRECTLY WITH '>q' ------------------------------ struct's own format code 'q' specifically means "signed 64-bit integer" (lowercase q; uppercase Q is the unsigned variant). Python's struct.pack('>q', -123456789) encodes the value using two's complement representation -- the standard way virtually every real computer represents signed integers in binary, where the high bit signals whether the value is negative and the rest of the bits are interpreted accordingly. struct.unpack('>q', ...) reverses that exact interpretation, correctly recovering -123456789. WHAT WOULD BREAK WITH '>Q' (UNSIGNED) INSTEAD ------------------------------ 'Q' tells struct to interpret the same 8 bytes as an UNSIGNED 64-bit integer -- meaning every one of the 2^64 possible bit patterns maps to a non-negative number, with no bit reserved to signal a negative value at all. Two consequences, both real: 1. struct.pack('>Q', -123456789) would raise a real error -- struct.error: argument out of range -- because -123456789 is simply not a representable value for an unsigned format at all; packing would fail immediately, before a single byte was ever written. 2. If, instead, some ALREADY-negative bit pattern (produced earlier by a signed '>q' pack) were later unpacked with '>Q', the result would be silently WRONG rather than an error: the same 8 bytes would decode to some enormous positive number (via two's- complement's own bit-pattern reinterpretation) instead of the original negative value -- a mismatch between how a value was WRITTEN and how it was later READ, exactly the kind of format inconsistency Chapter 2's own text length-prefix bug already demonstrated the real cost of. WHY THIS WORKS AS AN ANSWER ------------------------------ This confirms choosing '>q' over '>Q' isn't an arbitrary implementation detail -- it's the single design decision that determines whether this engine can store negative numbers (real, ordinary values like an overdrawn balance, a temperature below zero, or a signed difference between two counts) at all, and whether encoding and decoding stay consistent with each other. Mixing signed writes with unsigned reads (or vice versa) would be a real, silent correctness bug of exactly the same shape as this chapter's own text-length mistake: the code runs without crashing, and produces a confidently wrong answer.