Thinking about action the Belfast way

Logics preserve truth. If you start with premises that are true and transform them using logical rules, then your conclusions are also true. Algorithms do things. If you start with an input, following the steps of an algorithm will transform it to an output. There’s a fab way to apply logic to actions that was developed in Belfast by computer scientist Tony Hoare (1969), building on work by Robert Floyd (1967). I learned about this in Belfast, 2000; here’s an attempt to explain it.

A recipe is a kind of algorithm. Its inputs are ingredients and the steps involve peeling, chopping, heating, stirring. The output is a particular dish. But there’s no guarantee what the resulting dish will be if you start from the wrong ingredients, even if you follow the steps.

Hoare formalises this intuition for computer algorithms in what is now known as a Floyd-Hoare triple:

\(P \{Q\} R\)

\(P\) is the precondition (you have the right ingredients), \(Q\) is the sequence of steps of the algorithm (or recipe), and \(R\) is the postcondition (the resulting dish).

So \(P\) and \(R\) are statements that could be either true or false (logic as usual) and \(Q\) is sequence of actions (something different). The triple, \(P \{Q\} R\), as a whole is a statement that is true or false – that’s the key innovation. The triple lifts actions into the world of truths.

Here is how to read the triple (Hoare, 1969, p. 577):

“If the assertion \(P\) is true before initiation of a program \(Q\), then the assertion \(R\) will be true on its completion.”

We need axioms and rules of inference to reason about all the different kinds of program, for instance, doing one thing after another; choosing what to do, depending on whether some condition is true; and repeating things until a condition is met. These are the main ingredients of any program.

Assignment

Things quickly get complicated. Let’s start with a simple assignment statement. I’ll use an R-ish notation:

\(y \leftarrow x + 42\)

If you run this program, then \(y\) will be set to whatever \(x\) is plus 42.

The axiom for thinking about assignment statements (the axiom of assignment) is as follows:

\(P_0 \{ x \leftarrow f \} P\),

where \(P_0\) is the result of taking \(P\) and replacing all occurrences of \(x\) with \(f\). That’s how Hoare writes it. We could also write:

\(P[f/x] \{ x \leftarrow f \} P\),

where \(P[f/x]\) is another way to express the replacement in symbols. (I remember what it means because “/” is a forward slash, so it’s “\(f\) for \(x\)”.)

Now let’s suppose, for our one-liner program above, we want our postcondition to be that \(y\) is strictly positive. We have the following triple from the axiom of substitution:

\((y > 0)[x + 42 / y]\ \{ y \leftarrow x + 42\}\ y > 0\).

We can calculate \((y > 0)[x + 42 / y]\) by examining \(y > 0\) and replacing all occurrences of \(y\) (there’s one) with \(x + 42\). So we get:

\(x + 42 > 0\ \{ y \leftarrow x + 42\}\ y > 0\).

Equivalently,

\(x > -42\ \{ y \leftarrow x + 42\}\ y > 0\).

Let’s give this a go with an example. Suppose \(x = -41.9\). Then if we run \(y \leftarrow x + 42\), the result is \(-41.9 + 42 = 0.1\) and \(0.1 > 0\). It worked, the postcondition is true!

Suppose we try the program with \(x = -42\). Now the precondition doesn’t hold (the ingredients are wrong). If we run \(y \leftarrow x + 42\), the result is \(-42 + 42 = 0\) and so the postcondition doesn’t hold either.

If-else

That’s not a particularly impressive program. Let’s try something requiring an if-else: a program for finding the maximum of \(x\) and \(y\) and saving the answer in \(m\). Here’s a first go at the postcondition:

\(m \ge x \land m \ge y\).

In words, \(m\) should be greater than or equal to both \(x\) and \(y\). We would also probably like \(m\) to be one of \(x\) or \(y\), but let’s begin with this weaker postcondition.

Here’s a program:

if \((x \ge y)\) then
    \(m \leftarrow x\)
else
    \(m \leftarrow y\)

There is no apparent precondition (well, we could explicitly require that the inputs are valid values on which there is an ordering). The if-else gives us preconditions on the two routes through the code for free, as per the annotations below in square brackets:

if \((x \ge y)\) then
    \([x \ge y]\)
    \(m \leftarrow x\)
else
    \([x < y]\)
    \(m \leftarrow y\)

That is, in the arm of the if-else that only runs when \(x \ge y\), we can assume that \(x \ge y\). (We are assuming that the program which runs R programs is correct. Proving this is definitely somebody else’s problem.)

We can now reduce the program to two triples about assignment statements, one for each arm of the if-else:

\(x \ge y\ \{m \leftarrow x \}\ m \ge x \land m \ge y\)

\(x < y\ \{m \leftarrow y \}\ m \ge x \land m \ge y\)

Take the first. If we carry out the substitution on the postcondition as per the axiom of assignment, the resulting precondition is \(x \ge x \land x \ge y\). So we have now proved:

\(x \ge x \land x \ge y\ \{m \leftarrow x \}\ m \ge x \land m \ge y\)

But the triple we want to prove is:

\(x \ge y\ \{m \leftarrow x \}\ m \ge x \land m \ge y\)

We need some way to link the derived precondition, \(x \ge x \land x \ge y\), onto the one we have on this arm of the if-else, \(x \ge y\).

The way to do so is using Hoare’s rules of consequence. There are two such rules. The one we need is:

If \(P\{Q\}R\) and \(S \Rightarrow P\), then \(S\{Q\}R\).

We got \(P\) from the rule of assignment and \(S\) is the precondition we have from the if-else, so next we need to prove \(S \Rightarrow P\):

\(x \ge y\ \Rightarrow\ x \ge x \land x \ge y\).

It’s easy to see that this holds. It is indeed true that \(x \ge x\) and we get \(x \ge y\) for free from the antecedent. So by the rule of consequence, we have proved

\(x \ge y\ \{m \leftarrow x \}\ m \ge x \land m \ge y\)

Take the second arm of the if-else. If we carry out the substitution on the postcondition as per the axiom of assignment and apply the rule of consequence, we have to prove:

\(x < y\ \Rightarrow\ y \ge x \land y \ge y\).

Which is equivalent to

\(x < y\ \Rightarrow\ x \le y \land y \ge y\).

Again it is easy to see that this holds. If \(x < y\), then clearly \(x \le y\). And \(y \ge y\) holds too.

We have now proved that both routes through the program satisfy the postcondition, so we are done.

