You may have been thinking that Turing machines were already rather simplified models of real computers. Here we explore how far we can push the notion of simplicity!
0
and 1
.
That being so, we can interpret the tape as a pair of binary numbers:
for example,
... | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 0 | ... |
left | ^ | right |
can be read as the numbers
left =by reading outwards from the tape position. Now suppose, for example, we write100101101
[or 361], right =00100111
[or 228]
1
and move left:
... | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 0 | ... |
left | ^ | right |
corresponding to the numbers
left =We see that the effect has been to double right and add one [because we wrote one], and to halve left, and that the symbol now under the read head is00101101
[or 180], right =100100111
[or 457].
1
because the old value of left
was odd.
In other words, these changes could be emulated by a chunk of program looking rather like:
if state = 17 and symbol = 1 then if "left is odd" then nextsymbol = 1 else nextsymbol = 0 endif left = left / 2 right = right × 2 + 1 nextstate = 23 endifrepeated over every possible state and symbol, and the whole shoved into a simple loop which also assigns
symbol = nextsymbol; state = nextstate
before going around again.
Now, this could be a universal Turing machine, so we don't need all that
many states, and what we then have is a universal program, which
can, in principle, do anything that any computer can do, provided merely
that it is started with the correct values of left, right,
state and symbol;
what is more, it needn't be a big program -- recall that the `Minsky'
UTM had only 23 states, though more than two symbols.
We deduce that we could build a universal program from any computing
language with the following capabilities:
test whether a number is odd, double a number, halve it, increment it, assign numbers to variables, test for equality, logical and of two conditions, if statements and loops.The only catch is that two of the numbers could be extremely large [though not horrendously so -- no bigger than the active part of the tape!]. But most of these capabilities are redundant!
double: | halve: | test for odd: |
---|---|---|
temp = 0 while number > 0 do decrement number increment temp increment temp endwhile while temp > 0 do decrement temp increment number endwhile |
temp = 0 while number > 0 do decrement number increment temp endwhile while temp > 0 do decrement temp if temp > 0 then decrement temp increment number endif endwhile |
temp = 0 while number > 0 do decrement number increment temp endwhile while temp > 0 do decrement temp increment number odd = 1 if temp > 0 then decrement temp increment number odd = 0 endif endwhile |
while number > 0 do decrement number endwhile
and any other constant assignment by assigning zero and then incrementing
an appropriate number of times.
Also, the logical and is redundant:
if status = 17 then if symbol = 1 then ...
So, we now need:
increment a number, decrement it, if statements and loops.What is more, the ifs and whiles can all be reduced to a very simple form, namely tests against zero. Now we still have several variables; we don't need so many, as for example the three variables a, b and c can be encoded into the one number jumbo = 2a×3b×5c, with an obvious extension to more variables. Now, for example, decrementing b corresponds to dividing jumbo by 3, which we can again do by the techniques above. We can't reduce everything to a single variable, because we need the temp variable above as an auxiliary; but we can get down to two.
Conclusion: all we need in a programming language is the ability to
increment or decrement either of two integers, and do ifs and whiles based on whether they are greater than zero.Everything else is icing on the cake! [There are various alternative formulations of this result, most using
goto
statements to replace the ifs and whiles, but you
need either two sorts of goto
, or else an extra known
zero variable, and you also then need to be able to label
parts of the program.]
Note that our universal program is now a fixed, and quite small [a few thousand lines, even after all the expansions] program written in a tiny language for a computer which has only this minimal instruction set and the capacity to store two (admittedly rather large) integers. This program, running on this computer, can do anything that any other program on any computer, or indeed, by the Church-Turing thesis, that any effective process, can do. It may just be a little slower.
o | o | o | ||||||||||||||||
o | o | o | o | o | o | o | o | o | o | |||||||||
o | o | o | o | o | o | o | o | o | o | o | o | o | o | o | o | o | o | |
| ||||||||||||||||||
blank | m | n | blank |
This seems to be as good a place as any to solve the halting problem: