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

View File

@ -1,16 +1,15 @@
---
title: "Numbering Numbers, Part 2: Ordering Obliquely"
description: |
How do we count an infinitude of numbers?
How do we construct an irrational number from rational ones?
format:
html:
html-math-method: katex
date: "2023-12-31"
date-modified: "2025-07-26"
date-modified: "2025-07-29"
categories:
- algebra
- question-mark function
- 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.
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).
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.
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,
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.
Doing this introduces a number of headaches, the least of which is attempting to do arithmetic with such quantities.
However, we're only concerned with the contents of these sequences to show why we can't list out all irrationals.
Doing this introduces a number of headaches, the least of which is that of
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
@ -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
a new sequence can be found by taking the sequence on the diagonal
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,
showing that such an enumeration cannot exist.
The new sequence differs in at least one place from every element on the list,
so the new sequence cannot be on the list.
Therefore, such an enumeration cannot exist.
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,
but there are a few problems with doing so:
- We're using infinite sequences of digits, not numbers
- Equality between sequences is defined by having all elements coincide
- We're using infinite sequences of digits, not numbers.
- Equality between sequences is defined by having all elements coincide.
- We assume we have an enumeration of sequences to which the argument applies.
The contents of the enumeration are a mystery.
- We have no idea where the rational numbers are, or if we'd construct one
by applying the diagonal argument
- We have no idea which sequences are rational numbers, or if we'd construct one
by applying the diagonal argument.
### Equality
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.
In base ten, we have the peculiar identity that [$0.\overline{9} = 1$](https://en.wikipedia.org/wiki/0.999...).
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:
In base ten, we have the peculiar identity that
[$0.\overline{9} = 1$](https://en.wikipedia.org/wiki/0.999...).
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}
--| code-fold: true
--| 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 [
[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
"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.
Instead, let's focus on something we *can* do.
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.
If we take this enumeration, convert rational numbers to their expansions in a base, and apply the diagonal argument,
the resulting quantity *should* be an irrational number.
We can take this enumeration and convert each rational number to positional expansions in a base.
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
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
@ -171,8 +174,8 @@ However, it's still possible the argument constructs a number already equal to s
Into Silicon
------------
Before going any further, let's convert the diagonal argument into a function.
We want to, given an enumeration of binary sequences, produce a new sequence not on the list.
Before going any further, let's write a function for applying the diagonal argument to
a list of binary sequences.
This can be implemented in Haskell fairly easily:
@ -236,7 +239,7 @@ oneFifth = fromBinSeq 64 $ tail $ binDiv 1 5
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
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.
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>
@ -282,8 +286,8 @@ renderDiagTable badDiag 8
Computing the diagonal sequence, it quickly becomes apparent that we're going
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.
The length of a binary expansion grows logarithmically rather than linearly; in other words,
we can't count fast enough by just adding 1 (or adding any number, really).
The length of a binary expansion grows logarithmically, but going down the diagonal is a linear process.
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.
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.
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}
--| code-fold: true
--| classes: plain
@ -379,13 +390,9 @@ renderTransformTable n = renderTable
renderTransformTable 8 $ take 8 $ sbDiag id
```
For completeness's sake, the decimal expansions of the first few numbers which pop are as follows:
```{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
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.
In fact, this just gives us a bijection between the original enumeration and a new one containing our irrationals.
The new numbers we get depend heavily on the order of the original sequence.
In fact, this just gives us a bijection between the original enumeration
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.
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
@ -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
with any rationals.
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.
@ -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
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.
```{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.
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}
sbDiagSeq3 = transform sbDiagSeq2