Now let’s try again and check that by the end of program \(m = x\) or \(m = y\). Here are the two triples we need to prove (\(\lor\) is the symbol for “or”):

\(x \ge y\ \{m \leftarrow x \}\ m = x \lor m = y\)

\(x < y\ \{m \leftarrow y \}\ m = x \lor m = y\)

Apply the axiom of assignment to them both:

\(x = x \lor x = y\ \{m \leftarrow x \}\ m = x \lor m = y\)

\(y = x \lor y = y\ \{m \leftarrow y \}\ m = x \lor m = y\)

So we need to prove the following two propositions, via the rule of consequence:

\(x \ge y \Rightarrow (x = x \lor x = y)\)

\(x < y \Rightarrow (y = x \lor y = y)\)

which are equal to

\(x \ge y \Rightarrow \mathbf{true}\)

\(x < y \Rightarrow \mathbf{true}\)

Both reduce to \(\mathbf{true}\). We’re done!

Loops

Now the fun begins. Hoare (1969, p. 578) provides the following rule of iteration, mildly translated into R as follows:

If \(P \land B\ \{S\}\ P\), then \(P\) {while (\(B\)) \(S\) } \(\neg B \land P\). 

Note how \(P\) appears in both the precondition and postcondition of the loop. In later papers, this became known as the loop invariant.

Something else to note: there are two ways for \(B\) to be false. One is that, given some inputs and code before the loop, we already have the answer so there’s no need to run the loop. The other is that the loop has run at least once and now finished looping.

I want to find a program to illustrate how this works using the smallest number of assignment statements in the loop. The smallest I can think of, with one statement, counts down from (nonnegative) \(n\) to \(0\) and doesn’t do anything else:

\(i \leftarrow n\)
while (\(i > 0\))
    \(i \leftarrow i\ – 1\)

The problem with this is that there’s no interesting loop invariant.

So we will need more than one statement in the while loop. This means we need Hoare’s rule of composition (again mildly translated to R):

If \(P\{Q_1\}R_1\) and \(R_1 \{Q_2\} R\), then

\(P\)
\(\{Q_1\)
 \(Q_2\}\)
\(R\)

(Hoare used an explicit composition operator, \(;\). R allows a newline to compose statements. It does have the \(;\) too – often used when you want to squeeze multiple statements onto one line.)

The simplest program I can think of that actually does something requires two statements in the while loop: it multiplies a number, \(x\), by a natural number, \(n\), using repeated addition. Let’s save the result in \(r\). So the postcondition of the program is \(r = xn\).

I’m going to use the rule of iteration to help write the program. The easiest case is when \(n = 0\): \(x0 = 0\). Let’s suppose the loop doesn’t run when \(n = 0\) The program for this case is then simply:

\(r \leftarrow 0\)

Let’s use \(i\) as the loop variable. We need to find a suitable loop invariant – something that is true on each iteration of the loop. Let’s try \(r = xi\). So, a mild addition to the program above leads to:

\(r \leftarrow 0\)
\(i \leftarrow 0\)

This suggests that \(i\) needs to iterate from \(0\) up to \(n\) (without having to look after the loop invariant, I probably would have looped down from \(n\)). Here’s a first try for the whole program:

\(r \leftarrow 0\)
\(i \leftarrow 0\)
while (\(i \ne n\)) {
        \(r \leftarrow r + x\)
        \(i \leftarrow i + 1\)
}

Back to the rule of iteration, we need to prove:

\(r = xi \land i \ne n\ \{r \leftarrow r + x; i \leftarrow i + 1\}\ r = xi\)

Let’s begin with the last assignment statement. Using the axiom of assignment we get:

\(r = x(i+1)\ \{ i \leftarrow i + 1 \}\ r = xi\)

Your first thought on looking at that precondition might be, er, what?

But glance at the line above, \(r \leftarrow r + x\). If this has worked, then \(r\) has incremented by \(x\) and we just need to increment \(i\)  too so that the invariant still holds. I’m going to take this precondition and feed it through the previous line to calculate a postcondition using the axiom of assignment:

\(r + x = x(i+1)\ \{ r \leftarrow r + x \}\ r = x(i+1)\)

Before we think about what’s going on in the precondition on the left, we can glue both statements together by the rule of composition. Call the two liner in the while loop, \(T\). Now we have:

\(r + x = x(i+1)\ \{ T \}\ r = xi\)

We want to prove:

\(r = xi \land i \ne n \ \{T\}\ r = xi\)

This looks like another case for the rule of consequence we used above. Let’s fill in the variables with the information we have:

If both of these conditions hold:

(1) \(r + x = x(i+1)\ \{ T \}\ r = xi\)
(2) \(r = xi \land i \ne n\ \Rightarrow\ r + x = x(i+1)\),

then \(r = xi \land i \ne n \ \{T\}\ r = xi\).

We just need to prove the second conditional. We get \(r = xi\) from the antecedent. Adding \(x\) to both sides gives us

      \(r + x = xi + x\)
\(\equiv\)
      \(r + x = x(i + 1)\)

We’re done!

Strictly speaking, we need to drag the axiom of assignment and rule of composition up the program to check that running

\(r \leftarrow 0\)
\(i \leftarrow 0\)

establishes the invariant before the loop. Without using the formal axioms, I think it’s easy to see that they do and we did think about this when writing the program.

Additionally, we really need to show that the program establishes \(r = xn\). By the end of the loop, we know that \(i = n\) (otherwise the loop hasn’t terminated – also it’s part of the rule of iteration) and \(r = xi\) (by the loop invariant). So \(r = xn\) does indeed hold.

References

Floyd, R. W. (1967). Assigning meanings to programs. In J. T. Schwartz (Ed.), Proceedings of Symposium on Applied Mathematics (pp. 19–32).

Hoare, C. A. R. (1969). An axiomatic basis for computer programming. Communications of the ACM, 12(10), 576–583.

Listing numbers

By the end of this post you will understand how to list all (yes, all!) integers and rational numbers and why you can’t list the reals. You will also learn why this weird sequence of numbers is useful:

\(1, 2, 1, 1, 2, 3, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 5, \ldots\)

The integers

There’s an easy way to list all the natural numbers without leaving any gaps. Start with 0, then add 1 to the previous number:

\(0, 1, 2, 3, 4, 5, \ldots\)

You will never reach the end, but wherever you stop, you can be confident that you have all the naturals up to that point.

