extra revisions to number-number.2

This commit is contained in:
queue-miscreant 2025-07-30 00:37:47 -05:00
parent 3fe47b2af3
commit 118657d2fc
2 changed files with 56 additions and 39 deletions

View File

@ -1,13 +1,15 @@
-- Functions from the previous post (../1/index.qmd)
module Previous where module Previous where
import Data.Tree import Data.Tree
import Data.List (nubBy) import Data.List (nubBy)
-- Breadth-first search on a tree
bfs (Node root children) = bfs' root children where bfs (Node root children) = bfs' root children where
bfs' v [] = [v] bfs' v [] = [v]
bfs' v ((Node y ys):xs) = v:bfs' y (xs ++ ys) bfs' v ((Node y ys):xs) = v:bfs' y (xs ++ ys)
-- Tree of all binary (dyadic) fractions
binFracTree = unfoldTree make $ (1,1) where binFracTree = unfoldTree make $ (1,1) where
make v@(vn, vd) make v@(vn, vd)
= (v, [ = (v, [
@ -15,6 +17,7 @@ binFracTree = unfoldTree make $ (1,1) where
(2*vn + 1, 2*vd) (2*vn + 1, 2*vd)
]) ])
-- Stern-Brocot Tree, as (numerator, denominator) pairs
sternBrocot = unfoldTree make $ ((1,1), (0,1), (1,0)) where sternBrocot = unfoldTree make $ ((1,1), (0,1), (1,0)) where
make (v@(vn, vd), l@(ln, ld), r@(rn, rd)) make (v@(vn, vd), l@(ln, ld), r@(rn, rd))
= (v, [ = (v, [
@ -22,9 +25,13 @@ sternBrocot = unfoldTree make $ ((1,1), (0,1), (1,0)) where
(((vn + rn), (vd + rd)), v, r) (((vn + rn), (vd + rd)), v, r)
]) ])
-- Build a list of pairs which sum to n
listPairs n = [ (k, n - k) | k <- [0..n] ] listPairs n = [ (k, n - k) | k <- [0..n] ]
-- "Triangular" enumeration of all pairs of positive integers
allPairs = concat $ map listPairs [0..] allPairs = concat $ map listPairs [0..]
-- Equality for rational numbers expressed as pairs of positive integers
ratEqual (a, b) (c, d) = a * d == b * c ratEqual (a, b) (c, d) = a * d == b * c
-- All positive rational numbers in least terms, as pairs of positive integers
allRationals = nubBy ratEqual $ map (\(a,b) -> (a+1, b+1)) allPairs allRationals = nubBy ratEqual $ map (\(a,b) -> (a+1, b+1)) allPairs

View File

