what are the real numbers?

511 days ago by mpaul

Using pure Python, let's define a function domain(a, b, dx) that will return a discrete interval starting at a and ending at b (or as close to b as possible) stepping by dx.

 
       

One of the first things to notice is that this is an arithmetic sequence.

An arithmetic sequence begins somewhere and continues by repeatedly adding a constant.

If n is the number of steps needed to get from a to b jumping by dx, then

a + n\cdot dx = b, and n = \frac{b-a}{dx}.

Since n is supposed to be a count, it has to be an integer.

However, for many values of a, b, and dx, it won't work out that way.  There will be a remainder.

Therefore, n = \lfloor \frac{b-a}{dx} \rfloor is the maximum number of steps of dx we can take, beginning at a, without exceeding b.

We can express our position in the interval as a + i \cdot dx where 0 \le i \le n:

def domain(a, b, dx): nsteps = int((b-a)/dx) return [a+i*dx for i in range(nsteps+1)] 
       
domain(0, 1, 1/5) 
       
 
       

(Feel free to change values in the expression above and see what happens.)

Another way to accomplish this might be to use a generator.

You can think of a generator as a function with 'memory'.

A typical function returns its value and then completely forgets its state.

The next time you call it, it has no memory of having been called before.

In contrast, a generator yields its values one at a time, remembering its state between yields.

def domain(a, b, dx): while a <= b: yield a a += dx 
       
x = domain(0, 1, 1/5) x 
       

When you evaluate x above you don't get a list.  Instead, you find that x refers to a generator object.

You can access the generator's values by using the function next().

As you repeatedly evaluate the following cell, it will step through all values in x:

next(x) 
       
 
       

You can use a generator just like a list in constructing loops or list comprehensions:

[x**2 for x in domain(0, 1, 1/5)] 
       

If you want to see all the values of a generator at once in a list, you can list() them:

list(domain(0, 1, 1/5)) 
       
 
       

You can think of a generator as a potential list.

A generator could potentially be an infinite list!  That's pretty cool.

When you create a list through list comprehension, all of the values are generated at once, but

when you create a generator, each value in the list is generated one at a time as needed.

This can be useful both for program design and for conserving resources.

And, in terms of Math Analysis, it also gives us a great way to think about what the real numbers are.

 
       

Here's why -

It is impossible to create a generator whose next() function will yield all irrational numbers!

This is not impossible because of any technological reason but because of a purely mathematical one -

The irrational numbers cannot be listed.  They cannot be listed even using an infinite list!

This is really interesting ... and really important for Analysis.

You see, it actually IS possible to list the rationals!  This is significant.

We will create a rational number generator below that will, in principle, list all rational numbers.

 
       

Going beyond Python, Sage provides interval notation:

[0..1, step = 1/5] 
       

This makes lots of things easier!  : )

@interact def domain(a = 0, b = 1, dx = 1/5): D = [a..b, step = dx] show(D) print len(D),'elements' 
       

Click to the left again to hide and once more to show the dynamic interactive window

Experiment with the above.  Choose different values for a, b, dx and see what happens.

 
       

Important Question

What do you think?

In domain(0, 1, dx), as dx becomes infinitely small, will all possible values between 0 and 1 eventually show up in the list?

 
       

Another way to create a generator is just like interval notation except that you use ( ) in place of [ ]:

x = (0..1, step = 1/5) 
       
next(x) 
       

Now, as promised, here is a generator that will in fact list all of the rationals.

Seriously, all of them!  Any fraction you can think of will eventually appear in this sequence.

They will not be produced in ascending order, but every rational number will eventually be generated.

def rationals(): denominator = 1 yield 0; yield 1; yield -1 denominator += 1 while True: for numerator in [1..denominator]: if gcd(numerator, denominator) == 1: r = numerator/denominator for i in [r, -r, 1/r, -1/r]: yield i denominator += 1 Q = rationals() [next(Q) for i in range(30)] 
       
 
       

Here is a variation that creates a cumulative list of all the proper fractions in [0..1] whose denominators do not exceed n:

def rationals(n): for denominator in [1..n]: for numerator in [0..denominator]: if gcd(numerator, denominator) == 1: yield numerator/denominator @interact def _(n = 1): show(sorted(list(rationals(n)))) 
       

Click to the left again to hide and once more to show the dynamic interactive window

Important Question

What do you think? As n becomes infinitely large, will all possible values in [0 .. 1] eventually show up in the list?

 
       

So we see that the rational numbers can be listed.

This is what Georg Cantor proved.

He also proved something else -

that the irrational numbers cannot be listed.

 
       

For, suppose they could be.  After all, since we were able to do this with the rationals, why not with the irrationals?

Suppose you could create a generator irrational() that would generate all irrationals between 0 and 1.

Suppose you were going to let this generator run forever, supposedly listing all the irrationals.

Guess what Cantor proved? 

It is possible to construct the decimal representation of an irrational value that will never appear in that list!

Here's how:

  • Think of each irrational number in the list as an infinite list of digits.
  • Think of the list of irrational numbers itself as an infinite list of infinite lists, or, as an infinite matrix we'll call R.
  • R[i] will represent a row in R.  Each R[i] will represent an irrational number.
  • R[i][k] will represent the k^{th} digit in R[i]  .
  • Let d = [R[i][i] for i in [0..\infty]].  d will be an infinite list of digits created from the diagonal of R.
  • Change each of the digits in list d to some other digit, and call this new list r.
  • This list of digits r will not appear as any row in R.
    • For, suppose it did. Suppose it seemed to be the j^{th} row, so that r = R[j].
    • Because of how r was constructed, the j^{th} digits of r and R[j] will necessarily be different: r[j] ≠ R[j][j]!
    • Therefore, r ≠ R[j].
 
       

However, having worked our way through all of this, how real are the real numbers?

This is a really interesting topic to explore.