For integers, list the naturals, then for each natural, \(n\), add \(-n\) as follows:

\(0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5, \ldots\)

This, by the way, is sequence A001057 in the On-line Encyclopedia of Integer Sequences.

The reals

We can’t list the real numbers, as Cantor showed in 1891.

To see why, let’s explore the reals from 0 to strictly less than 1, or \([0,1)\) for short.

Suppose, for the sake of argument, that we could list them all. This means there will be a first number, a second, a third, and so on. So let’s number them and write some arbitrarily chosen examples to get a sense of how the list might look. I’ve put the ith digit in bold for reasons that will soon be apparent.

i Decimal expansion
1 0.00013…
2 0.01202…
3 0.00310…
4 0.12772…
…

Here’s Cantor’s trick. Work down the infinite list of numbers to create a new number in the following way.

  • From the the 1st number take the 1st digit after the decimal point and subtract it from 9: \(9 – 0 = 9\).
  • From the the 2nd number take the 2nd digit after the decimal point and subtract it from 9: \(9 – 1 = 8\).
  • From the the 3rd number take the 3rd digit after the decimal point and subtract it from 9: \(9 – 3 = 6\).
  • From the the 4th number take the 4th digit after the decimal point and subtract it from 9: \(9 – 7 = 2\).
  • More generally, from the ith number take the ith digit after the decimal point and subtract it from 9.

Now use these subtractions to generate a new number. So for the example above, we just subtract 9 from the numbers in bold to make:

0.9862…

Our assumption was that we had listed all the real numbers in \([0,1)\). But here we have created a new number that differs from each one in that list by at least one digit. Our assumption that we could list all the numbers in \([0,1)\) has led to the conclusion that we can’t: a contradiction. If we can’t do it for \([0,1)\) then surely we can’t list all the reals.

Although there are infinitely many integers, we can list them. The number of real numbers is too big an infinity to list!

The rationals: Cantor’s zigzag

We can, however, list the rational numbers in a way that’s guaranteed not to leave any out. Here are two ways to do it. I’ll focus on the non-negative rationals from which it is easy to get the negatives.

The first is due to Cantor, let’s call it the zigzag, and is easiest to show in a picture than to explain. The diagram below puts denominators on the columns and numerators on the rows. Start with \(1/1\) and follow the arrows:

Beautiful, isn’t it? This zigzag solves the problem that if we go down the first column, say,

\(1/1, 1/2, 1/3, 1/4, \ldots\),

we head off to infinity without ever moving to the second column, so, for example, we would miss out \(2/5\) and \(3/4\).

I think about this in terms of compass directions. Begin by adding \(1/1\) to your list of rationals and heading east. Let’s call the numerator \(p\) and denominator \(q\), so we are enumerating \(p/q\). Here’s what happens at each step:

  • If we are heading east then set \(p \leftarrow p + 1\) and add \(p/q\) to our list of rationals. Head southwest on the next step.
  • If we are heading southwest, then set
    \(p \leftarrow p – 1\)
    \(q \leftarrow q + 1\)
    Add \(p/q\) to our list of rationals.
    If \(p = 1\), i.e., we have hit the leftmost column, then head south on the next step.
  • If we are heading south then set \(q \leftarrow q + 1\) and add \(p/q\) to our list of rationals. Head northeast on the next step.
  • If we are heading northeast, then set
    \(p \leftarrow p + 1\)
    \(q \leftarrow q – 1\)
    Add \(p/q\) to our list of rationals.
    If \(q = 1\), i.e., we have hit the top row, then head east on the next step.

All I have done above is spell out the zigzag pattern. You will see that if we are heading east or south, then we just take one step then change direction. Whereas if we are heading southwest or northeast then we keep going until we hit the edge.

The sequences of numerators and denominators are A092543 and A092542, respectively. It’s interesting looking at the numbers and trying to forget that you know how they were generated. Like, what on earth is going on with

\(1, 2, 1, 1, 2, 3, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 5, \ldots\)

It makes sense when you look at the numerators in the zigzag diagram. (I bet this sequence is used in an IQ test or job selection exam somewhere, so basically it just asks if you’re aware how to enumerate the rationals.)

The zigzag is great because it’s easy to see that it works. However, it has two problems: (1) we get the same numbers multiple times, e.g., \(1/2\) and \(2/4\) are there and (2) related to this, the fractions aren’t always in their simplest forms, i.e., the numerators and denominators will often have common factors over 1 (again \(2/4\) is an example – top and bottom can be divided by 2).

The rationals: Stern-Brocot’s sandwich

Enter the Stern-Brocot tree. Stern, a mathematician, discovered the approach in 1858 and Brocot, a clockmaker, discovered it independently in 1861 in the process of devising systems of gears. Hayes (2000) delves into the history.

I’m doing to ignore the tree aspect of Stern-Brocot’s approach because I don’t think it helps understand how it works. Instead, let’s consider a multilayer Stern-Brocot sandwich (not on Google when I searched, incidentally) as follows.

Begin with the two fractions \(0/1\) and \(1/0\). The first is zero, the second is weird – we usually aren’t allowed to divide by zero. But you could think of it as an infinitely large number. (Hayes quotes an anonymous source who says that it is “infinity reduced to lowest terms”.)

Now we fill the sandwich by taking the mediant of these two fractions. This is defined:

\(\displaystyle  \mathit{mediant}\left( \frac{p_L}{q_L},  \frac{p_R}{q_R} \right) = \frac{p_L + p_R}{q_L + q_R}\).

In other words, simply sum the numerators and sum the denominators of the left and right fractions to get, respectively, the numerator and denominator of the mediant.

Okay, here we go. Start with our sandwich bread(?):

\(\displaystyle \frac{0}{1} \qquad   \frac{1}{0}\)

Fill it with the mediant:

\(\displaystyle \frac{0}{1} \qquad \mathbf{\frac{1}{1}} \qquad \frac{1}{0}\)

Now we have created the number 1. Let’s go again, filling the gaps between numbers we have using the mediant:

\(\displaystyle \frac{0}{1} \qquad \mathbf{\frac{1}{2}}  \qquad \frac{1}{1} \qquad \mathbf{\frac{2}{1}} \qquad \frac{1}{0}\)

One more go:

