From 118657d2fc413acaebc8ec9ccda0b94de2754922 Mon Sep 17 00:00:00 2001 From: queue-miscreant Date: Wed, 30 Jul 2025 00:37:47 -0500 Subject: [PATCH] extra revisions to number-number.2 --- posts/number-number/2/Previous.hs | 9 +++- posts/number-number/2/index.qmd | 86 +++++++++++++++++-------------- 2 files changed, 56 insertions(+), 39 deletions(-) diff --git a/posts/number-number/2/Previous.hs b/posts/number-number/2/Previous.hs index 4c0aec8..7372342 100644 --- a/posts/number-number/2/Previous.hs +++ b/posts/number-number/2/Previous.hs @@ -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 diff --git a/posts/number-number/2/index.qmd b/posts/number-number/2/index.qmd index 9943089..0b0569f 100644 --- a/posts/number-number/2/index.qmd +++ b/posts/number-number/2/index.qmd @@ -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.
@@ -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