This is one of those geeky posts more a “Dear Diary” (or “Dear Lab Notebook”) entry than a post I expect anyone anywhere will get anything out of. This — in part — is about how we define numbers using set theory, so it’s pretty niche and rarified. Tuning out is understandable; this is extra-credit reading.
This is also about having a double-lightbulb moment. I finally get why what always seemed an overly complicated approach is actually perfect. A smaller lightbulb involves easily solving a programming problem that confounded me previously.
Fun for me, but your mileage may vary.
To explain the lightbulbs, I need to say a bit about defining numbers using set theory. (It’s not as bad as it may sound. Formal language can be intimidating, but the ideas it’s expressing, at least here, aren’t that complicated. And since this is more for me, I’ll try to be brief.) (Stop laughing.)
I’ve written before how I think numbers and math are inevitable for any intelligent species. [See Inevitable Math and Numbers Gotta Number] Numbers, and the math that is their grammar, force themselves on us in our experience of the physical world. (As I wrote in Inevitable Math, I think they force themselves on us even in just our thoughts.)
Two kinds of numbers present themselves to our experience: things we measure (heat, weight, distance, height) and things we count (sheep, coins, cheeses, boats). [See Magnitudes vs Numbers] Humanity seems to have discovered counting first. Our earliest measurements counted cubits or miles or days. Notions of 3/4 (let alone 0.75) miles came later.
Counting objects seems very natural, which is why the positive integers are called the natural numbers. But it would be nice to have a well-grounded theory explaining them, because they are the foundation much else in mathematics (and science and technology) stands upon.
§
The idea of counting depends on the recognition there is something that can be counted. That depends on classifying different objects as the same under some abstraction. That’s an intellectual jump.
When I walk Bentley down a familiar street, she perceives a series of specific individual objects, each with different smells and location relative to each other. I see a bunch of trees I can number because they belong to the same set of things: trees. More specifically, the set of trees along this street. That set has a size — the number of trees.
Any set of things has a size, the number of things in the set. The sheep in the pasture, the coins in the sack, the cheeses in the pantry, the boats in the harbor, these are all sets of things, and they all have a size. Indeed, they could all be the same size despite being made of very different things. [See Numbers & Sets]
This notion of things in a set and of set size is the foundation of how the natural numbers are defined using set theory. They are the cardinal sizes of abstract sets beginning with the empty set (zero) and then adding (imaginary abstract) items.
So, zero is a set with nothing in it, one is a set with one thing in it, two is a set with two things, and so on. Forever, because the natural numbers go on forever.
§
The last bit of background is the successor function. We imagine a function that, given some set (of some size), returns a new set with one more imaginary item in it. Put numerically, a successor function, given some number, returns the next number “in line” (given 41 it would return 42).
The successor function is the abstraction of counting. Think of the trees along that street. I have a small empty bag and a handful of pebbles. At the first tree, I put a pebble in the bag (my set went from zero to one). At the next tree, I put another pebble in the bag (my set size is now two). I keep doing this for every tree. At the end of the street, my set size is the count of trees.
And note the abstraction of pebbles standing for trees. The whole notion of numbers is based on the bag of pebbles and street of trees being the same a numeric level.
§ §
Okay then, so here’s the thing. In a common version of the mathematics of this set theory for numbers, the set items are themselves sets. The process starts with the empty set (called the null set, written as {}), which represents zero. And a successor function (often just called S) for getting the next set.
If we give the successor function the null set, it should return a set representing one — a set with one thing in it. The only thing we know about is the null set, so the set for the number one contains one null set (written as { {} } — a (null) set within a set).
Now it might seem obvious that the set representing two would have two null sets (written as { {}, {} }), the set for three would have three ({ {}, {}, {} }), and so on. (I’ve added spaces for clarity.) Seems simple and easy, but that’s not how it works, and until now I’ve never understood exactly why not.
The way it really works differs beginning with two. Rather than just two null sets in a set — which would indeed have the size two — the set for two contains the set for one plus the set for zero (written as {{{}}, {}}). The set for three contains the set for two, one, and zero (written as {{{{}}{}}, {{}}, {}}). And so on.
As you might imagine, this gets involved quickly. For instance, here’s the set for the number ten:
{{{{{{{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}},{{{{}},{}},{{}},{}},{{{}},{}},{{}},{}}
I kid you not. And I’ve long wondered exactly why it needs to be like that. What’s wrong with getting the set size by just adding the null set over and over as needed?
§
Well, as it turns out, plenty. For one thing, abstracting a bit by showing the lengths of the sets rather than the sets (because that would be crazy), what we want for the set for ten is:
10 := {9, 8, 7, 6, 5, 4, 3 ,2, 1, 0}
But in using just the null set, we’d be defining ten as:
10 := {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
And while both sets are size ten, the first version is built on all previous versions and its ten members are all distinct and sortable. Not so the second version.
Fine, that makes sense, but couldn’t each member just be a set with the right number of nulls? The definition above is recursive. Again, using just the lengths of the sets, the actual definition is:
10 := { {9,8,7,6,5,4,3,2,1,0}, {8,7,6,5,4,3,2,1,0}, {7,6,5,4,3,2,1,0} … {0} }
And each one of those lengths expands into its own set of nested sets. It seems like a lot of sets, and what do they all accomplish?
§
That’s the lightbulb. They accomplish something important. Because each number contains all previous numbers, you can trace (justify!) any number all the way back to the axiomatic zero. To follow a number down, just recursively take the largest set member (which I’ve consistently shown first, sorting sets from largest member to smallest). When you reach the empty set, that’s the zero that grounds all numbers.
All the braces make it hard to follow, so let’s try a simple example, the set for the number three:
3 := { {{{}},{}}, {{}}, {} }
It has length three and contains the sets for two, one, and zero. If we take the largest member, the set for two, we have:
2 := { {{}}, {} }
Which has length two and contains the sets for one and zero. If we again take the largest member, the set for one, we have:
1 := { {} }
Which has length one and contains the set for zero. We take the last set, and:
0 := {}
We’ve reached the axiom, which took three steps. Essentially, we’ve counted down from zero. Three recursions of the successor function count up to three:
3 := S(S(S(0)))
Because S(0)=1, S(1)=2, and S(2)=3.
You could do the same thing with the set for ten above, it just takes a few more steps.
Bottom line, sets are recursive so that every subset they contain is also a valid number-representing set. A set of, say, eight null sets to represent eight isn’t sortable and isn’t recursive, so it only has size.
And counting by adding and removing an arbitrary null set isn’t as justifiable as counting up by including all previous sets or counting down by taking the highest number in the set.
It’s actually pretty cool, and I’m glad the lightbulb finally went fully on.
§ §
The smaller lightbulb is just that I’d tried twice in the past to write some Python code to generate sets. It’s a fairly simple recursion, so how hard could it be? But twice I couldn’t seem to wrap my head around it.
I stumbled over it recently and thought to give it one more shot. Much to my surprise, it was pretty easy. Made me wonder what I’d missed before.
For whatever it’s worth, here’s that Python code (I even wrote an adder that can add two sets):
002|
003| NULL = ()
004|
005| def successor (n=None):
006| ”’\
006| Integers Successor Function.
006| Given a number, returns next number.
006| ”’
007| if n is None: return NULL
008| if len(n) == 0: return tuple([NULL])
009| t = []
010| a = n
011| while 0 < len(a):
012| t.append(a)
013| a = a[0]
014| t.append(NULL)
015| return tuple(t)
016|
017| def adder (na, nb):
018| ”’Adder. Returns sum of numbers A and B.”’
019| _na = na
020| _nb = nb
021| while len(_nb):
022| _na = successor(_na)
023| _nb = _nb[0]
024| return _na
025|
026| def get_numbers (upto=10):
027| ”’Return a list of numbers up to N.”’
028| curr_number = successor()
029| cardinals = {0:curr_number}
030|
031| for n in range(1,upto+1):
032| next_number = successor(curr_number)
033| cardinals[n] = next_number
034| curr_number = next_number
035|
036| return cardinals
037|
038| # Get the numbers from 0 to 20…
039| ns = get_numbers(20)
040|
041| print(‘Cardinal sets (numbers):’)
042| print(‘(Shows lengths of the items in the set.)’)
043| print()
044| for ix in sorted(ns):
045| ts = [len(t) for t in ns[ix]]
046| print(‘%2d: %s’ % (ix, ts))
047| print()
048|
049| # Add two numbers together…
050| a = ns[5]
051| b = ns[8]
052| tot = adder(a, b)
053| print(‘ %2d: %s’ % (len(a), [len(t) for t in a]))
054| print(‘+%2d: %s’ % (len(b), [len(t) for t in b]))
055| print(‘=%2d: %s’ % (len(tot), [len(t) for t in tot]))
056| print()
057|
§ §
Stay theoretical, my friends! Go forth and spread beauty and light.
∇












October 18th, 2023 at 9:12 am
Certainly briefer than usual! 😂
October 18th, 2023 at 9:17 am
I’ve heard of human societies that supposedly don’t have any numbers above two. They “count” one, two, many. And it’s possible to wonder how they deal with, say, a dinner party.
0, 1, 2, 3, 4, … the higher we go, the less differentiation there is. Zero and One are the most significant. None versus some. And, indeed, all information reduces to zeros and ones (or noughts and ones, as the Brits say). It’s possible to understand how a society might not see anything worth differentiating about after two.
As for dinner parties and groups, perhaps they are a small enough group that everyone known everyone else, so it isn’t a matter of counting so much as recognizing which individuals are present and which are not. Alex, Blair, Charlie, Drew, … wait, where’s Drew?
That said, I’m a little suspicious any human intelligence has no notion of numbers above two, so I’d like to know a lot more about these supposed peoples.
December 22nd, 2023 at 10:16 am
Here’s a more complete version of the Python code:
002| from os import path
003|
004| ”’The only axiomatic object.”’
005| NULL = ()
006|
007| def successor (n=None):
008| ”’Successor Function. The only axiom.”’
009| # If no number given, return axiomatic first number…
010| if n is None:
011| return NULL
012| # Special case: if number is NULL (zero), return ONE…
013| if len(n) == 0:
014| return tuple([NULL])
015| # All other numbers: generate next number…
016| t = []
017| a = n
018| # Build new number by adding all previous numbers…
019| while 0 < len(a):
020| t.append(a)
021| a = a[0]
022| t.append(NULL)
023| return tuple(t) # return as tuple
024|
025| def get_numbers (up_to=10):
026| ”’Return a dictionary of numbers up to N.”’
027| # Get the axiomatic first number…
028| curr_number = successor()
029| # Use it to initialize the set of cardinal numbers…
030| cardinals = {0:curr_number}
031|
032| # Generate numbers through iteration…
033| for n in range(1, up_to+1):
034| # Get the next number…
035| next_number = successor(curr_number)
036| # Add it to the cardinal numbers set…
037| cardinals[n] = next_number
038| # Advance current number…
039| curr_number = next_number
040|
041| # Return the cardinal numbers set…
042| return cardinals
043|
044| def op_add (na, nb):
045| ”’Adder. Returns A+B.”’
046| _na = na
047| _nb = nb
048| while len(_nb):
049| _na = successor(_na)
050| _nb = _nb[0]
051| return _na
052|
053| def op_sub (na, nb):
054| ”’Subtracter. Returns A-B.”’
055| _na = na
056| _nb = nb
057| while len(_na) and len(_nb):
058| _na = _na[0]
059| _nb = _nb[0]
060| if len(_nb):
061| raise RuntimeError(‘Negative result!’)
062| return _na
063|
064| def op_mul (na, nb):
065| ”’Multiplier. Returns numbers A*B.”’
066| _np = NULL # product
067| _nb = nb
068| while len(_nb):
069| _np = op_add(_np, na)
070| _nb = _nb[0]
071| return _np
072|
073| def op_div (na, nb):
074| ”’Divider. Returns A/B (dividend & remainder).”’
075| if len(nb) == 0:
076| raise RuntimeError(‘Division by zero!’)
077| _nd = NULL # dividend
078| _nr = na
079| while len(nb) <= len(_nr):
080| _nr = op_sub(_nr, nb)
081| _nd = successor(_nd)
082| return (_nd, _nr)
083|
084| def prn_cardinal_set (nums, stream=stdout):
085| ”’Print Cardinal Number Sets.”’
086|
087| print(‘Cardinal Sets (Numbers):’, file=stream)
088| print(file=stream)
089| for ix in sorted(nums):
090| ts = [len(t) for t in nums[ix]]
091| print(‘%2d: %s’ % (ix, ts), file=stream)
092| print(file=stream)
093| print(‘(Showing lengths only.)’, file=stream)
094| print(file=stream)
095|
096| def prn_number_set (nbr, stream=stdout, wrap=72):
097| ”’Print the Set and all sub-sets of a given Number.”’
098| cmap = {ord(‘ ‘):None, ord(‘(‘):‘{‘, ord(‘)’):‘}’}
099|
100| prn = str(nbr)
101| prn = prn.translate(cmap)
102| # While string is too long, break at commas…
103| while wrap < len(prn):
104| sx = prn.find(‘,’, wrap)
105| if sx < 0:
106| break
107| print(prn[0:sx+1], file=stream)
108| prn = prn[sx+1:]
109| print(prn, file=stream)
110| print(file=stream)
111|
112|