\(\displaystyle \frac{0}{1} \mathbf{\quad \frac{1}{3}} \quad \frac{1}{2} \mathbf{\quad \frac{2}{3}} \quad \frac{1}{1} \mathbf{\quad \frac{3}{2}} \quad \frac{2}{1} \mathbf{\quad \frac{3}{1}} \quad \frac{1}{0}\)

Lovely! You get \(2^s + 1\) rationals on each step, \(s\), e.g., the last step above is step three and has \(2^3 + 1 = 9\) numbers. Follow the steps 10 times and you get 1,205 rationals.

It can be proved this works: all non-negative rationals are enumerated in the correct order and in simplest forms. I shall delegate the proofs to a Google search…

By the way, one reason we can list the rationals at all is because there’s the same number of them as natural numbers.

Algorithms are to programs as information is to data

‘An algorithm is a precisely-defined sequence of rules telling how to produce specified output information from given input information in a finite number of steps. A particular representation of an algorithm is called a program, just as we use the word “data” to stand for a particular representation of “information”.’

– Donald E. Knuth (1974, p. 323) [Computer Science and its Relation to Mathematics. The American Mathematical Monthly, 81(4), 323–343]

Surreal numbers

The surreal numbers (No) were developed (or discovered, depending on what you think mathematics is) by John “Don’t mention Game of Life” Conway. He first introduced them in lectures in the 1970s (see Conway, 2001). The name, surreal numbers, was coined by Donald Knuth (1974) in the first book on the surreals, an undergrad maths text thinly disguised as a novelette (hear Knuth talk about the book).

I’ve been fiddling with the dyadic rational part of the surreals in the eves for the past couple of weeks in R and am now beginning to wrap my head around \(\omega\) and beyond. These notes explain where I’ve got to. You can check out the R over here, which currently gets as far as addition and a few other useful functions.

Familiar friends

All the kinds of numbers we are already familiar with are special cases of surreal:

  • Natural numbers (\(\mathbb{N}\)) are in there:
    0, 1, 2, 3, 4, …
  • As are the integers (\(\mathbb{Z}\)), the natural numbers and their negation:
    …, -4, -3, -2, -1, 0, 1, 2, 3, 4, …
  • Rational numbers (\(\mathbb{Q}\)): the numbers that can be expressed as a fraction of two integers, \(p/q\), where \(q \ne 0\). We get the integers by setting \(q = 1\).
  • The real numbers (\(\mathbb{R}\)): everything above, plus irrational numbers, such as \(\pi\) and \(\sqrt{2}\), that lie between rationals.

So, next time you calculate \(2 + 2\) or \(\pi r^2\) you could say you are doing surreal arithmetic.

But there’s much more in the surreals: infinitely big numbers, bigger than any real number, and infinitesimally small numbers, smaller than any positive real but yet bigger than zero. An amazing feature of the surreals is that all the above, from naturals to these huge and miniscule numbers, can be defined in a concise way.

Defining the surreals

This is how to define them:

If
(a) \(L\) and \(R\) are two sets of surreal numbers and
(b) there is no \(l \in L, r \in R\) such that \(l \ge r\),
then \((L|R)\) is a surreal number.

The notation \(x \in A\) means that \(x\) is a member of set \(A\). For instance, \(42 \in \{ 7, 27, 42 \}\).

This definition is recursive, i.e., defined in terms of itself. A surreal, \((L|R)\), is an ordered pair of sets of surreals, \(L\) and \(R\), left and right. It works because as you follow the \(L\) and \(R\) threads, you eventually reach an empty set and can stop, as we shall see.

I’m using a blend of the notation by Conway (2001) and Knuth (1974).  To make it easier refer to the left and right parts of a surreal, \(x= ( L | R )\) can be written \(( x_L | y_R )\). Note that \(L\) or \(R\) can be infinitely big sets. That turns out to be how interesting surreals are built.

Something to ponder: to define the surreals in this way, we already needed some notion of set theory that allows infinitely big sets and ordered pairs. To what extent is the structure of the surreals determined by the structure of the chosen set theory? (I don’t have an answer.)

Zero

Surreals are pairs of sets of surreals, but we don’t have any surreals yet. The only way to bootstrap them into existence is with empty sets (\(\emptyset\) or \(\{ \}\)), so the simplest surreal number is \((\emptyset | \emptyset)\). It turns out that this is zero – the same zero we know and love, so we can call it 0. We can also write this as \(( | )\) or \((\{ \} | \{ \} )\).

How can we tell if \(( | )\) really is zero? We could try to prove if it behaves like a zero, e.g., \(x + 0 = x\) and \(0x = 0\), for all \(x\). Not today. But let’s check something simpler: is 0 at least a surreal number? We need to check that there is no \(l \in 0_L, r \in 0_R\) such that \(l \ge r\). That’s easy to do since \(0_L = 0_R = \{ \}\). There’s nothing at all in either set, so 0 passes the test and is indeed surreal.

One and minus one

Our next candidate surreal numbers are built by plugging zero into left, right, or both:

  • \(( 0 |  )\)
  • \((  | 0 )\)
  • \(( 0 | 0 )\)

The first two are fine, by a similar argument to the one we used for 0: since one of the pair is the empty set, there’s nothing in there to threaten the absence of the greater-than-or-equal-to condition.

The interpretation of these numbers is:

  • \(( 0 | ) = 1\)
  • \(( | 0 ) = -1\)

Intuitively, \(( 0 | 0 )\) isn’t a surreal number since left and right are equal. But we haven’t actually defined a way to think about this yet.

Let’s now define \(\ge\).

\((x_L | x_R) \ge (y_L | y_R)\) if and only if
(a) there is no \(x_R\) such that \(y \ge x_R \) and
(b) there is no \(y_L\) such that \(y_L \ge x\).

Note how this definition is recursive, mirroring how surreals are constructed. Again, it relies on the empty set to stop. If there is no \(x_R\) or \(y_L\) at all then it’s vacuously true.

Let’s use this to test if \((0|0) =( (\{\}|\{\})|(\{\}|\{\}))\) is surreal.

We want nothing on the left to be greater than or equal to anything on the right, so we don’t want \(0 \ge 0\). But this does hold.

\(0 \ge 0\), that is \((\{\}|\{\}) \ge (\{\}|\{\})\), if and only if (a) there is no element \(0_R\) such that \(0 \ge 0_R\) and (b) no \(0_L  \ge 0\). Both conditions do hold:

(a) \(0_R = \{ \}\). There is nothing there, so nothing that 0 could be greater than or equal to.
(b) \(0_L = \{ \}\), so again nothing that could be greater than or equal to 0.

