AI-NATIVE SERIES · NUMBERS AT FOUR AGES, EPISODE 4 OF 4 · AGE 20
Number vs Representation: The First Abstraction Every Programmer Uses (for Age 20)
1-minute takeaway
What this is. An engineer's essay for 20-year-olds. The idea a child meets with apples and stones, that a number is not its name, is the same abstraction behind abstract data types, encodings, floating point and Unicode digits.
Why it matters. Bugs live in the gap between the value and the encoding: 0.1 + 0.2 that is not 0.3, a byte that is 251 or −5 depending on who reads it, a spreadsheet column that is base 26 with no zero. Knowing which layer a fact belongs to is a daily engineering decision.
What you can do after reading. Write a converter that round-trips every number through three encodings and passes the invariance test, find one bug in your own code that depends on representation, then teach the place-value proof to a 15-year-old.
Numbers at Four Ages — the series spine
▶ See it running: open the live demo public · no sign-in
- Age 7 · What is a number? episode 1
- Age 11 · Why so many ways to write numbers? episode 2
- Age 15 · Ten symbols, every number episode 3
- Age 20 · Number vs representation this episode
The one question: Why does every programmer need the difference between a number and its representation?
This episode does NOT teach the place-value proof itself; that is episode 3 (age 15). Continuous quantities (measuring rather than counting) are the next season.
What this episode is about: number vs representation
This is an engineer's essay about number vs representation: the difference between a value and the way it is written down or stored. It is for readers of about twenty who write code, or soon will. You need a laptop with Python and fifteen minutes for the exercise.
It is episode 4 of four, and it closes a loop.
- Episode 1 showed a 7-year-old that five apples and five stones share one thing, and that "5" is only its name.
- Episode 2 let an 11-year-old invent number systems and watch place value win.
- Episode 3 gave a 15-year-old the proof that every number has exactly one spelling in any base, zero or no zero.
This episode shows that the child's idea is the first abstraction every programmer uses. The ideas follow a chapter by 陈诗峰 (Shifeng Chen), co-author of this series, on numbers and their representations. Pushed one step further, they become a test you can run on your own code.
The series runs on one picture from the kitchen, climbing a ladder: eggs in a bowl, a restaurant, a fast-food stockroom. Here we reach the top rung, a national food supply chain. One crate of 42 cartons moves through a dozen systems. A barcode encodes it in one way, the warehouse database stores it as a binary integer, a CSV export writes the string "0042", and a spreadsheet keeps it in column AP. The quantity never changes; every system writes it differently. Supply-chain bugs live exactly in that gap.
Before you read on. You will end by walking a 15-year-old through the uniqueness proof from episode 3. Read as someone who will have to explain it. Across 39 studies, teaching after studying with that expectation scored g = 0.48, and teaching without it scored g = −0.02 (Kobayashi (2024), Educational Psychology Review (ERIC EJ1414484)).
Activity 1 · Count to 31 on one hand (invent first)
Your hand holds 0 to 5 if each raised finger means one. Without reading ahead, invent a way to count to 31 on that one hand, so that every number from 0 to 31 has its own finger pattern and you never reuse one.
Then use your system to show 19 and 31. Write down the rule you invented.
If each finger means double the one before it (1, 2, 4, 8, 16), you invented binary. 19 is 16 + 2 + 1, and 31 is all five fingers. Same hand, same number of fingers, six times the range. You changed the representation, and the numbers did not change at all. That is the whole essay in one gesture.
"Same number" is defined by pairing; a representation is an encoding
Episode 1's apple game is not a children's simplification. It is the definition of "same number" that Frege built arithmetic on. In the foundations of mathematics, two collections have the same number exactly when there is a one-to-one correspondence between them. This is Hume's Principle, at the root of Frege's 1884 construction of arithmetic (Zalta, Stanford Encyclopedia of Philosophy, "Frege's Theorem and Foundations for Arithmetic"). Nothing in that definition mentions digits.
A representation is an encoding of that value: a finite string over some alphabet plus a rule for reading it. Base ten, binary, Roman numerals, tally marks and the bits in a register are all representations. Software engineering names the same separation: an abstract data type is what a value does, separate from how it is stored, and the idea was set out in 1974 (Liskov & Zilles (1974), "Programming with abstract data types", ACM SIGPLAN Notices). A number is the original abstract data type, and every numeral system is one implementation of it.
A property that stays true under every encoding is an invariant of the value. Sorting invariants from encoding facts is the skill this essay trains. You can try it now in the Number Playground, a free page that writes any number nine ways and runs Chen's sorting game.
The core idea of the series, in one line: a number is the value, a representation is an encoding, and correct code depends only on the value.
Why positional notation won: a log-length code with a fixed alphabet
Chen's chapter is a history of compression, and an engineer can state it exactly.
- A tally needs n marks for n: length grows linearly.
- Additive systems (Roman, Egyptian) are shorter, but they need new symbols as numbers grow. The Egyptians had a single sign for a million, but nothing above it: a dead end.
- Positional notation in base b needs only 1 + ⌊log_b n⌋ digits (Weisstein, MathWorld, "Digit"). The length is logarithmic in the value, and the alphabet never grows.
Chen's sentence: place value turns the infinity of symbols into an infinity of places.
That trade has a cost. Every place must be kept, including an empty one, which is what the placeholder zero is for. Zero is not strictly needed, though. In bijective (zero-free) base k the digits run from 1 to k. Every positive integer still has exactly one string, and the empty string is zero. You use one every day: spreadsheet columns A to Z, then AA, AB…, are bijective base 26. Excel allows 16,384 columns, and the last one is XFD (Microsoft, "Excel specifications and limits"): 24 × 26² + 6 × 26 + 4 = 16,384.
Activity 2 · Write the converter, then test what it must not change
Write three encoders and their decoders before reading the version below: base b with a zero digit, zero-free base ten (digits 1 to 9 and κ for ten, Chen's symbol), and spreadsheet column labels. Then write property tests. They should check that every encoding round-trips, and they should catch any "fact" that only holds for one representation.
One version, which passes every assertion when run:
DIGITS = "0123456789abcdefghijklmnopqrstuvwxyz"
def to_base(n, b):
if n == 0:
return "0"
out = ""
while n:
n, r = divmod(n, b)
out = DIGITS[r] + out
return out
def from_base(s, b):
return int(s, b)
def to_zero_free_ten(n): # digits 1-9 and κ (ten); no 0
out = ""
while n:
r = n % 10 or 10
out = "123456789κ"[r - 1] + out
n = (n - r) // 10
return out
def from_zero_free_ten(s):
n = 0
for ch in s:
n = n * 10 + "123456789κ".index(ch) + 1
return n
def column_label(n): # spreadsheet columns: zero-free base 26
out = ""
while n:
r = n % 26 or 26
out = chr(64 + r) + out
n = (n - r) // 26
return out
def column_number(s):
n = 0
for ch in s:
n = n * 26 + ord(ch) - 64
return n
assert to_zero_free_ten(3020) == "2κ1κ" and to_zero_free_ten(10**6) == "99999κ"
assert column_label(27) == "AA" and column_label(16384) == "XFD"
for n in range(1, 10_000): # every encoding round-trips: the value is untouched
assert from_base(to_base(n, 3), 3) == n
assert from_zero_free_ten(to_zero_free_ten(n)) == n
assert column_number(column_label(n)) == n
assert (n % 3 == 0) == to_base(n, 3).endswith("0") # true in base 3 ...
assert any((n % 3 == 0) != str(n).endswith("0") for n in range(1, 100)) # ... false in base ten: a fact about notation
Look at the last two assertions. "Divisible by 3 means the last digit is 0" holds for every number in base 3, and the final line shows it failing in base ten (3 is divisible by 3 and ends in 3). A property that holds in one encoding and fails in another belongs to the encoding, not to the number. Chen's sorting experiment, in which children decide whether a sentence is about the number or about its writing, is the same move as a property test that holds the value fixed and swaps the encoding. In base b the digit-sum shortcut works exactly for the divisors of b − 1 (Weisstein, MathWorld, "Divisibility Tests"), so the familiar rule for 3 and 9 is a fact about ten, not about three.
The same bits, different numbers: where it bites in code
Three runs you can reproduce in a Python prompt. Each is a representation fact leaking into the value layer.
- One byte, two numbers. The bit pattern 11111011 walks into a bar, and the bartender asks: "251 or −5?" It is 251 read as an unsigned byte and −5 read as an 8-bit two's complement integer. The bits are identical; the agreed encoding decides the number. Bugs follow when two systems disagree about the encoding.
- 0.1 + 0.2 is 0.30000000000000004. A binary float cannot hold one tenth exactly, just as a decimal cannot hold one third. In base 3, a third is simply 0.1. The value is fine; the encoding rounds it.
0.1 + 0.2 == 0.3is a question about encodings, which is why numeric code compares with a tolerance. - Five characters, one value. The characters 5, ٥, ५, Ⅴ and 五 all carry the numeric value 5 in the Unicode Character Database (Unicode Character Database (UnicodeData.txt) and Unihan (U+4E94)). Python's
int("٥")andint("५")return 5, because those are decimal digits in Unicode, whileint("五")andint("Ⅴ")raise ValueError, by design:intreads digits, not numerals.unicodedata.numeric("五")returns 5.0. Every parser draws this line somewhere; the bug is drawing it without knowing.
The supply-chain version: a product code "0042" loaded into a spreadsheet becomes the number 42, and the leading zeros that were part of the identifier are gone. Somewhere a warehouse now believes that product 0042 (rubber ducks) and product 42 (also rubber ducks, different supplier) are the same thing, and orders twice the ducks. The code was an identifier stored as a string of digits, never a quantity. Knowing which layer a value lives in would have prevented it.
A map with a prediction: does a number have a body?
The series uses a lens from Paul's "play-engineering" notes, tested rather than assumed: a body has a spirit, bones, flesh and skin. In a number system:
- the spirit is the value;
- the bone is the place-value frame, the type's structure;
- the flesh is the algorithms on numerals (carrying, comparison, parsing);
- the skin is the glyphs and bytes.
The prediction that makes it worth carrying: whatever survives a change of encoding belongs to the value; whatever breaks belongs to an implementation layer. Addition is spirit; the carry rule is flesh: one rule with N in it ("when you reach N, carry one"), so changing the base changes only the N, while addition itself never changes. In code that is the rule "test properties at the value layer, and keep encoding-specific facts behind the encoding's interface".
Where the map breaks:
- You can swap the bones (change base) and keep the spirit, which no body allows.
- "Discrete vs continuous" is an axis, not a layer. Aristotle drew it: "Quantity is either discrete or continuous." (Aristotle, Categories, Part 6, tr. E. M. Edghill (MIT Internet Classics Archive)). Everything here is discrete; measuring, and with it floats done properly, is the next season.
- Whether the spirit exists independently is open. On the Platonist view, numbers exist, are abstract and do not depend on any mind (Linnebo, Stanford Encyclopedia of Philosophy, "Platonism in the Philosophy of Mathematics"). On the nominalist view, there are no abstract number objects at all (Bueno, Stanford Encyclopedia of Philosophy, "Nominalism in the Philosophy of Mathematics"). Engineers can stay agnostic: the interface is what matters. That is itself a philosophical position, a functional one.
Why one idea fits four ages
This series bets on a claim usually traced to Jerome Bruner's The Process of Education (1960): that any subject can be taught in some intellectually honest form at any stage of development. We paraphrase it on purpose. We could not check the primary wording, so we do not quote it.
The test of "honest" is the same invariance test. Every episode states the same kernel, and nothing a 7-year-old learns here has to be unlearned at twenty. The pairing game is Hume's Principle. "5 is only a name" is the value/encoding split. The two-hand counter is the carry rule.
The playbook behind the series is built on studies you can check:
- Working memory holds three to five chunks (Cowan (2001), "The magical number 4 in short-term memory", Behavioral and Brain Sciences 24).
- Support that helps novices can hurt experts (Kalyuga, Ayres, Chandler & Sweller (2003), "The Expertise Reversal Effect", Educational Psychologist 38).
- Expecting to teach is what makes teaching pay (Kobayashi (2024), Educational Psychology Review (ERIC EJ1414484)).
One more lens, labelled as mine. I hold that Truth, Love and Beauty are not three separate ideals but three faces of one Reality, and in my faith, of one Person. In this series they are also an engineering checklist that stands without the faith. A lesson needs to be true (checkable by the learner), loving (written for a particular person and met at their mistakes), and beautiful (one moment worth showing someone).
- Truth without love is a correct proof nobody can follow.
- Love without truth is a fun game that plants a falsehood.
- Beauty without either is a pretty infographic with the wrong history.
Mistakes that are half right
- "0.1 is stored wrong." It is stored as the nearest binary fraction, exactly as specified. The surprise is the encoding, not an error, and the fix is to compare with a tolerance or use a decimal type.
- "Leading zeros don't matter." True for a quantity, false for an identifier. "0042" as a product code is a string that happens to contain digits.
- "Base 26 column labels are just letters." They are a positional number system with no zero digit, which is why Z is followed by AA, not BA.
Now teach it
Your teach-back. Find a 15-year-old and work through "The proof: every number has exactly one spelling" in episode 3 with them. Don't write the steps for them: ask what the remainder is when 352 is divided by 3, then what happens to the quotient, and let them see that nobody chose the digits.
Outcome: the 15-year-old writes the remainder steps for 352 in base 3 unaided (111001) and says in one sentence why the last digit is forced. Post their age and result as a comment on this article.
Why it matters: five whys
The problem: a data pipeline drops the leading zeros from product codes, so 0042 and 42 merge and two products become one.
- Why did the codes merge? A loader parsed "0042" as the number 42. Both strings map to one value, so the identifiers collided.
- Why was it parsed as a number? The type was inferred from the look of the characters, digits only, rather than from what the field means.
- Why infer from the look? Because the system treated the representation (a string of digits) as if it were the value, the exact confusion Chen's chapter teaches children to avoid.
- Why wasn't it caught? No test held the value fixed while changing the encoding. A test that held the value fixed and changed the encoding would fail at once, the way the base-ten contrast line in the converter shows a base-3 "fact" failing in base ten.
- Root cause: code depended on the representation layer, not the value layer. The fix is the episode's rule: decide what the value is (quantity or identifier), keep encoding facts behind an interface (Liskov & Zilles (1974), "Programming with abstract data types", ACM SIGPLAN Notices), and test invariance across encodings.
Operate: run it this week
- Read the teach-ahead line.
- Count to 31 on one hand (2 minutes).
- Write the three converters and the property tests, then compare with the version above (15 minutes).
- Open the Number Playground. Type 16384 and 1000000, check your converter against its rows (including the spreadsheet-column row: 16384 is XFD), then play the N-or-R game.
- Find one place in your own code where a fact depends on representation (string vs number, float equality, a digit-only parser). Add one invariance test.
- Teach episode 3's proof to a 15-year-old.
Feedback: how you know it worked
- Your converter passes every assertion above.
- Your own code has one new property test that would fail if a representation fact leaked into the value layer.
- You can explain, in a sentence each, why 11111011 is two numbers, why 0.1 + 0.2 ≠ 0.3 in binary floats, and why Z is followed by AA.
Re-check yourself after a week. Testing beat rereading at 1 week (Roediger & Karpicke (2006), "Test-Enhanced Learning", Psychological Science 17). What this cannot see: whether the habit holds under deadline pressure. The property test in CI is the part that can.
Feedforward: tripwires, decided now
- If a field is all digits, decide first whether it is a quantity or an identifier. Write the decision into the schema before the loader guesses.
- If you compare floats with
==, stop. Use a tolerance or a decimal type, and add a test with 0.1 + 0.2. - If your parser meets ٥ or 五, decide on purpose: Python's
intaccepts Unicode decimal digits such as ٥ and rejects numerals such as 五 and Ⅴ by design. Write down which your system accepts, and test it.
Truth, love and beauty in this episode
- Truth: every claim runs. The converter asserts pass, and the byte and float examples reproduce in any Python prompt.
- Love: the essay hands you a 15-year-old to teach, and meets each common mistake with what it got right.
- Beauty: one idea, from five apples to Excel's last column, holds at every age without a word unlearned. (The egg that held hands with a stone in episode 1 is now carton 0042 on a national truck. Its count is still one; only its name keeps changing.)
The 5S self-score
| S | Claim | Evidence |
|---|---|---|
| Simple | The prose reads at a grade-13 level or easier. | Flesch–Kincaid grade of the prose: 8.4 (band ≤ 13), recomputed by node scripts/check-numbers-four-ages.mjs |
| Solid | The definitions and numbers are sourced; the code runs. | Excel's 16,384 columns (Microsoft); Hume's Principle |
| Sharp | Positional notation is a log-length code with a fixed alphabet; correct code depends only on the value. | Log-length · the property test |
| SMART | This week: converter + one invariance test in your own code + one teach-back. | Operate · Playground |
| Surprise | Your spreadsheet has counted in zero-free base 26 all along. | XFD = 16,384 |
| Truth · Love · Beauty | Runnable claims; a person to teach; one idea at every age. | Truth, love and beauty |
Sources
- 陈诗峰 (Shifeng Chen), draft chapter 《第一章 数和数的表示形式》 ("Numbers and Their Representations"), 2026. Read the original chapter (PDF, Chinese). Co-author of this series.
- Zalta, E. N. "Frege's Theorem and Foundations for Arithmetic." Stanford Encyclopedia of Philosophy. plato.stanford.edu
- Liskov, B. & Zilles, S. (1974). Programming with abstract data types. ACM SIGPLAN Notices. Crossref 10.1145/942572.807045
- Weisstein, E. W. "Digit." MathWorld. mathworld.wolfram.com
- Weisstein, E. W. "Divisibility Tests." MathWorld. mathworld.wolfram.com
- Microsoft. "Excel specifications and limits." support.microsoft.com
- Unicode Character Database, UnicodeData.txt. unicode.org
- Aristotle. Categories, Part 6, tr. E. M. Edghill. classics.mit.edu
- Linnebo, Ø. "Platonism in the Philosophy of Mathematics." Stanford Encyclopedia of Philosophy. plato.stanford.edu
- Bueno, O. "Nominalism in the Philosophy of Mathematics." Stanford Encyclopedia of Philosophy. plato.stanford.edu
- Cowan, N. (2001). The magical number 4 in short-term memory. Behavioral and Brain Sciences 24. doi:10.1017/S0140525X01003922
- Kalyuga, S., Ayres, P., Chandler, P. & Sweller, J. (2003). The expertise reversal effect. Educational Psychologist 38. doi:10.1207/S15326985EP3801_4
- Kobayashi, K. (2024). Interactive learning effects of preparing to teach and teaching. Educational Psychology Review. ERIC EJ1414484
- Roediger, H. L. & Karpicke, J. D. (2006). Test-enhanced learning. Psychological Science 17. doi:10.1111/j.1467-9280.2006.01693.x
- DeLamater, M. (1999), on Bruner's hypothesis. Education Week. edweek.org (secondary; the primary wording was not checked)