In university I learned a lot of different stuff but one of my favorite topics in the "Computer Theory" class was URM (Unlimited Register Machine).
URM serves to demonstrate some concepts about computers and the computability of some operations. I won't go very deep into details of the history or theory, but it's similar to the theory of the Turin machine, which states that given a set of ordered instructions (called algorithm) and an infinite tape, the machine is capable of run that algorithm and give a result for valid inputs.
The registers in URM work much alike to an infinite sequential number of cells enumerated by position. You can only input numbers in the registers. The preconditions or information about this registers depends mostly on the author, but most agree that if the register hasn't been touched; it does not contain data. Other authors prefer to just go and say every register contains a zero before being touched by the machine.
To obtain a result of a computable algorithm (or demonstrate it's not computable) URM provides us with 3 basic operations. Some books or authors mention a fourth operation, but the fourth operations I have seen on paper are achievable with just the 3 basics ones (names or letters may vary depending on the author):
Z(position)Zero operation. Replaces whatever is in the register at
positionwith a 0
I(position)Increment operation. Adds 1 to whatever is on the register at
position. If we are strict… if nothing is at the register it cannot add 1 to it (so you need to invoke
J(positionX, positionY, instruction)Jump operation. Compares the value of register at
positionY,when the value is the same continues the execution at
instruction. Is valid to send a
instructionout of range, this will indicate the immediate end of the execution
This also varies from author to author, but the idea is the same: You have an ordered and identified list of calls for operations on registers representing your algorithm. Once the list ends, URM will stop.
Using a generic notation (there is not a formal or standard one), the list will look like:
1: Z(5) 2: I(5) 3: Z(2) 4: J(2, 5, 7) 5: I(5) 6: I(2) 7: J(2, 2, 6)
It is also possible to provide preconditions to the machine to indicate a set of previously filled data, or how the algorithm works using variables (all lowercase):
Will indicate that the registers at position 0 and 3 started with a value. URM will proceed to read the instructions with this assumption and give a result.
Will indicate the existence of unknown numbers with names
y . This will later execute with real values, the concept of variable does not exist in URM. There's also a chance to provide preconditions for the
X: [x,y] -> x < y
This depends totally on the author and implementation. In most cases you state that on paper (or in some doc for your implementation).
There's no formal indication on where the result will be. I created a small set of instructions to copy the result to the register at position 0. In that way you can check for a specific result in that position. Copying to different positions can get complicated but you can simply state that every set of functions works on different URMs (multiple machines). Some say this is cheating, but it helps if you want to reuse ideas. Remember this works in theory.
A set of instructions with the precondition of having a single number at register 1 and copying the value to the register 0
X: [,y] 1: J(0, 1, 6) 2: Z(0) 3: J(0, 1, 6) 4: I(0) 5: J(0, 0, 3)
You can then use this to invoke as a new operations (you will end up writing something similar but with different register position).
This is something I wanted to share (and something I'll use for later examples) and I hope gives you the same amount of fun it gave to me. It's fun to code this as an exercise. I recommend you to give it a try (I know I will in Kotlin).