Therefore \(0 \ge 0\) and thus \((0|0)\) is not a surreal number.

Other numbers up to ω

Next comes

  • \(( 1 | ) = 2\)
  • \(( | 1 ) = -2\)

To make the numbers explicit, we can paste in their constituent surreals, e.g.,

\(( 1 | ) = ( ( 0 | ) | ) = ( ( (\emptyset | \emptyset) | ) | )\)

We also get the first rationals:

  • \(( 0 | 1) = 1/2\)
  • \(( -1 | 0) = -1/2\)

And, amongst other numbers, a new representation for zero:

  • \(( -1 | 1) = 0\)

This is analogous to how \(1/2 = 2/4 = 3/6 = \ldots\)

How did Conway convince himself that these really are the numbers represented by this notation? I expect first by trying arithmetic on a few of them to get a sense of how they work, e.g., is \(1/2 + 1/2 = 1\)? He then proved the properties of arithmetic for all surreals (see, e.g., Conway, 2001, Chapter 1).

Let’s go back to the naturals, which are also surreals. As you create 0, 1, 2, 3, …, put them in a set. So now we have a set of infinitely many surreals, \(\{ 0, 1, 2, 3, … \}\), which can be used to make another surreal, \(\omega\):

\(\omega = (0, 1, 2, 3, \ldots | )\)

That is, \(\omega_L = \{ 0, 1, 2, 3, … \} \) and \(\omega_R = \{  \} \).

This is the first transfinite surreal number and the first number that isn’t a natural, integer, rational, or real.

There’s a lot more in there, but I’ll stop here for now. Here’s a map by Simons (2017, p. 37) of the landscape of surreals and the order they are created.

A map of the surreal numbers, by Jim Simons (2017)

What does the (L | R) notation mean?

Now I’m going to depart from the many formal introductions of surreals which prove that everything works (see the further reading below) and move onto the intuitive meaning of \((L|R)\), drawing on Conway and Guy (1996, pp. 283-291).

The notation \((L = a, b, c, \ldots | R = d, e, f, \ldots)\) refers to the simplest number strictly greater than everything in \(L\) and strictly less than everything in \(R\).

Apparently to understand “simplest” you need to learn about a two-player game called Hackenbush. I am not joking, this is not the name of a new BBC Radio 4 comedy. It’s part of the field of combinatorial game theory, and what prompted the discovery of the surreals in the first place. Tom Davis has written a program that allows you can play Hackenbush against the computer. It’s actually quite easy to play and requires no knowledge of the surreals. The idea is that surreals can be represented as states in the game, and which of the two players can win tells you something about the value of the number. But Hackenbush is for another blog post.

The examples Conway and Guy provide illustrate the general idea.

  • \(( | ) = 0\), “the simplest number of all” (ibid., p. 283).
  • \((0|) = 1\), the simplest number greater than 0.

Many distinct representations can refer to the same number, e.g., these are all 2:

  • \((0,1|) = 2\), the simplest number greater than 0 and 1.
  • \((1|) = 2\), the simplest number greater than 1.
  • \(( -1, 0, 1|) = 2\), the simplest number greater than -1, 0, and 1.

Here’s how fractions work:

  • \((0|1) = 1/2\), the simplest number greater than 0 and less than 1.

And negative numbers:

  • \((|0) = -1\), the simplest number less than 0.
  • \((| -1 ) = -2\), the simplest number less than -1.

So, to make sense of a surreal expressed as \((L|R)\), the trick is first to strip out numbers in \(L\) and \(R\) which don’t constrain the number. Leave the biggest in \(L\) and the smallest in \(R\).

One way to think of “simplest” is in terms of the order in which the surreals were created, e.g., 0 first, then -1, 1, and so on. The simplest is the earliest created.

Wise words from Matthew Roughan (I have mildly edited the notation):

“naively, you might expect that the form \(( 3 | 17 )\) could be mapped to the mean of the two sets, i.e., 10. However, \((x_L | x_R)\) is the simplest number such that \(x_L < x < x_R\), so, in fact, this form is equivalent to 4.”

This checks out in my R, which is a relief:

> equal(surreal(dali(3,0), dali(17,0)), dali(10,0))
[1] FALSE
> equal(surreal(dali(3,0), dali(17,0)), dali(4,0))
[1] TRUE

Where \(\mathit{surreal}(l, r) = (l|r)\), \(\mathit{dali}(n, k)\) gives the surreal of \(n/2^k\), and \(\mathit{equal}(x, y) \equiv x = y\).

As you might guess from how vague this section is, I don’t completely get this yet and this is a note to future me. Stay tuned.

Automagically building dyadic rational surreals

Here are the first few surreals with singleton sets left and right, courtesy of R, for your arithmetical pleasure (let me know if I’ve missed any or added too many):

Let's make some surreal numbers!

Day 0
Starting with: ∅
Checking: (∅ | ∅) - Yes

Day 1
Starting with: ∅, (∅ | ∅)
Checking: (∅ | (∅ | ∅)) - Yes
Checking: ((∅ | ∅) | ∅) - Yes
Checking: ((∅ | ∅) | (∅ | ∅)) - No

Day 2
Starting with: ∅, (∅ | ∅), (∅ | (∅ | ∅)), ((∅ | ∅) | ∅)
Checking: (∅ | (∅ | (∅ | ∅))) - Yes
Checking: (∅ | ((∅ | ∅) | ∅)) - Yes
Checking: ((∅ | ∅) | (∅ | ∅)) - No
Checking: ((∅ | ∅) | (∅ | (∅ | ∅))) - No
Checking: ((∅ | ∅) | ((∅ | ∅) | ∅)) - Yes
Checking: ((∅ | (∅ | ∅)) | ∅) - Yes
Checking: ((∅ | (∅ | ∅)) | (∅ | ∅)) - Yes
Checking: ((∅ | (∅ | ∅)) | (∅ | (∅ | ∅))) - No
Checking: ((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅)) - Yes
Checking: (((∅ | ∅) | ∅) | ∅) - Yes
Checking: (((∅ | ∅) | ∅) | (∅ | ∅)) - No
Checking: (((∅ | ∅) | ∅) | (∅ | (∅ | ∅))) - No
Checking: (((∅ | ∅) | ∅) | ((∅ | ∅) | ∅)) - No