@ -1,16 +1,15 @@
--- ---
title: "Numbering Numbers, Part 2: Ordering Obliquely" title: "Numbering Numbers, Part 2: Ordering Obliquely"
description: | description: |
How do we count an infinitude of numbers? How do we construct an irrational number from rational ones?
format: format:
html: html:
html-math-method: katex html-math-method: katex
date: "2023-12-31" date: "2023-12-31"
date-modified: "2025-07-26" date-modified: "2025-07-29"
categories: categories:
- algebra
- question-mark function
- haskell - haskell
- diagonal argument
--- ---
@ -63,17 +62,19 @@ Because rationals -- and even some irrationals -- can be enumerated, we might im
that it would be nice to enumerate *all* irrational numbers. that it would be nice to enumerate *all* irrational numbers.
Unfortunately, we're not very lucky this time. Unfortunately, we're not very lucky this time.
Let's start by making what we mean by "number" more direct. Let's start by making what we mean by "number" more concrete
We've already been exposed to infinite expansions, like 1/3 in binary (and conveniently, also decimal). We've already been exposed to infinite expansions, like 1/3 in binary (and conveniently, also decimal).
In discussing the question mark function, I mentioned the utility of repeating expansions as a means of In discussing the question mark function, I mentioned the utility of repeating expansions as a means of
accessing quadratic rationals in the Stern-Brocot tree. accessing quadratic rationals in the Stern-Brocot tree.
A general sequence need not repeat, so we choose to treat these extra "infinite expansions" as numbers. A general sequence need not repeat, so we can choose to treat these extra "infinite expansions" as numbers.
Proving that a rational number must have a repeating expansion is difficult, but if we accept this premise, Proving that a rational number must have a repeating expansion is difficult, but if we accept this premise,
then the new non-repeating expansions are our irrationals. then the new non-repeating expansions are our irrationals.
For example, $2 - \varphi \approx 0.381966...$, which we encountered last time, is such a number. For example, $2 - \varphi \approx 0.381966...$, which we encountered last time, is such a number.
Doing this introduces a number of headaches, the least of which is attempting to do arithmetic with such quantities. Doing this introduces a number of headaches, the least of which is that of
However, we're only concerned with the contents of these sequences to show why we can't list out all irrationals. the definition of arithmetic on such quantities.
However, we need only be concerned with the contents of these sequences to show why
we can't list out all irrationals.
### Diagonalization ### Diagonalization
@ -86,8 +87,9 @@ Suppose that we have an enumeration of every infinite sequence on this list -- n
[Cantor's diagonal argument](https://en.wikipedia.org/wiki/Cantor%27s_diagonal_argument) shows that [Cantor's diagonal argument](https://en.wikipedia.org/wiki/Cantor%27s_diagonal_argument) shows that
a new sequence can be found by taking the sequence on the diagonal a new sequence can be found by taking the sequence on the diagonal
and changing each individual digit to another one. and changing each individual digit to another one.
The new sequence differs in at least one place from every element on the list, so it cannot be on the list, The new sequence differs in at least one place from every element on the list,
showing that such an enumeration cannot exist. so the new sequence cannot be on the list.
Therefore, such an enumeration cannot exist.
This is illustrated for binary sequences in this diagram: This is illustrated for binary sequences in this diagram:
@ -107,26 +109,27 @@ This is illustrated for binary sequences in this diagram:
It's fairly common to show this argument without much further elaboration, It's fairly common to show this argument without much further elaboration,
but there are a few problems with doing so: but there are a few problems with doing so:
- We're using infinite sequences of digits, not numbers - We're using infinite sequences of digits, not numbers.
- Equality between sequences is defined by having all elements coincide - Equality between sequences is defined by having all elements coincide.
- We assume we have an enumeration of sequences to which the argument applies. - We assume we have an enumeration of sequences to which the argument applies.
The contents of the enumeration are a mystery. The contents of the enumeration are a mystery.
- We have no idea where the rational numbers are, or if we'd construct one - We have no idea which sequences are rational numbers, or if we'd construct one
by applying the diagonal argument by applying the diagonal argument.
### Equality ### Equality
The purpose of the diagonal argument is to produce a new sequence which was not previously enumerated. The purpose of the diagonal argument is to produce a new sequence which was not previously enumerated.
The sequence is different in all positions, but what we actually want is equality with respect to the base. The sequence is different in all positions, but what we actually want is equality with respect to the base.
In base ten, we have the peculiar identity that [$0.\overline{9} = 1$](https://en.wikipedia.org/wiki/0.999...). In base ten, we have the peculiar identity that
This means that if the diagonal argument (applied to base ten sequences) constructs a new sequence with the digit 9 repeating forever, [$0.\overline{9} = 1$](https://en.wikipedia.org/wiki/0.999...).
it might be equivalent to a sequence which was already in the list: This means that if the diagonal argument (applied to base ten sequences) constructs a new sequence
with the digit 9 repeating forever, it might be equivalent to a sequence which was already in the list:
```{haskell} ```{haskell}
--| code-fold: true --| code-fold: true
--| classes: plain --| classes: plain
--| fig-cap: A case in which the diagonal argument could construct a number already on the list. --| fig-cap: "A case in which the diagonal argument could construct a number already on the list."
diagData = rowsOmega [ diagData = rowsOmega [
[1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6],
@ -151,16 +154,16 @@ In the enumeration given, the diagonal continues with 9's forever, so we end up
### Picking a Sequence and Ensuring Rationals ### Picking a Sequence and Ensuring Rationals
"No problem, just pick another enumeration," you might say. "No problem, just pick another enumeration," you might say.
Indeed, the example given relies on a simple function applied on the diagonal and an enumeration Indeed, the example given relies on a simple function and an enumeration
to which it is particularly ill-suited. to which it is particularly ill-suited.
Instead, let's focus on something we *can* do. Instead, let's focus on something we *can* do.
Instead of assuming we have all irrational numbers listed out already, let's start smaller. Instead of assuming we have all irrational numbers listed out already, let's start smaller.
As of last post, we already have several ways to enumerate all rational numbers between 0 and 1. As of last post, we already have several ways to enumerate all rational numbers between 0 and 1.
If we take this enumeration, convert rational numbers to their expansions in a base, and apply the diagonal argument, We can take this enumeration and convert each rational number to positional expansions in a base.
the resulting quantity *should* be an irrational number. Then after applying the diagonal argument, the resulting quantity *should* be an irrational number.
For convenience, we'll use the binary expansions of rationals as our sequences. For convenience, we'll use binary expansions of rationals as our sequences.
That way, to get a unique sequence on the diagonal, we only have to worry That way, to get a unique sequence on the diagonal, we only have to worry
about changing "0"s to "1"s (and vice versa). about changing "0"s to "1"s (and vice versa).
Since we have less flexibility in our digits, it also relieves us from some of the responsibility Since we have less flexibility in our digits, it also relieves us from some of the responsibility
@ -171,8 +174,8 @@ However, it's still possible the argument constructs a number already equal to s
Into Silicon Into Silicon
------------ ------------
Before going any further, let's convert the diagonal argument into a function. Before going any further, let's write a function for applying the diagonal argument to
We want to, given an enumeration of binary sequences, produce a new sequence not on the list. a list of binary sequences.
This can be implemented in Haskell fairly easily: This can be implemented in Haskell fairly easily:
@ -236,7 +239,7 @@ oneFifth = fromBinSeq 64 $ tail $ binDiv 1 5
print oneFifth print oneFifth
``` ```
The precision `p` here is mostly useless, since we intend to take this to as far as doubles will go. The precision `p` here is mostly useless, since we intend to take this to as far as `Double` will go.
`p` = 100 will do for most sequences, since it's rare that we'll encounter more than a few zeroes `p` = 100 will do for most sequences, since it's rare that we'll encounter more than a few zeroes
at the beginning of a sequence. at the beginning of a sequence.
@ -245,7 +248,8 @@ Some Enumerations
----------------- -----------------
Now, for the sake of argument, let's look at an enumeration that fails. Now, for the sake of argument, let's look at an enumeration that fails.
Using the tree of binary fractions from the last post, we use a breadth-first search to create a list of terminating binary expansions. Using the tree of binary fractions from the last post, we use a breadth-first search to create
a list of terminating binary expansions.
![ ![
Tree of binary fractions, as the tree of terminating binary expansions. <br> Tree of binary fractions, as the tree of terminating binary expansions. <br>
@ -282,8 +286,8 @@ renderDiagTable badDiag 8
Computing the diagonal sequence, it quickly becomes apparent that we're going Computing the diagonal sequence, it quickly becomes apparent that we're going
to keep getting "0"s along the diagonal. to keep getting "0"s along the diagonal.
This is because we're effectively just counting in binary some number of digits to the right. This is because we're effectively just counting in binary some number of digits to the right.
The length of a binary expansion grows logarithmically rather than linearly; in other words, The length of a binary expansion grows logarithmically, but going down the diagonal is a linear process.
we can't count fast enough by just adding 1 (or adding any number, really). In other words, we can't count fast enough by just adding 1 (or adding any number, really).
Even worse than this, we get a sequence which is equal to sequence 1 as a binary expansion. Even worse than this, we get a sequence which is equal to sequence 1 as a binary expansion.
We can't even rely on the diagonal argument to give us a new number that *isn't* equal We can't even rely on the diagonal argument to give us a new number that *isn't* equal
@ -346,6 +350,13 @@ This new number can just be tacked onto the beginning of the list.
Then, we re-apply the diagonal argument to obtain a new number. Then, we re-apply the diagonal argument to obtain a new number.
And so on ad infinitum. And so on ad infinitum.
```{haskell}
transform :: [[Int]] -> [[Int]]
-- Emit the new diagonal sequence, then recurse with the new sequence
-- prepended to the original enumeration
transform xs = let ds = diagonalize xs in ds:transform (ds:xs)
```
```{haskell} ```{haskell}
--| code-fold: true --| code-fold: true
--| classes: plain --| classes: plain
@ -379,13 +390,9 @@ renderTransformTable n = renderTable
renderTransformTable 8 $ take 8 $ sbDiag id renderTransformTable 8 $ take 8 $ sbDiag id
``` ```
For completeness's sake, the decimal expansions of the first few numbers which pop are as follows:
```{haskell} ```{haskell}
transform :: [[Int]] -> [[Int]]
-- Emit the new diagonal sequence, then recurse with the new sequence
-- prepended to the original enumeration
transform xs = let ds = diagonalize xs in ds:transform (ds:xs)
sbDiagSeq = let (Node _ (tree:__)) = sternBrocot in sbDiagSeq = let (Node _ (tree:__)) = sternBrocot in
transform $ map (tail . uncurry binDiv) $ bfs tree transform $ map (tail . uncurry binDiv) $ bfs tree
@ -393,8 +400,10 @@ mapM_ print $ take 10 $ map (fromBinSeq 100) sbDiagSeq
``` ```
Does this list out "all" irrational numbers? Not even close. Does this list out "all" irrational numbers? Not even close.
In fact, this just gives us a bijection between the original enumeration and a new one containing our irrationals. In fact, this just gives us a bijection between the original enumeration
The new numbers we get depend heavily on the order of the original sequence. and a new one containing our irrationals.
The new numbers we get also depend heavily on the order of the original sequence.
This is obvious just by looking at the first entry produced by our two good enumerations. This is obvious just by looking at the first entry produced by our two good enumerations.
Perhaps if we permuted the enumeration of rationals in all possible ways, we would end up listing Perhaps if we permuted the enumeration of rationals in all possible ways, we would end up listing
all irrational numbers, but we'd also run through all irrational numbers, but we'd also run through
@ -403,7 +412,7 @@ Perhaps if we permuted the enumeration of rationals in all possible ways, we wou
The fact that "bad" enumerations exist tells us that it's not even guaranteed that we don't collide The fact that "bad" enumerations exist tells us that it's not even guaranteed that we don't collide
with any rationals. with any rationals.
I conjecture that the good enumerations won't do so, since we shouldn't ever encounter I conjecture that the good enumerations won't do so, since we shouldn't ever encounter
an infinite sequence of "0"s, and a sequence should eventually differ an infinite sequence of "0"s or "1"s, and a sequence should eventually differ
in at least one position from one already listed. in at least one position from one already listed.
@ -411,7 +420,7 @@ I conjecture that the good enumerations won't do so, since we shouldn't ever enc
Since the function has the same input and output type, you may wonder what happens when Since the function has the same input and output type, you may wonder what happens when
it's applied multiple times. it's applied multiple times.
Perhaps you assume that, since `\x -> 1 - x` swaps 0 and 1, we'll get the original sequence back again. Perhaps it is possible that since `\x -> 1 - x` is an involution, so too is this new function.
Alas, experimentation proves us wrong. Alas, experimentation proves us wrong.
```{haskell} ```{haskell}
@ -428,7 +437,8 @@ mapM_ putStrLn [ "1/2", "1/3", "11/12", "1/8", "57/80", "23/80",
The fraction forms of the above numbers are my best guesses. The fraction forms of the above numbers are my best guesses.
Either way, it only matches for the first two terms before going off the rails. Either way, it only matches for the first two terms before going off the rails.
Even stranger than this distorted reflection is the fact that going through the mirror again doesn't take us anywhere new Even stranger than this distorted reflection is the fact that going through the mirror again
doesn't take us anywhere new:
```{haskell} ```{haskell}
sbDiagSeq3 = transform sbDiagSeq2 sbDiagSeq3 = transform sbDiagSeq2