Day 3
Starting with: ∅, (∅ | ∅), (∅ | (∅ | ∅)), ((∅ | ∅) | ∅), (∅ | (∅ | (∅ | ∅))), (∅ | ((∅ | ∅) | ∅)), ((∅ | ∅) | ((∅ | ∅) | ∅)), ((∅ | (∅ | ∅)) | ∅), ((∅ | (∅ | ∅)) | (∅ | ∅)), ((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅)), (((∅ | ∅) | ∅) | ∅)
Checking: (∅ | (∅ | (∅ | (∅ | ∅)))) - Yes
Checking: (∅ | (∅ | ((∅ | ∅) | ∅))) - Yes
Checking: (∅ | ((∅ | ∅) | ((∅ | ∅) | ∅))) - Yes
Checking: (∅ | ((∅ | (∅ | ∅)) | ∅)) - Yes
Checking: (∅ | ((∅ | (∅ | ∅)) | (∅ | ∅))) - Yes
Checking: (∅ | ((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅))) - Yes
Checking: (∅ | (((∅ | ∅) | ∅) | ∅)) - Yes
Checking: ((∅ | ∅) | (∅ | ∅)) - No
Checking: ((∅ | ∅) | (∅ | (∅ | ∅))) - No
Checking: ((∅ | ∅) | (∅ | (∅ | (∅ | ∅)))) - No
Checking: ((∅ | ∅) | (∅ | ((∅ | ∅) | ∅))) - No
Checking: ((∅ | ∅) | ((∅ | ∅) | ((∅ | ∅) | ∅))) - Yes
Checking: ((∅ | ∅) | ((∅ | (∅ | ∅)) | ∅)) - No
Checking: ((∅ | ∅) | ((∅ | (∅ | ∅)) | (∅ | ∅))) - No
Checking: ((∅ | ∅) | ((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅))) - No
Checking: ((∅ | ∅) | (((∅ | ∅) | ∅) | ∅)) - Yes
Checking: ((∅ | (∅ | ∅)) | (∅ | (∅ | ∅))) - No
Checking: ((∅ | (∅ | ∅)) | (∅ | (∅ | (∅ | ∅)))) - No
Checking: ((∅ | (∅ | ∅)) | (∅ | ((∅ | ∅) | ∅))) - Yes
Checking: ((∅ | (∅ | ∅)) | ((∅ | ∅) | ((∅ | ∅) | ∅))) - Yes
Checking: ((∅ | (∅ | ∅)) | ((∅ | (∅ | ∅)) | ∅)) - Yes
Checking: ((∅ | (∅ | ∅)) | ((∅ | (∅ | ∅)) | (∅ | ∅))) - Yes
Checking: ((∅ | (∅ | ∅)) | ((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅))) - Yes
Checking: ((∅ | (∅ | ∅)) | (((∅ | ∅) | ∅) | ∅)) - Yes
Checking: (((∅ | ∅) | ∅) | (∅ | ∅)) - No
Checking: (((∅ | ∅) | ∅) | (∅ | (∅ | ∅))) - No
Checking: (((∅ | ∅) | ∅) | ((∅ | ∅) | ∅)) - No
Checking: (((∅ | ∅) | ∅) | (∅ | (∅ | (∅ | ∅)))) - No
Checking: (((∅ | ∅) | ∅) | (∅ | ((∅ | ∅) | ∅))) - No
Checking: (((∅ | ∅) | ∅) | ((∅ | ∅) | ((∅ | ∅) | ∅))) - No
Checking: (((∅ | ∅) | ∅) | ((∅ | (∅ | ∅)) | ∅)) - No
Checking: (((∅ | ∅) | ∅) | ((∅ | (∅ | ∅)) | (∅ | ∅))) - No
Checking: (((∅ | ∅) | ∅) | ((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅))) - No
Checking: (((∅ | ∅) | ∅) | (((∅ | ∅) | ∅) | ∅)) - Yes
Checking: ((∅ | (∅ | (∅ | ∅))) | ∅) - Yes
Checking: ((∅ | (∅ | (∅ | ∅))) | (∅ | ∅)) - Yes
Checking: ((∅ | (∅ | (∅ | ∅))) | (∅ | (∅ | ∅))) - Yes
Checking: ((∅ | (∅ | (∅ | ∅))) | ((∅ | ∅) | ∅)) - Yes
Checking: ((∅ | (∅ | (∅ | ∅))) | (∅ | (∅ | (∅ | ∅)))) - No
Checking: ((∅ | (∅ | (∅ | ∅))) | (∅ | ((∅ | ∅) | ∅))) - Yes
Checking: ((∅ | (∅ | (∅ | ∅))) | ((∅ | ∅) | ((∅ | ∅) | ∅))) - Yes
Checking: ((∅ | (∅ | (∅ | ∅))) | ((∅ | (∅ | ∅)) | ∅)) - Yes
Checking: ((∅ | (∅ | (∅ | ∅))) | ((∅ | (∅ | ∅)) | (∅ | ∅))) - Yes
Checking: ((∅ | (∅ | (∅ | ∅))) | ((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅))) - Yes
Checking: ((∅ | (∅ | (∅ | ∅))) | (((∅ | ∅) | ∅) | ∅)) - Yes
Checking: ((∅ | ((∅ | ∅) | ∅)) | ∅) - Yes
Checking: ((∅ | ((∅ | ∅) | ∅)) | (∅ | ∅)) - No
Checking: ((∅ | ((∅ | ∅) | ∅)) | (∅ | (∅ | ∅))) - No
Checking: ((∅ | ((∅ | ∅) | ∅)) | ((∅ | ∅) | ∅)) - Yes
Checking: ((∅ | ((∅ | ∅) | ∅)) | (∅ | (∅ | (∅ | ∅)))) - No
Checking: ((∅ | ((∅ | ∅) | ∅)) | (∅ | ((∅ | ∅) | ∅))) - No
Checking: ((∅ | ((∅ | ∅) | ∅)) | ((∅ | ∅) | ((∅ | ∅) | ∅))) - Yes
Checking: ((∅ | ((∅ | ∅) | ∅)) | ((∅ | (∅ | ∅)) | ∅)) - No
Checking: ((∅ | ((∅ | ∅) | ∅)) | ((∅ | (∅ | ∅)) | (∅ | ∅))) - No
Checking: ((∅ | ((∅ | ∅) | ∅)) | ((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅))) - No
Checking: ((∅ | ((∅ | ∅) | ∅)) | (((∅ | ∅) | ∅) | ∅)) - Yes
Checking: (((∅ | ∅) | ((∅ | ∅) | ∅)) | ∅) - Yes
Checking: (((∅ | ∅) | ((∅ | ∅) | ∅)) | (∅ | ∅)) - No
Checking: (((∅ | ∅) | ((∅ | ∅) | ∅)) | (∅ | (∅ | ∅))) - No
Checking: (((∅ | ∅) | ((∅ | ∅) | ∅)) | ((∅ | ∅) | ∅)) - Yes
Checking: (((∅ | ∅) | ((∅ | ∅) | ∅)) | (∅ | (∅ | (∅ | ∅)))) - No
Checking: (((∅ | ∅) | ((∅ | ∅) | ∅)) | (∅ | ((∅ | ∅) | ∅))) - No
Checking: (((∅ | ∅) | ((∅ | ∅) | ∅)) | ((∅ | ∅) | ((∅ | ∅) | ∅))) - No
Checking: (((∅ | ∅) | ((∅ | ∅) | ∅)) | ((∅ | (∅ | ∅)) | ∅)) - No
Checking: (((∅ | ∅) | ((∅ | ∅) | ∅)) | ((∅ | (∅ | ∅)) | (∅ | ∅))) - No
Checking: (((∅ | ∅) | ((∅ | ∅) | ∅)) | ((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅))) - No
Checking: (((∅ | ∅) | ((∅ | ∅) | ∅)) | (((∅ | ∅) | ∅) | ∅)) - Yes
Checking: (((∅ | (∅ | ∅)) | ∅) | ∅) - Yes
Checking: (((∅ | (∅ | ∅)) | ∅) | (∅ | ∅)) - No
Checking: (((∅ | (∅ | ∅)) | ∅) | (∅ | (∅ | ∅))) - No
Checking: (((∅ | (∅ | ∅)) | ∅) | ((∅ | ∅) | ∅)) - Yes
Checking: (((∅ | (∅ | ∅)) | ∅) | (∅ | (∅ | (∅ | ∅)))) - No
Checking: (((∅ | (∅ | ∅)) | ∅) | (∅ | ((∅ | ∅) | ∅))) - No
Checking: (((∅ | (∅ | ∅)) | ∅) | ((∅ | ∅) | ((∅ | ∅) | ∅))) - Yes
Checking: (((∅ | (∅ | ∅)) | ∅) | ((∅ | (∅ | ∅)) | ∅)) - No
Checking: (((∅ | (∅ | ∅)) | ∅) | ((∅ | (∅ | ∅)) | (∅ | ∅))) - No
Checking: (((∅ | (∅ | ∅)) | ∅) | ((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅))) - No
Checking: (((∅ | (∅ | ∅)) | ∅) | (((∅ | ∅) | ∅) | ∅)) - Yes
Checking: (((∅ | (∅ | ∅)) | (∅ | ∅)) | ∅) - Yes
Checking: (((∅ | (∅ | ∅)) | (∅ | ∅)) | (∅ | ∅)) - Yes
Checking: (((∅ | (∅ | ∅)) | (∅ | ∅)) | (∅ | (∅ | ∅))) - No
Checking: (((∅ | (∅ | ∅)) | (∅ | ∅)) | ((∅ | ∅) | ∅)) - Yes
Checking: (((∅ | (∅ | ∅)) | (∅ | ∅)) | (∅ | (∅ | (∅ | ∅)))) - No
Checking: (((∅ | (∅ | ∅)) | (∅ | ∅)) | (∅ | ((∅ | ∅) | ∅))) - Yes
Checking: (((∅ | (∅ | ∅)) | (∅ | ∅)) | ((∅ | ∅) | ((∅ | ∅) | ∅))) - Yes
Checking: (((∅ | (∅ | ∅)) | (∅ | ∅)) | ((∅ | (∅ | ∅)) | ∅)) - Yes
Checking: (((∅ | (∅ | ∅)) | (∅ | ∅)) | ((∅ | (∅ | ∅)) | (∅ | ∅))) - No
Checking: (((∅ | (∅ | ∅)) | (∅ | ∅)) | ((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅))) - Yes
Checking: (((∅ | (∅ | ∅)) | (∅ | ∅)) | (((∅ | ∅) | ∅) | ∅)) - Yes
Checking: (((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅)) | ∅) - Yes
Checking: (((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅)) | (∅ | ∅)) - No
Checking: (((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅)) | (∅ | (∅ | ∅))) - No
Checking: (((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅)) | ((∅ | ∅) | ∅)) - Yes
Checking: (((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅)) | (∅ | (∅ | (∅ | ∅)))) - No
Checking: (((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅)) | (∅ | ((∅ | ∅) | ∅))) - No
Checking: (((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅)) | ((∅ | ∅) | ((∅ | ∅) | ∅))) - Yes
Checking: (((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅)) | ((∅ | (∅ | ∅)) | ∅)) - No
Checking: (((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅)) | ((∅ | (∅ | ∅)) | (∅ | ∅))) - No
Checking: (((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅)) | ((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅))) - No
Checking: (((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅)) | (((∅ | ∅) | ∅) | ∅)) - Yes
Checking: ((((∅ | ∅) | ∅) | ∅) | ∅) - Yes
Checking: ((((∅ | ∅) | ∅) | ∅) | (∅ | ∅)) - No
Checking: ((((∅ | ∅) | ∅) | ∅) | (∅ | (∅ | ∅))) - No
Checking: ((((∅ | ∅) | ∅) | ∅) | ((∅ | ∅) | ∅)) - No
Checking: ((((∅ | ∅) | ∅) | ∅) | (∅ | (∅ | (∅ | ∅)))) - No
Checking: ((((∅ | ∅) | ∅) | ∅) | (∅ | ((∅ | ∅) | ∅))) - No
Checking: ((((∅ | ∅) | ∅) | ∅) | ((∅ | ∅) | ((∅ | ∅) | ∅))) - No
Checking: ((((∅ | ∅) | ∅) | ∅) | ((∅ | (∅ | ∅)) | ∅)) - No
Checking: ((((∅ | ∅) | ∅) | ∅) | ((∅ | (∅ | ∅)) | (∅ | ∅))) - No
Checking: ((((∅ | ∅) | ∅) | ∅) | ((∅ | (∅ | ∅)) | ((∅ | ∅) | ∅))) - No
Checking: ((((∅ | ∅) | ∅) | ∅) | (((∅ | ∅) | ∅) | ∅)) - No

Further reading

Conway (2001) and Knuth (1974) provide the classic introductions. Former GCHQ mathematician turned teacher, Jim Simons (2017), has written an accessible intro with loads of proofs, as has retired software engineer Claus Tøndering (2019). I found Matthew Roughan’s (2019) paper handy too and have just started playing with his Julia implementation (on GitHub). Frans Faase has written a neat web-based calculator in JavaScript that does simple sums and (in)equalities. Here’s Tom Davis’s implementation of Hackenbush which includes a tutorial. Elwyn Berlekamp produced a great YouTube video which explains how to play Hackenbush. Finally, here’s my attempt to implement surreal addition and a few other bits and pieces in R.

References

Conway, J. H. (2001). On numbers and games (2nd ed.). A. K. Peters, Ltd.

Conway, J. H., & Guy, R. K. (1996). The book of numbers. Springer-Verlag.

Knuth, D. E. (1974). Surreal numbers. Addison-Wesley.

Roughan, M. (2019). Practically surreal: Surreal arithmetic in Julia. SoftwareX, 9, 293–298.

Simons, J. (2017). Meet the surreal numbers. Handout to accompany a talk at the 2017 annual conference of the Mathematical Association.

Tøndering, C (2019). Surreal Numbers – An Introduction (Version 1.7). Unpublished note.

More magic

Photo of the infamous magic switch, by Guy L. Steele Jr; licenced under CC BY-NC-SA 3.0. Source.

“You don’t touch an unknown switch on a computer without knowing what it does, because you might crash the computer. The switch was labeled in a most unhelpful way. It had two positions, and scrawled in pencil on the metal switch body were the words ‘magic’ and ‘more magic’. The switch was in the ‘more magic’ position.

“I called another hacker over to look at it. He had never seen the switch before either. Closer examination revealed that the switch had only one wire running to it! The other end of the wire did disappear into the maze of wires inside the computer, but it’s a basic fact of electricity that a switch can’t do anything unless there are two wires connected to it. This switch had a wire connected on one side and no wire on its other side.

“It was clear that this switch was someone’s idea of a silly joke. Convinced by our reasoning that the switch was inoperative, we flipped it. The computer instantly crashed.”

Read on…

The Halting Problem in R

It’s possible to write a program that peeks at another program and tells you something about it. Here’s an R function that tells you how long the definition of a function is:

library(stringr)

function_length <- function(func) {
  body(func) |>
    as.character() |>
    str_length() |>
    sum()
}

It can even be applied to itself: function_length(function_length) happens to be 42.

It might be handy to have an R function that tells you whether a function terminates in finite time, returning TRUE if it does and FALSE if it runs forever. Something like this, with magic in the ellipsis to make it work:

terminates <- function(f) {
  if (...)
    TRUE
  else
    FALSE
}

For instance, consider this function:

is_a_loop_is_a_loop <- function() {
  while (TRUE) {}
}

Calling terminates(is_a_loop_is_a_loop) should return FALSE, since it has an infinite while loop.

The challenge of finding such a function is known as the halting problem. It turns out that there is no general function – a result due to Alan Turing (1937).

Here is an R version of Christopher Strachey’s (1965) proof, inspired by a proof Turing told him verbally “in a railway carriage on the way to a conference” in 1953.

Suppose for the sake of argument that terminates does exist, with the ellipsis above filled in, and that it works as we hope.

Now, define another function, taking_the_p, as follows:

taking_the_p <- function() {
  if (terminates(taking_the_p)) 
    while (TRUE) {}
}

The argument has a Russell’s paradox flavour to it.

Suppose terminates(taking_the_p) is TRUE. Then the if statement will dutifully trigger an infinite loop, meaning that taking_the_p doesn’t terminate. So terminates got it wrong. Suppose taking_the_p does actually terminate. That means terminates(taking_the_p) must have returned FALSE – again it was wrong.

But we started the proof by assuming that terminates actually works. Contradiction. Therefore there is no general function terminates.

There are countably infinite computable functions. The terminates function is an example that is not computable.

This general result does not mean it is never possible to determine whether a function terminates. There’s an easy heuristic: run a function for a bit and if it hasn’t finished after a period of time, decide that it doesn’t terminate. It’s far from the general solution, but for what it’s worth here’s the R:

library(R.utils)

terminates <- function(expr, secs = 1) {
  terminates <- FALSE
  res <- NULL
  try({
    res <- withTimeout(expr,
                       timeout = secs)
    terminates <- TRUE
  }, silent = TRUE)

  terminates
}

Note that this takes an expression rather than a function, so that it easily applies to functions of any arguments.

Let’s try two examples: the identity function (which simply returns its argument) applied to itself, and is_a_loop_is_a_loop. For the latter, note the empty parentheses, (), which is how to ask R to run a function rather than inspect it.

terminates(identity(identity))
# [1] TRUE

terminates(is_a_loop_is_a_loop())
# [1] FALSE

The timeout is doing the heavy lifting. I have chosen 1 second, which would rule out a large number of functions that do terminate.

References

Strachey, C. (1965). An impossible program. The Computer Journal, 7(4), 313–313.

Turing, A. M. (1937). On Computable Numbers, with an Application to the Entscheidungsproblem. Proceedings of the London Mathematical Society, 42(1), 230–265.

Enumerating r x c contingency tables with fixed grand total

One way: recursion over the flattened length of the table, i.e., to produce sequences of length rc.

  • Base case: e(1,n), the contingency sequence with 1 element, contains just the grand total, n.
  • Step case: to compute e(l,n) for l > 1, you need
    • 0 conjoined with each sequence produced by e(l-1,n), i.e., all tables one size smaller with the originally desired grand total
    • 1 conjoined with each e(l-1,n-1)
    • 2 conjoined with each e(l-1,n-2)
    • …
    • n-1 conjoined with each e(l-1,1)
    • n conjoined with each e(l-1,0)

The number of 2×2 contingency tables seems to be given by the triangular pyramidal numbers, 1, 4, 10, 20, 35, 56, 84, 120, 165, 220, 286, 364, 455, 560, 680, 816, 969, … which is a sum of triangular numbers.

There’s a bunch of work on the geometry of contingency tables.  Fun stuff!  (Ended up playing with this to generate stimuli for an experiment.)