<p>In physical machine design, rather than triggering logic on both edges of a single clock and managing strict duty cycle constraints, designers often deploy two clocks separated by a 180-degree phase shift. This configuration, known as two-phase clocking, allows all registers to sample uniformly on a rising edge (or all on a falling edge) while permitting designers to adjust the phase separation to optimize timing margins. This approach can be used to accommodate any number of phase separated clocks. Alternatively, a single-edge-triggered clock can drive the entire control loop. In this design, the current state propagates through the instruction table, selects the driving status register in the TTU, passes through the status decoder, and resolves at the next-state matrix. Because the <RT·code>s</RT·code> and <RT·code>d</RT·code> registers are wired as appendages to this primary loop and also sample on the rising edge, the system must enforce strict hold-time requirements. This guarantees the registers capture the established state rather than the transient values newly propagating across the bus. Clocking designs that successfully maintain these operational margins across the extremes of manufacturing tolerances and thermal ranges are said to be <em>clean</em>.</p>
- <!--------------------------------------------------------------------------------->
- <RT·chapter>Address</RT·chapter>
-
-
- <h2>Unary Representation address</h2>
-
- <p>
- A person can define a Turing Machine that is identical to the recursive definition of Natural Numbers as given by Peano. <RT·endnote>Giuseppe Peano, <em>Arithmetices principia, nova methodo exposita</em> (Turin: Fratres Bocca, 1889).</RT·endnote> If a person were to run the Natural Number Machine and observe as it writes to the tape, that person would watch as the Natural Numbers are printed one after another: '·s·ss·sss·ssss· ...'. Here '·' represents zero, and 's·' represents one, etc. Because the Natural Number Machine never halts, the machine cannot be used to initialize a tape, but an analyzer can evaluate the machine to make conclusions about what it would write if it were run.
- </p>
-
- <p>
- In contrast, a function extension version of the Natural Number sequence generator can be run. The function extension machine would be given as input a prior function extension result, or an initial empty tape. It would then modify the tape to contain the next Natural Number, as per the sequence that the Natural Number machine would write, if it could be run. This is accomplished through two subroutine calls: <RT·code>find_empty</RT·code> and <RT·code>increment</RT·code>. The <RT·code>find_empty</RT·code> machine checks the symbol under the head. Upon finding it is not the empty symbol, the machine steps right and checks the next cell, repeating until it finds the empty symbol, whereupon it halts. The <RT·code>increment</RT·code> machine then writes an 's' onto the tape and halts. Recurrent calls to the Natural Number extender then produce a sequence of result tapes: <RT·code>[], [s], [ss], …</RT·code>. Similarly, a programmer can write a machine called <RT·code>decrement</RT·code>, though the programmer must note that decrementing can return the <RT·code>left-of-leftmost</RT·code> symbol.
- </p>
-
- <p>
- To say that Natural Number A is smaller than Natural Number B is to say that A would occur on the Natural Number Machine tape to the left of B, if the machine were run. Conversely, if B were said to be greater than A, that would mean B occurs further to the right. It is a simple matter for a programmer to write a decider machine for this. The decider is given two input tapes for the two numbers to compare, and it keeps a head on each. It then steps forward until neither head has an 's' under it. If empty symbols are found simultaneously on both tapes, then the two numbers are equal; otherwise, the number with an empty symbol under the head is the lesser number.
- </p>
-
- <p>
- As such, a person can assign a Natural Number to each cell of a given tape by using a mechanical procedure. Given a machine, say <RT·code>P</RT·code>, and an address machine, say <RT·code>A_P</RT·code>, each time <RT·code>P</RT·code> is stepped right, a call is made to run <RT·code>increment</RT·code> on <RT·code>A_P</RT·code>. Similarly, each time <RT·code>P</RT·code> is stepped left, a call is made to run <RT·code>decrement</RT·code> on <RT·code>A_P</RT·code>. In this manner the tape on <RT·code>A_P</RT·code> will always hold the address that machine <RT·code>P</RT·code>'s head is on.
- </p>
-
- <p>
- The Natural Number found on <RT·code>A_P</RT·code> is then called the address for the cell that machine <RT·code>P</RT·code>'s head is on. As each increment and decrement of the address is a constant time operation, keeping the address of the cell the head is on is computationally inconsequential.
- </p>
-
- <p>
- An <RT·term>address space</RT·term> is a set of addresses for contiguous cells. The tape's address space is the set of addresses for all the cells on the tape. Typically the address of 0 is given to the leftmost among the contiguous cells.
- </p>
-
-
-<h2>Binary Hindu-Arabic representation address</h2>
-
- <p>
- Would it be computationally consequential if binary Hindu-Arabic representation were used instead of unary for addresses? For unary representation each increment was constant time, but with Hindu-Arabic addresses, increments have a carry. Would the carry cause stepping to the right, combined with calling the address machine, to become slower and slower as the machine steps further and further?
- </p>
-
- <p>
- For this discussion, the Hindu-Arabic representation address is variable precision, so it will grow as the count becomes larger. The empty symbol will serve as the terminator.
- </p>
-
- <p>
- For <RT·code>Hindu-Arabic_increment</RT·code>, the machine reads the cell under the head; upon finding a 0 or the terminator, it writes a 1 and halts. Upon finding a 1, it writes a 0, steps right, and repeats the procedure.
- </p>
-
- <RT·code>
- increment:
- a = TTU.read()
- if a == 0 or a == □:
- TTU.write(1)
- halt
- TTU.write(0)
- TTU.step()
- goto increment
- </RT·code>
-
- <p>Here <RT·code>TTU</RT·code> is the tape transport unit. It has the number to be incremented mounted on it. This number is either zero, which would mean the leftmost cell is empty, or it is of the same form as a result from a Natural Number extension machine. A <RT·code>TTU.read</RT·code> places a copy of the symbol instance found in the cell under the head into the read buffer automatically when the machine enters a new state on the programmed controller, so it is not counted as a step. In contrast, the other actions are associated with the state of the programmed controller, so when the machine arrives at a <RT·code>write</RT·code>, <RT·code>step</RT·code>, or <RT·code>halt</RT·code> node, it means that the programmed state controller has taken a step. Sequential instructions mean unconditional next state choices, whereas an <RT·code>if</RT·code> signals a conditional next state choice. The <RT·code>if</RT·code> condition is a logical proposition based on the read symbol.</p>
-
- <p>The loop form here is worth taking note of, as it will come up again. The controller executes a first action, that of a <RT·code>read</RT·code>, followed by a middle break test, and then the recursive form actions.</p>
-
- <p>
- Each row shows an input tape, and actions taken to write the result tape. For any given row, the result tape is the same as the input tape on the next row down. The table lists 7 increments, which is sufficient to reach the maximum count that can be held by a 3 bit counter.
- </p>
-
- <table>
- <tr>
- <th>input</th>
- <th>action</th>
- <th>cost (steps)</th>
- </tr>
- <tr>
- <td>0</td>
- <td>write 1, halt (result is maximum 1 bit count)</td>
- <td>2</td>
- </tr>
- <tr>
- <td>1 </td>
- <td>write 0, step, write 1, halt</td>
- <td>4</td>
- </tr>
- <tr>
- <td>01</td>
- <td>write 1, halt (result is maximum 2 bit count)</td>
- <td>2</td>
- </tr>
- <tr>
- <td>11</td>
- <td>write 0, step, write 0, step, write 1, halt</td>
- <td>6</td>
- </tr>
- <tr>
- <td>001</td>
- <td>write 1, halt</td>
- <td>2</td>
- </tr>
- <tr>
- <td>101</td>
- <td>write 0, step right, write 1, halt</td>
- <td>4</td>
- </tr>
- <tr>
- <td>011</td>
- <td>write 1, halt (result is maximum 3 bit count)</td>
- <td>2</td>
- </tr>
- <tr>
- <td>111</td>
- <td></td>
- <td></td>
- </tr>
- </table>
-
- <p>
- The number of programmable controller state transition steps required to reach a maximum 1 bit, 2 bit, and 3 bit count are: <RT·math>[2, 8, 22]</RT·math>. Note these are cumulative. The three bit maximum count cost includes that of the two bit maximum count cost, etc. Each maximum count is exponentially larger, so a better measure is the average cost per step: <RT·math>[2/1, 8/3, 22/7] = [2, 2.667, 3.143]</RT·math>
- </p>
-
- <p>
- For an <RT·math>n</RT·math> bit counter, the sequence of costs follows a pattern. Half of the increments evaluate one bit (costing 2 steps), a quarter evaluate two bits (costing 4 steps), an eighth evaluate three bits (costing 6 steps), and so forth, over the <RT·math>2^n - 1</RT·math> increments required to reach the maximum <RT·math>n</RT·math> bit count:
- </p>
-
- <RT·math> \sum_{k=1}^{n} 2k · 2^{n-k} = 2^{n+2} - 2n - 4</RT·math>
-
- <p>
- To find the average cost per increment to reach each maximum, an analyst divides by the total number of increments, which is <RT·math>2^n - 1</RT·math>:
- </p>
-
- <RT·math> \text{Average Cost} = \frac{2^{n+2} - 2n - 4}{2^n - 1} = \frac{4(2^n-1)+4-2n-4}{2^n-1}</RT·math>
-
- <p>
- This simplifies algebraically to:
- </p>
-
- <RT·math> \text{Average Cost} = 4 - \frac{2n}{2^n - 1} </RT·math>
-
- <p>
- As the address space grows, the bit width <RT·math>n</RT·math> increases. The polynomial term <RT·math>2n</RT·math> is outpaced by the exponential denominator <RT·math>2^n - 1</RT·math>, causing the fractional terms to converge to zero. The average work done by the machine head converges to exactly four tape actions per increment. Constant time, same as for unary. This is a surprising counter intuitive result.
- </p>
-
- <p>
- Consequently, maintaining an Hindu-Arabic address on a secondary Address Machine remains a constant time operation in the amortized sense, making it computationally inconsequential to the complexity class of the base machine. However, if a problem were to increment and decrement repeatedly around a binary power count, the behavior would be the limiting logarithmic time due to the length of the counter. An unlucky situation like this is called <RT·term>aliasing</RT·term>.
- </p>
-
- <p>
- The model used here to define an address is analogous to keeping a pointer into memory. Each step then increments or decrements the pointer. Conventional processors do the converse of the Turing Machine model presented here. For a processor, the <em>address</em> is the base object, while the <em>head location</em> is what is implied by the address. On processors, pointers will be one word in width. In small geometries where computation element delay dominates, two operand adders tend towards logarithmic complexity evaluation times against bit width, and the constants are small. Because of this, it is practical to perform word width addition operations atomically in one machine cycle. In fact, for some machines, the adder time likely sets the clock period. Such an adder will be used for address arithmetic; consequently, a programmer cannot arrange a program to expose aliasing in the address increment time to the real world. It remains a theoretical observation.
- </p>
-
- <p>
- An address is sent on a trip through the memory hierarchy to locate the corresponding memory cell, ultimately landing on a memory decoder over a physically implemented memory. Address decoders resemble carry chains, and they also have logarithmic physical evaluation times as word width grows. Hence, a fixed width word holding an address gets decoded in approximately constant time measured in clock ticks. However, the trip through the memory hierarchy is not negligible.
- </p>
-
- <p>
- Direct address decoding operates independently of reference locality. This fundamentally differs from a Turing Machine head, which incurs a linear computation cost strictly proportional to the distance traveled. However, direct decoding is only the base of the memory hierarchy.
- </p>
-
- <p>
- It is possible that decoding occurs separately in a row and column manner, and changes in the lower bits of the address walk a buffered row that was found in a prior access. This makes access to neighbor cell addresses within a row a low expense operation. However, this depends on memory implementation. One layer up, it is common for memory to be made of interleaved banks, so neighbor access that remains in the leaf collection is generally faster than that which requires fetching a new leaf set.
- </p>
-
- <p>
- There will, however, be a more dominant effect. For programs with islands of locality of reference, instructions or data will be discovered in a cache. Caches bring in two types of locality: that of access within a cache line, which makes neighbor walks that stay in the line fast, and cache line prefetch and replacement policies, which assume for any given fetch address that a subsequent fetch will be nearby in memory. These effects lead to similar memory behavior as a head on tape, where a single step to a neighbor is faster than taking many steps to find memory further away, but there is not a one to one correspondence.
- </p>
-
- <p>
- Memory accesses into the processor local memory file are even faster. If locality of reference, even in an abstract sense, remains tight, then memory cells can be accessed in a minimal amount of time.
- </p>
-
- <p>
- For programs with large memory footprints and sparse memory access patterns, the dominant factor becomes the virtual memory subsystem. Here locality again comes into play as the translation lookaside buffer provides stateful location context, and the neighbor relationship between pages is sometimes taken into account for performance reasons. However, once a program starts performing at virtual memory page fetch times instead of cache access, or even local system memory access times, the program is said to be <RT·term>page thrashing</RT·term>, and it becomes very slow. It is also possible for small programs to have memory access patterns that alias against the memory subsystem's heuristics and lead to page thrashing. For a typical system, a programmer can write a small program to demonstrate this.
- </p>
-
- <p>
- In summary then, using Hindu-Arabic representation for addressing is not statistically computationally consequential. On the other hand, the idea of using a decoded address in place of a tape head raises computational model issues. So much so that it would not be an exaggeration to say that the very goal of modern computer architecture is to ensure that, within an execution context, address decode does not become computationally consequential. Architects employ a memory hierarchy to do this, and that memory hierarchy rewards locality of reference, but access times are not linear in time relative to distance from prior access. Instead they are clumped into a constant time access abstract concept of locality groups where the time to access each group grows exponentially with the distance from the processor.
- </p>
-
- <p>
- Let us put this into perspective. Suppose in ancient Roman times that a clock tick for a computer was scaled to be one day long, so that the action involves sending letters rather than variable values. Under this scale, a single nanosecond of real world execution time equates to three days. Suppose a program initiated a read request for a location in memory on the Ides of March, the date when Caesar was assassinated on -0043-03-15. Surely that was a big day for posting letters. The following table provides the historical date that the variable value would finally be loaded into the processor, depending on the memory tier being accessed:
- </p>
-
- <table>
- <thead>
- <tr>
- <th>Memory Tier</th>
- <th>Real World Latency</th>
- <th>Scaled Delay</th>
- <th>Scaled Arrival Era</th>
- <th>Historical Context</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>L1 Cache Hit</td>
- <td>1 ns</td>
- <td>3 days</td>
- <td>-0043-03-18</td>
- <td>Three days after the assassination.</td>
- </tr>
- <tr>
- <td>DRAM (Main memory)</td>
- <td>100 ns</td>
- <td>300 days</td>
- <td>-0042-01-09</td>
- <td>Nearly a year later, during the Liberators' civil war.</td>
- </tr>
- <tr>
- <td>NVMe SSD Page Swap</td>
- <td>15 µs</td>
- <td>45,000 days</td>
- <td>0080</td>
- <td>123 years later, exactly as the Colosseum is completed in Rome.</td>
- </tr>
- <tr>
- <td>SATA SSD Page Swap</td>
- <td>100 µs</td>
- <td>300,000 days</td>
- <td>0778</td>
- <td>821 years later, during the reign of Charlemagne and the Frankish Empire.</td>
- </tr>
- <tr>
- <td>Magnetic HDD Page Swap</td>
- <td>10 ms</td>
- <td>30,000,000 days</td>
- <td>82092</td>
- <td>Tens of thousands of years in the future, long after current human civilizations are dust.</td>
- </tr>
- </tbody>
- </table>
-
- <p>
- In computer architecture, there is a rule that the slower the access time, the larger the amount of data that should be transferred. The idea is then that of access time per bit, instead of access time per fetch. A common example given is that of a station wagon of hard drives driven across the country. <RT·endnote>Andrew S. Tanenbaum famously coined this concept in his 1981 textbook <em>Computer Networks</em>, stating: "Never underestimate the bandwidth of a station wagon full of tapes hurtling down the highway." This physical data transfer method is universally referred to as a sneakernet.</RT·endnote> Ten terabits, at three days, equates to 26 nanoseconds per bit. Not bad, if a user can wait three days to continue the program, but aliasing between data sets would make a program very slow. The Turing Machine model does not capture the effects of clumped data accesses.
- </p>
-
<!--------------------------------------------------------------------------------->
<RT·chapter>Area and partitioning</RT·chapter>
<p>Consider the case of a partitioned tape. Then consider that we write a gasket, such that we have a higher level Turing Machine that considers each of the areas of the partition as a cell. So then, initially the Turing machine starts with its head on the leftmost area, stepping right steps to the right neighbor area etc. In this manner we abstract the concept of a cell to areas. A length then becomes a count of areas; however the <RT·term>size</RT·term> remains a count of the cells. Something similar is done in the C language, where the length of an array is a count of the elements in the array, but the size of the array is a count of bytes. A byte being an addressable unit in physical memory, and thus the underlying <em>cell</em> that everything is built up from.</p>
- <!--------------------------------------------------------------------------------->
- <RT·chapter>The Universal Turing Machine</RT·chapter>
+ <RT·chapter>Hindu-Arabic representation</RT·chapter>
- <p>The Computer Theoretic model chapter provided symbolic definitions for the Turing machine and the TTCA variation. Those definitions were written as strings of characters, which the reader scanned, and presumably understood, thus demonstrating the ability of those text strings to convey meaning. Furthermore the text explained in detail how an executor could make use of those definitions so as to perform the input string transformations. As Turing originally noted, the executor could be a person. Alternatively, as the book continued on to describe in detail, the executor could be a machine that applied the input transformations automatically.</p>
+ <p>Charles Burnett published a very interesting book about the translation of Hindu-Arabic mathematics in Europe during the Middle Ages <RT·endnote>Charles Burnett, <em>Numerals and Arithmetic in the Middle Ages</em> (Farnham: Ashgate Variorum, 2010). DOI: 10.33137/aestimatio.v9i0.25990.</RT·endnote>. He describes a 9th-century treatise on Hindu-Arabic arithmetic authored by Muḥammad ibn Mūsā al-Khwārizmī, where "al-Khwārizmī" indicated he was from Khwarazm in Central Asia. When this manuscript was translated into Latin in the 12th century, European translators approximated his name as Algoritmi or Algorismi, thus giving us the word algorithm. He notes that the Arabs called such numbers "Indian Numbers". Another chapter, "Why we read Arabic numerals backwards," shows that the <RT·term>little-endian</RT·term> and <RT·term>big-endian</RT·term> debate that has plagued programmers for decades has its origin in the Middle Ages <RT·endnote>Danny Cohen, "On Holy Wars and a Plea for Peace," <em>IEEE Computer</em> 14, no. 10 (October 1981): 48-54. DOI: 10.1109/C-M.1981.220208</RT·endnote>.</p>
- <p>In his original paper Alan Turing put these things together and explained that a Universal Turing Machine could read the definition of a Turing Machine from tape, and thus be a Turing Machine executor. Hence, say, a Turing Machine reads the symbolic definition of a Turing Machine from one tape, and then automatically performs the described input string transformations on another tape. Said Universal Turing Machine would then be chameleon-like, performing the function of any other Turing Machine so described on the first tape.</p>
+ <p>The information about ENIAC in this chapter is found in a 1947 IRE article, "Electronic Computing Circuits of the ENIAC" by Arthur W. Burks <RT·endnote>Arthur W. Burks, "Electronic Computing Circuits of the ENIAC," <em>Proceedings of the IRE</em> 35, no. 8 (August 1947): 756-767. DOI: 10.1109/JRPROC.1947.234265</RT·endnote>. I found it interesting that some of the design issues for flip-flops made of vacuum tubes resemble those of designing static RAM cells in CMOS. Although ENIAC's implementation is electronic, its architecture is fundamentally that of a mechanical machine.</p>
- <p>The only information that the first tape of a Universal Machine need contain is the variable part, <RT·math>\mathit{MP}</RT·math>, which the text established as the <em>program</em>, as the remainder of the definition is common to all machines and can thus be built into the controller. <RT·math>\mathit{MP}</RT·math> describes a state controller, it lists the states, the symbols of the alphabet, the instruction to be issued from each state, the symbol gated next state transitions, and the halting state. The Universal Machine control program would then have to interpret that information and send the universal machine through the same steps that a human executor would take while running the described machine.</p>
+ <h2>Numeric value</h2>
- <p>However, the controller can be simplified if the program encoding is changed from the raw definition. Notice that the only information that leaves the state controller while it runs are the instructions issued per state, with that list terminating when the controller reaches the halt state.</p>
+ <p>An Hindu-Arabic number consists of a sequence of digits, <RT·math>d_0 d_1 d_2 \ldots</RT·math> where, in base 10, each digit has a value ranging from 0 to 9. In this section, these sequences are represented using sequence notation. For example, <RT·math>X = [7, 8, 9]</RT·math> is a sequence with three components. Its zero index component, <RT·math>x_0</RT·math>, is 7, etc. Note that <RT·math>X</RT·math> is written as a capital letter to denote that it is a container, while its individual members use small letters.</p>
- <p>Imagine then, mounting a tape on a given machine, running the machine, and recording the instructions that leave the controller up until it halts. Then taking that list of instructions, and the same input tape, and mounting them on a Playback Machine. The Playback Machine then takes the instructions from the list on the first tape and issues them out of its own controller one by one. The playback controller is quite simple. Though of course, this approach has the drawback of having to run the given machine first so as to observe it, thus making the playback run moot.</p>
+ <p>A person interprets these sequences as numbers by using a weighted sum.</p>
- <p>Consider then, inserting jump table instructions to handle the next state transitions. Then a state controller can be mechanically changed into an instruction sequence with embedded jump table instructions without having to run it and observe it. Accordingly, first examine the state diagram for the controller. Take all the sequential state runs, even those of length 1, from the controller, and list their instructions in the same sequence order. Then, after each such sequence, wherever a state has next state transition arcs based on the value of the status register, insert a jump table instruction, so that it jumps to the appropriate instruction sequence.</p>
+ <div style="margin-left: 2em;">
+ <RT·math>a = \sum_{i=0}^n x_i \cdot ten^i</RT·math>
+ </div>
- <p>Applying this mechanical procedure to the TTCA two-headed reverse string example controller results in:</p>
+ <p>Here <RT·math>a</RT·math> is a numeric value, so it is written in lower case. Each <RT·math>x_i</RT·math> is the <RT·math>i</RT·math>th component of the sequence <RT·math>X</RT·math>. The value <RT·math>x_i</RT·math> is also known as the '<RT·math>i</RT·math>th digit' of the number. The value ten is called the base or the radix of the number. It is fortunate that ten is so well known that it has a name, because otherwise an author is tempted to write the base while using the exact representation that he is trying to define. Although this function gives numeric meaning to our digit sequences, performing the suggested computation is pointless. The result is a number, and a person must represent that number, resulting in the exact sequence that was given as the input to the function in the first place.</p>
- <RT·code>
- # Phase 1: Both heads scan right to the EOM pivot
- L·initial:
- read('s' ,0)
- jump(s, {EOM: L·Check_Empty, def: L·Scan_Right})
+ <p>In many contexts, a person drops the container notation without causing ambiguity. Conventionally, dropping the notation reverses the order of the components. The sequence from the prior example, <RT·math>X = [7, 8, 9]</RT·math>, becomes <RT·math>x = 987</RT·math>. To be clear, note that this number is 13 away from a thousand.</p>
- L·Scan_Right:
- right(0)
- right(1)
- jump(def: L·initial)
+ <p>Independent of whether the number is represented with or without the bracket decoration, its most significant digit is the one with the greatest weight. In this example, it is the digit with the value 9, i.e., <RT·math>x_2</RT·math>. Analogously, the least significant digit is 7, i.e., <RT·math>x_0</RT·math>. Using a term from the previous chapter on arrays, the extent of our example number is 2. The digit count, or length, of this number is 3.</p>
- # Phase 2: Setup pointers or short-circuit on empty string
- L·Check_Empty:
- status(0)
- jump(s, {leftmost: L·Empty_Setup, def: L·Setup_Write})
+ <h2>Meaning of the word digital</h2>
- L·Empty_Setup:
- right(1)
- jump(def: L·Write_EOR_Done)
+ <p>The first computing machines which performed discrete state computation made use of ten symbols for a digit’s potential values while mimicking how humans perform manual arithmetic. Examples include Pascal’s calculator, Babbage’s machine, the mechanical calculators that came after, Aiken’s Mark computers, and ENIAC.</p>
- L·Setup_Write:
- right(1)
- left(0)
- jump(def: L·Copy_Read)
+ <p>Mechanical machines used physical gears with ten positions, each being 36 degrees apart. An index, such as a metal pointer, pointed at one of the marks, indicating which digit value had been registered on the gear. To change which digit value was registered, the gear turned until the index pointed at the mark corresponding to the desired digit value. Instead of gears, the electronic computer ENIAC used circular shift registers of ten vacuum tube flip-flops. These were called ring counters or decade counters. ENIAC operated on ten-digit signed numbers, so there were ten such ring counters plus a flip-flop for each number. It is structurally important that a person does not conflate the number of available digit values with the length of the digit sequence.</p>
- # Phase 3: The Linear Copy Loop
- L·Copy_Read:
- read('d' ,0)
- status(0)
- jump(s, {leftmost: L·Copy_Last, def: L·Copy_Loop})
+ <p>Mechanical machines, such as Howard Aiken’s Mark machines, gated rotational momentum with control linkages and clutches. ENIAC used an electronic analogy to the main rotating shaft: a central pulse clock. Pulses were sent to an electronic gate, and if the control to that gate allowed it, the pulses passed through to the corresponding ring counters to cause each flip-flop in the ring to flip in succession depending on the pulse count.</p>
- L·Copy_Loop:
- write('σ' ,0 ,SP)
- write('d' ,1)
- right(1)
- left(0)
- jump(def: L·Copy_Read)
+ <p>To add digit values on a mechanical computer, the two gears in question were mechanically connected. When one rotated back to zero, the other rotated up to the sum, possibly tipping a carry bar along the way. On ENIAC, one ring counter gated pulses to its partner while counting down. While pulses were gated to it, the second ring counter counted up, optionally setting a carry flip-flop.</p>
- # Phase 4: Final character, advance, and clean halt
- L·Copy_Last:
- write('σ' ,0 ,SP)
- write('d' ,1)
- right(1)
- jump(def: L·Write_EOR_Done)
+ <p>Hence the term digital initially meant both being discrete and making use of ten-state logic. If a person examines the control levers of the mechanical machines, or the control signals of ENIAC, he finds two-state switch logic, but computation units processed numbers consisting of ten-state digits, mimicking human computers. Early computing work gave us more than the techniques we take for granted today; it gave us words such as gate, register, and index.</p>
- L·Write_EOR_Done:
- write('σ' ,1 ,EOR)
- halt
- </RT·code>
+ <p>Earlier, Claude Shannon and others wrote extensively on methods for simplifying switch logic, making it apparent that direct binary computation could be performed. The first electronic computer to do so was the Atanasoff-Berry machine developed in the late 1930s. Because there are two states in switch logic, it is maximally efficient to use a base two number system for arithmetic. In such an arithmetic system, each binary digit has the value of either zero or one. It is conventional to shorten the term binary digit to <RT·term>bit</RT·term>. John von Neumann assigned Atanasoff to audio work during World War II, so the Atanasoff-Berry machine remained unused in a basement at Iowa State University.</p>
- <p>The state labels have become addresses into the program tape, and the address of the cell indicated by the head on the first tape is now an Instruction Pointer (IP). In a sense the programmed controller has been replaced by a little Turing Machine of its own. One that controls the head on the first tape, and moves it in response to the control instructions found on the first tape. Such a controller on a processor is called a <RT·term>sequencer</RT·term>.</p>
+ <p>Binary computation prevailed. By the 1950s almost all discrete state computers used two-state switch logic for computation. Still, the term digital continued to describe them. This leads to some curious naming conventions. For example, the company Digital Equipment Corporation, which began shipping computers in 1959, exclusively built binary computers. As another example, an engineer who studies modern digital electronics will never see wheels or decade counters used to implement operations in an ALU. Though the term digital continues to refer to computing with discrete state digits, the term has lost the connotation that there must be exactly ten of those states.</p>
- <p>By separating the control path from the data path, utilizing an integrated symbol alphabet, replacing states with sequential instructions, and introducing an explicit addressable instruction pointer, the theoretical machine has physically crossed the bridge to conventional computing. The resulting architecture is a stored-program, von Neumann-style machine organization.</p>
+ <p>Today, computing based on ten-state digits, such as what is found on handheld calculators, is known as decimal computing. If a machine made use of 256-state digits, it would be neither decimal nor binary. Because we lack a special name for the number 256, a person states that such a computer uses base 256 digits and that it is a base 256 computer. Decimal computing, binary computing, and base 256 computing are all examples of digital computing. Independent of the base for computation, control signals are almost universally on or off. We say these are Boolean or binary valued, sometimes referring to their values as one or zero, but note they represent logical states rather than numbers.</p>
- <p>Some of the default cases for the jump table unnecessarily jump to the instruction at the next sequential address. This code lacks the regularity to use a computed jump table, so this style of jump table becomes a Lisp <RT·code>cond</RT·code> statement, i.e. sequential conditional tests. So then by using the instructions <RT·code>test</RT·code>, <RT·code>beq</RT·code> (for branch on equal), and <RT·code>jump</RT·code> as control instructions, and rearranging to favor sequential execution, this code becomes:</p>
-
- <RT·code>
- # Phase 1: Both heads scan right to the EOM pivot
- L·initial:
- read('s' ,0)
- test('s' ,EOM)
- beq L·Check_Empty
+ <p>As computer implementations moved from ten-state logic to two-state logic, it was difficult to completely abandon base ten number systems, particularly in business applications where users expect dollars and cents results that match manual computations, even when fractions and rounding are involved. Hence, IBM designed a scheme whereby groups of 4 bits represent the decimal digits; this code is called binary coded decimal or BCD. A machine which processes numbers using BCD has a decimal architecture.</p>
- # L·Scan_Right (Implicit fall-through)
- right(0)
- right(1)
- jump L·initial
+ <p>Most computer users never see a memory dump. Instead, they see the output of print functions. By default, print functions render numbers in a decimal format familiar to the user, independent of the internal representation used for numbers. In contemporary computing, the time it takes to convert a binary number to a decimal number is negligible compared to the time required to execute a long computation, so decimal printing is practical.</p>
- # Phase 2: Setup pointers or short-circuit on empty string
- L·Check_Empty:
- status(0)
- test('s' ,leftmost)
- beq L·Empty_Setup
+ <h2>Scanning-Order and Digit-Order</h2>
- # L·Setup_Write (Implicit fall-through)
- right(1)
- left(0)
+ <p>Had this book been written in Hindu-Arabic, the text would be written right to left. Let us explore what that looks like by using an example where we start with an English sentence and then reverse it. Notice that in this right to left string, the sequence [9, 8, 7] without notation is 987. Both strings match.</p>
- # Phase 3: The Linear Copy Loop
- L·Copy_Read:
- read('d' ,0)
- status(0)
- test('s' ,leftmost)
- beq L·Copy_Last
+ <blockquote>
+ Writing from right to left the sequence [7, 8, 9] without notation is 987.
+ </blockquote>
- # L·Copy_Loop (Implicit fall-through)
- write('σ' ,0 ,SP)
- write('d' ,1)
- right(1)
- left(0)
- jump L·Copy_Read
+ <p>When reversed this becomes:</p>
- # Phase 4: Final character, advance, and clean halt
- L·Copy_Last:
- write('σ' ,0 ,SP)
- write('d' ,1)
+ <blockquote>
+ .987 si noitaton tuohtiw [9 ,8, 7] ecneuqes eht thgir ot tfel morf gnitirW
+ </blockquote>
- L·Empty_Setup:
- right(1)
+ <p>Following the 1,000-year-old convention, I did not reverse the order of the digits of the number. For the reversed sentence to make sense, I changed 'tfel ot thgir' (right to left) to 'left to right', which is the sort of adaptation that Middle Ages translators did not universally apply. Also, I changed the brackets so that they still enclose the sequence components; otherwise, they point outward. When reading the number, the eye first lands on a digit of unknown weight. A person must scan further right to find the one’s place, then scan back while counting the digits to make sense of the number. Reversing the string is insufficient; a person must understand the sentence and change the phrase "right to left", to "left to right".</p>
- # L·Write_EOR_Done (Implicit fall-through)
- write('σ' ,1 ,EOR)
- halt
- </RT·code>
+ <p>Now consider starting with the exact same right to left string as the early translators did, but instead of making an exception for numbers, we literally reverse the entire string:</p>
- <p>This is an assembly-level code description of an instruction sequence. To get it into final form, the labels that appear as instruction arguments would be replaced with their addresses. Symbolic labels would not appear on the tape. As an alternative to using absolute branches, relative branches could be used. Performance will be greatly enhanced for a sequencer that performs relative branching if it contains an adder circuit; otherwise, the addition of offsets would be another TTCA program invocation.</p>
+ <blockquote>
+ Writing from right to left the sequence [7, 8, 9] without notation is 789.
+ </blockquote>
- <!--------------------------------------------------------------------------------->
- <RT·chapter>The software layer</RT·chapter>
+ <p>Now both the scan order of the sequence and the digits are identical. This maintains the property designed into the original Indian Numbers. Note, that 789 here is still thirteen away from one thousand.</p>
- <h2>Symbol in mathematics</h2>
+ <p>To make the structure uniform, Western writers must either start writing right to left like the Arabs do, or reverse the order of digits in numbers. Computer architects implemented this latter option; for example, Intel processors utilize it.</p>
- <p>
- A <RT·term>symbol</RT·term> is a distinct mathematical object capable of being instantiated. Within a given context, any instance of a specific symbol evaluates as equal to any other instance of that identical symbol, and evaluates as not equal to any instance of a different symbol.
- </p>
+ <p>In summary, this section defines two concepts: scanning-order when reading, and the digit-order of numbers. Changing the scanning order without changing the digit order effectively causes the number to be read in reverse.</p>
- <p>
- Put more formally, given a set of instantiable objects and a collection of instances made from them, for these objects to be symbols, two conditions must be met. First, it must be possible to define an instance comparison operation, denoted <RT·math>=</RT·math>, that acts as an equivalence relation to partition the collection into discrete equivalence classes. There must be a one to one correspondence between the resulting equivalence classes and the original instantiable objects from which the member instances were derived.
- </p>
+ <p>All conventional computers use low-to-high address scanning order for writing and reading text. Human operators universally find that low-to-high address scanning order for text feels natural. Relative to the low-to-high address scanning order, Big Endian computers follow the current Western ideal that the large digit comes first. In contrast, Little Endian computers are more faithful to the design of Hindu-Arabic numbers, storing numbers consistently with the scanning order matching the order of the digit weights.</p>
- <p>
- It follows from this definition that the distinct equivalence classes can be used as a proxy for the instantiable objects themselves. That is, a person can name either the instantiable object or the equivalence class, and then through this correspondence, find the other.
- </p>
+ <p>Because a page of text is two-dimensional, there are additional writing direction conventions. However, computer memory is one-dimensional, so only the two discussed here are relevant to this architectural discussion. Programmers tasked with writing display drivers encounter the others.</p>
- <h2>Symbol in computing</h2>
+ <h2>Binary, Octal, Hexadecimal, BCD</h2>
- <p>
- In the context of real machines, the symbol itself can be defined as a factory that produces symbol instances. A new symbol instance of the given symbol is then made, say, by calling the factory's <RT·code>make</RT·code> function. All of the symbol instances made by the factory constitute the members of the corresponding equivalence class.
- </p>
+ <p>A person interprets a sequence of <RT·math>n</RT·math> bits as a number by using this function:</p>
- <p>
- A <RT·term>symbol instance</RT·term> newly minted by the factory is said to <RT·neologism>come direct from the factory</RT·neologism>. A symbol instance direct from the factory is also called an <RT·neologism>original</RT·neologism>.
- </p>
+ <div style="margin-left: 2em;">
+ <RT·math>\sum_{i=0}^n x_i \cdot two^i</RT·math>
+ </div>
- <h3>Required properties of symbol factories</h3>
+ <p>As with sequences of decimal digits, a person drops the sequence notation to yield strings of bits. Similar to decimal digit strings, there are two options for writing the string: most-significant-digit-first, or least-significant-digit-first.</p>
- <p>
- Any two symbol instances returned directly from two distinct factories will always evaluate to <RT·code>False</RT·code> during an equality comparison. In other words, two distinct originals will always be not equal.
- </p>
+ <p>Although both strings of binary digits and strings of decimal digits grow in length logarithmically when counting, binary strings grow more than twice as fast. The expansion to a length two string occurs immediately at the count of two. Length expands to three at a count of four, and then to a string of length four at a count of eight. While incrementing to eight, the binary digit string has already expanded to length four, yet for the exact same count, a decimal digit string remains one digit long.</p>
- <p>
- Given an original, all copies <RT·neologism>stemming from</RT·neologism> it will be equal to each other and to the original. By <RT·neologism>stemming from</RT·neologism>, this definition includes all direct copies and copies of copies.
- </p>
+ <p>Writing such long strings is inefficient, so programmers group bits. When bits are grouped in threes, the base is eight instead of two. This is called octal notation, using one of the symbols 0, 1, 2, 3, 4, 5, 6, or 7 for the octal digit values.</p>
- <p>
- Given any two originals, say <var>A</var> and <var>B</var>, it is established that <var>A</var> is not equal to <var>B</var>, as discussed above. Note also that <var>A</var> is not equal to any copy stemming from <var>B</var>, and <var>B</var> is not equal to any copy stemming from <var>A</var>.
- </p>
+ <table>
+ <thead>
+ <tr>
+ <th>octal</th>
+ <th>binary</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr><td>0</td><td>000</td></tr>
+ <tr><td>1</td><td>001</td></tr>
+ <tr><td>2</td><td>010</td></tr>
+ <tr><td>3</td><td>011</td></tr>
+ <tr><td>4</td><td>100</td></tr>
+ <tr><td>5</td><td>101</td></tr>
+ <tr><td>6</td><td>110</td></tr>
+ <tr><td>7</td><td>111</td></tr>
+ </tbody>
+ </table>
- <p>
- Though symbol instances are integer like in that copy and equality comparison operations can be used with them, symbol instances are disallowed from being used with other integer operators. Symbols cannot be compared for greater than or less than; they cannot be incremented, added, nor subtracted, etc.
- </p>
+ <p>In the 1960s, computer panels featured switches and lights organized in threes, and coding forms were filled out in octal. Today, documents showing bit strings universally use groups of fours. Such a digit has 16 values, utilizing 0 through 9, and continuing with the letters a, b, c, d, e, and f. This is known as the hexadecimal system, often shortened to hex.</p>
- <h3>Instance implementation</h3>
+ <table>
+ <thead>
+ <tr>
+ <th>hex</th>
+ <th>binary</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr><td>0</td><td>0000</td></tr>
+ <tr><td>1</td><td>0001</td></tr>
+ <tr><td>2</td><td>0010</td></tr>
+ <tr><td>3</td><td>0011</td></tr>
+ <tr><td>4</td><td>0100</td></tr>
+ <tr><td>5</td><td>0101</td></tr>
+ <tr><td>6</td><td>0110</td></tr>
+ <tr><td>7</td><td>0111</td></tr>
+ <tr><td>8</td><td>1000</td></tr>
+ <tr><td>9</td><td>1001</td></tr>
+ <tr><td>a</td><td>1010</td></tr>
+ <tr><td>b</td><td>1011</td></tr>
+ <tr><td>c</td><td>1100</td></tr>
+ <tr><td>d</td><td>1101</td></tr>
+ <tr><td>e</td><td>1110</td></tr>
+ <tr><td>f</td><td>1111</td></tr>
+ </tbody>
+ </table>
- <p>
- Within a process, a reference to the factory can be used as a symbol instance, which will cause the factory to become trivial. Making a new instance will merely require copying the factory reference, and there will be nothing in memory that the base factory reference is pointing to.
- </p>
+ <p>The table of hexadecimal digits is twice as long as the table of octal digits because each added bit doubles the table size.</p>
- <p>
- In general, memory addresses are built in symbol instances, hence within the context of a single process run, a program can make use of these symbols. However, this diminishes the size of the address space and leaves the memory at those addresses unused. A common hedge is then to use references into a dictionary, where the data looked up in the dictionary is the <RT·term>name</RT·term> of the symbol.
- </p>
+ <p>Another common grouping is the <RT·term>byte</RT·term>. This name is word play on the term bit. Today a byte is universally a group of eight bits; however, historical computers used other lengths, ranging from six to twelve bits. Vestiges of that past remain. K&R’s "C Programming Language" leaves the length of a byte unspecified. The designers of UTF-8 sought clarity regarding groups of eight, naming such a group an octet rather than a byte.</p>
- <p>
- Such symbol names are non structural strings, so they do not need to follow the rules of symbols. For example, a program written where references to strings were used as symbol instances, could give multiple, or all, strings the same name, and the program would function. Conventionally, the names are made to be distinct so as to avoid confusion. The hazard here is that a programmer will then conflate the string name with the symbol instance, and perform symbol operations with it.
- </p>
+ <p>When a group of eight bits, i.e., an octet, functions as a digit of a number, the arithmetic operates in base 256. Note that octet and octal are distinct concepts. An octet is a group of 8 bits, whereas octal is a number system based on digits that have values running from 0 to 7.</p>
- <p>
- An alternative implementation is to have the factory return an integer value. Each factory has a base integer that is distinct from that of other factories. Calling <RT·code>make</RT·code> then returns the base integer.
- </p>
+ <p>Groups of bits become larger. The organization of early RISC microprocessors specified that memory was always moved in groups of 32 bits, called words. Today, address variables are generally 64-bit words, while integer variables are either 32- or 64-bit words. Groups of bits found on internal buses scale larger yet.</p>
- <p>
- As another alternative, each factory can be given a base string, and then <RT·code>make</RT·code> returns a copy of the base string. Here we refer literally to the string as the symbol instance. There is no separate name, and the string data, not the reference to the string, becomes the symbol instance. This is however merely an architectural constraint, under the hood an implementation could use string references as long as it always appears to the programmer that the string value is being used.
- </p>
+ <p>Bits are also grouped to construct numbers with bases that are not powers of two. In the BCD code, bits are grouped in fours to form decimal digits.</p>
- <p>
- At the time of this writing many machines use 64 bit words. This is equivalent to 8 ASCII characters, while the average size of an identifier is about 5 characters. Hence the approach of using a string as a symbol might not be as inefficient as it seems to be at first. Using strings has advantages. Symbol instances can carry semantic clues for the programmer. There is no hazard of conflating the string instance with the name, as they are the same. Also, a string instance will have integrity across contexts, such as between invocations or when passed between processes (note the section below on crossing context boundaries). A drawback is in cases there is no language support, the strings are typically ad hoc so the guarantee of distinctness becomes merely a contract with the programmer.
- </p>
+ <table>
+ <thead>
+ <tr>
+ <th>BCD</th>
+ <th>binary</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr><td>0</td><td>0000</td></tr>
+ <tr><td>1</td><td>0001</td></tr>
+ <tr><td>2</td><td>0010</td></tr>
+ <tr><td>3</td><td>0011</td></tr>
+ <tr><td>4</td><td>0100</td></tr>
+ <tr><td>5</td><td>0101</td></tr>
+ <tr><td>6</td><td>0110</td></tr>
+ <tr><td>7</td><td>0111</td></tr>
+ <tr><td>8</td><td>1000</td></tr>
+ <tr><td>9</td><td>1001</td></tr>
+ </tbody>
+ </table>
- <h3>Distinctness across contexts</h3>
+ <p>In BCD, some possible bit value combinations are unused. This is an unavoidable consequence of emulating a number base in binary switch logic when that base is not a power of 2.</p>
- <p>
- If a symbol persists across contexts (such as across scopes or processes), it must remain distinct from all other symbols in its new context.
- </p>
+ <p>Octal and hexadecimal operate primarily as writing aids. Internally, the computer computes in binary, i.e., base 2. In contrast, BCD computation relies on the structural encoding boundaries. When two BCD digits are summed, the carry occurs when the sum of two digits reaches ten or more, which does not fall on a power of two boundary.</p>
- <p>
- One way to meet this requirement is to find a scope encompassing both contexts and to place the symbol factory there. Another solution is to give each context a distinct root symbol and to use an array of symbols in place of the imported symbol. Yet another approach is to associate an imported symbol with a new symbol in the given new context using a correspondence map.
- </p>
+ <p>Some processors lack native BCD computation, providing BCD to binary conversion operations instead. Consequently, numbers that appear as BCD in memory convert to binary before computation. Other computers, most notably many IBM machines, implement true BCD architectures featuring instructions for directly operating on BCD encoded numbers.</p>
- <p>
- When utilizing memory addresses as symbols in a virtual memory environment, the convention is to disallow addresses in one process from being used in another. If that isolation is insufficient, it is often adequate to use indexes instead of addresses, taking the address to the base of the data structure. Though the absolute address of the data structure might differ across contexts, the relative offset remains constant. Another approach is to reserve memory address blocks and to guarantee imported pages have the exact same addresses as before, though they might be imported sequentially to reuse the memory block. In architecture, this is generally known as the <RT·term>pointer swizzling problem</RT·term>.
- </p>
+ <p>Although BCD encoding is less efficient, BCD encoded binary place values numbers still grow in length logarithmically while counting. Because numbers are represented in base ten, no number conversion is necessary when printing, though today this performance cost is insignificant. More importantly, BCD numbers divide by ten without generating infinite fractions.</p>
- <h2>Symbol copy consequentiality</h2>
+ <h2>Allocation</h2>
- <p>
- Copying symbol instances is an integral part of the operation of the Turing machine. Typically the programmed control steps are used as a cost metric for running a program. Such steps integrate operations of copying symbols to and from the tape. Thus from a pure step count perspective the cost of the symbol copy is inconsequential, not due to any behavior of a symbol copy, but rather due to the symbol copy being subsumed within the definition of the step.
- </p>
+ <p>In most text documents, a number is written down once and remains unchanged. In contrast, a computing program frequently returns to the exact memory location and changes its value. This is equivalent to erasing an old value on a paper document and writing a new value in the same space.</p>
- <p>
- However, this is feels unsatisfactory. Here we are tying to build a bridge from computation theory to architecture, and thus we want a step to be a constant time occurrence at least in the average, or at least approximately. To model the symbol copy time, a symbol could be copied a tranche of bits per step, thus giving the copy a step count. Then, if symbols were to grow in tranche count as a program ran longer, then symbol copy of ever longer symbols would be computationally consequential.
- </p>
+ <p>Consider recording a count in real time, where the system must routinely erase the current count value and write a new one. The process begins at zero. When an event occurs, the count increments to 1. The sequence remains a single digit. The count grows to two digits in length upon reaching ten. The count grows by another digit in length upon reaching a hundred, and again at a thousand. A digit sequence representing the count grows in length against the log of the count value. The log function grows without bound, but it does so slowly, allowing relatively short digit sequences to represent exceedingly large numbers. This structural observation is central to understanding contemporary computer architecture.</p>
- <p>
- Fortunately, the Turing Machine is defined with a finite alphabet and a couple of additional symbols. As these are part of the machine definition, and thus do not change at run time, there is no growth in size, and the time to copy them is inconsequential.
- </p>
+ <p>If a paper document only provides space for a single digit count, an error occurs when the count grows to ten, escalating when it reaches a thousand. If a programmer anticipates counts in the thousands, he allocates space for four digits in advance. Such an allocation minimizes wasted space because the digit sequences for massive numbers are not significantly longer than those for small numbers.</p>
- <p>
- On a real machine, the factory would be used to make the data alphabet and a couple of additional symbols. As these are set up before the machine runs, and thus before we count steps, these actions are computationally inconsequential.
- </p>
+ <p>For any allocated boundary, there remains the statistical possibility that a computed number will exceed the allocation, resulting in an overflow error. To establish rigorous allocations, a programmer must evaluate how much numbers grow via applied operations, and how many of those operations the system executes. This latter constraint ties directly to the projected lifespan of the task.</p>
- <h2>Alphabet</h2>
+ <p>Universally, a program cannot analyze another arbitrary program and determine how many steps it will take before stopping, or if it will ever stop. Even in specific bounded cases, the analysis is highly complex, and the programmer frequently skips it unless the code drives a life-critical application. Because data lengths expand during runtime, users operate under the threat of allocation overflow. If the allocation overflows, there is a high probability the program will stop and emit an error, or it will continue executing while outputting garbage results. This failure mode does not surprise contemporary users. The real-time computer user experiences catastrophic failure, and the error emerges during accident analysis.</p>
- <p>
- An alphabet is another name for 'a set of symbols'. Because it is simply a set, an infinite number of distinct alphabets can exist, and a set of symbols could be finite or infinite.</p>
+ <p>Conventional system memory is an array of allocation units called bytes. The indexes into this array are system memory addresses. Serving as the minimum addressable allocation unit defines the term byte. The number of bits in a byte is a parameter of the computer architecture, not the compiler.</p>
- <p>For a Turing machine there are a fixed number of alphabets that are all finite. The set of data symbols, control symbols, and controller state name symbols, are all finite and set in advance.
- </p>
+ <p>It follows that any larger allocation consists of multiple bytes. For a given allocation unit, the smallest address among the contained bytes establishes the address of the allocation unit. Each allocation unit is bounded by two parameters: its address, and its extent.</p>
- <h2>Symbol implementation examples</h2>
+ <p>Setting aside the scanning order variable, a number logically begins with its least-significant digit. This is justified because an analyst must read a number starting with its least-significant digit to derive the weights of the subsequent digits. The allocation scheme where the address of the allocation maps to the address of the least-significant digit is called <RT·term>little-endian</RT·term>. The definition of <RT·term>big-endian</RT·term> involves zero-padding to fulfill the allocation width. Relative to starting at the least-significant digit, the big-endian allocation address resolves to either the most-significant digit or the final zero in the padding.</p>
- <p>
- The <RT·code>enum</RT·code> of C is used to make alphabets of named symbols. Each entry in the enum is a static symbol factory, and instances are distinct integers.
- </p>
+ <p>In the architectural terminology utilized here, viewing memory as a horizontal tape with bytes in the cells and addresses increasing while moving to the right, little-endian numbers have the least-significant digit on the left, and big-endian numbers have the least significant digit on the right of an allocation. Stated structurally, little-endian numbers are zero padded on the right, and big-endian numbers are zero padded on the left.</p>
- <RT·code>
- /* The enum definition acts as the factory. */
- typedef enum {
- SYMBOL_EMPTY = 0,
- SYMBOL_ZERO = 1,
- SYMBOL_ONE = 2,
- SYMBOL_A = 3,
- SYMBOL_B = 4
- } TapeAlphabet;
+ <p>Figure 10 depicts a word featuring byte addresses represented in hexadecimal, running from c0 to c3. (In decimal these addresses represent 192, 193, 194, 195). The address of the byte before c0 is bf. The address after c3 is c4. The address for the word itself evaluates to c0, as it is the minimum byte address. This word holds a little-endian number. Treating a byte as an octet digit, the binary encoding for the least-significant digit of this number is 0001 1000. The most-significant digit is 1010 1110.</p>
- /* Instantiating copies of the symbols: */
- TapeAlphabet cell_1 = SYMBOL_A;
- TapeAlphabet cell_2 = SYMBOL_A;
+ <figure>
+ <img src="Hindu-Arabic number fig 1.png" class="rt-diagram" alt="Figure 10: Left justified, least-significant-digit-first">
+ <figcaption>Figure 10: Left justified, least-significant-digit-first</figcaption>
+ </figure>
- /* Equality comparison over instances */
- if(cell_1 == cell_2){
- /* Evaluates to True */
- }
- </RT·code>
+ <p>In Figure 11, the same number populates the word using big-endian architecture. For all but very large numbers, the digit pointed at by the allocation pointer evaluates to zero. A system continues to scan zeros until reaching either the end of the allocation or the most-significant digit. If it reaches the end of the allocation, the contained number evaluates to zero. Because this is the exact same number shown in the prior figure, it retains the identical least-significant digit and most-significant digit.</p>
- <p>
- The <RT·code>enum</RT·code> is a static alphabet made by the compiler, where symbol instances are integers. In the following example, the alphabet is made dynamically, where each symbol instance is a string pointer.
- </p>
+ <figure>
+ <img src="Hindu-Arabic number fig 2.png" class="rt-diagram" alt="Figure 11: Same number using big-endian digit order">
+ <figcaption>Figure 11: Same number using big-endian digit order</figcaption>
+ </figure>
- <RT·code>
- #include <string.h>
- #include <stdlib.h>
- #include <stdio.h>
+ <p>If the specified word holds a count, the counting mechanics differ. When counting with the little-endian convention, a number grows into larger addresses as the count carries into new digits. In contrast, with big-endian architecture, counting carries into strictly smaller memory addresses.</p>
- // maximum legal index into the symbol list
- #define LIST_EXTENT 3
+ <p>Typically, processor registers and buses are one word in length, and a processor loads the entire contents of a memory word into a register as a single atomic operation. Contemporary processors are designed effectively for either little-endian or big-endian number representation; however, once designed for little-endian, the processor is fundamentally incompatible with big-endian, and vice-versa.</p>
- typedef const char *Instance;
- typedef struct{
- Instance *head;
- Instance *tail;
- Instance *extent;
- } List;
+ <p>If an unconventional processor loads numbers sequentially as digit streams, the load instruction must possess a mechanism to detect the end of the loaded number, or it must receive the exact length prior to execution. This structural boundary is identical to the existing mechanism for loading character strings, where systems utilize length counts or explicit end terminators.</p>
- static List SYM_LIST = {NULL ,NULL ,NULL};
+ <p>For serial computation utilizing little-endian notation, the processor produces the first digit of the sum immediately after receiving the first digit of the operand. If big-endian is used, the least significant digit arrives last, forcing the system to buffer the entire number before computing the first digit of the sum. Attempting to load from the far ends of the operands fails because the physical location of the sum's far end remains undefined until the carry propagates. Resolving big-endian serial addition requires implementing signed digit arithmetic.</p>
- Instance make_list(const char *name){
- size_t size = LIST_EXTENT + 1;
- SYM_LIST.head = (Instance *)malloc( size * sizeof(Instance) );
- SYM_LIST.tail = SYM_LIST.head;
- SYM_LIST.extent = SYM_LIST.head + LIST_EXTENT;
- *SYM_LIST.head = strdup(name);
- return *SYM_LIST.head;
- }
+ <h2>Bit Order Within Bytes</h2>
- Instance make_symbol(const char *name){
- if(!SYM_LIST.head) return make_list(name);
+ <p>Data transports between hardware nodes over bundles of wires called buses. Bus specifications explicitly define the order of bits in bytes, and for contemporary machines, bytes are strictly octets. All compute processors, channel processors, and attached devices must conform to the bus’s specifications.</p>
- Instance *pt = SYM_LIST.head;
- while(1){
- if( strcmp(*pt ,name) == 0 ) return *pt;
+ <p>Channel processors bridge the gap between a computer's system bus and a storage bus connected to storage devices. The storage device designers adhere strictly to the storage bus specification without evaluating the host computer architecture. The compatibility burden relies entirely on selecting a channel processor that respects the established standard. Consequently, unless a programmer explicitly designs bus standards or hardware interfaces, the physical bit order within bytes remains invisible.</p>
- if(pt == SYM_LIST.extent){
- fprintf(stderr ,"symbol list overflow for %s\n" ,name);
- return NULL;
- }
+ <p>Some processors physically store bits into memory bytes in different orders. However, the data read and written into the respective memories routes through a bus, and at that boundary, the bit order aligns with the applicable bus specification.</p>
- if(pt == SYM_LIST.tail){
- *++SYM_LIST.tail = strdup(name);
- return *SYM_LIST.tail;
- }
+ <p>On all contemporary computers, ALU hardware performs arithmetic on bytes. Carries propagate strictly from lower significant bits to more significant bits; therefore, the ALU imposes a logical bit order within bytes. That logical order conforms directly to the documentation for the processor’s internal bus, where bit 0 serves universally as the least significant bit.</p>
- pt++;
- }
- }
+ <p>Because doing so is impossible in all but special cases, an address points at the entire byte, rather than pointing at the most significant bit or the least significant bit.</p>
- int main(){
- Instance a = make_symbol("a");
- Instance b = make_symbol("b");
- Instance c = make_symbol("c");
- Instance d = make_symbol("d");
- Instance e = make_symbol("e"); // overflows table
+ <h2>Byte Order Within Words</h2>
- Instance *pt = SYM_LIST.head;
- Instance *pt_tail = SYM_LIST.tail;
- while(1){
- puts(*pt);
- if(pt == pt_tail) break;
- pt++;
- }
+ <p>A specific processor architecture features native support for byte data alongside varied word lengths, most commonly 16, 32, and 64 bits (or 4, 8, and 16 bytes).</p>
- if(e == NULL) printf("e is NULL\n");
- }
- </RT·code>
+ <p>Communication channels and storage devices organize payloads strictly as octets, possessing no native structural support for words. Therefore, storing or transmitting a word requires serializing the word into a sequence of bytes, transferring the data, and subsequently reading the data while deserializing the byte stream back into the original word configuration.</p>
- <h2>Property</h2>
+ <p>Our Indian Number derived representations consist of sequences of digits. Words of allocation consist of consecutively addressed bytes. Hardware manages bytes atomically. Any bit encoding for the digits of a number must pack cleanly into bytes, otherwise the digits fracture. Achieving a clean packing requires padding the data with zeros to force alignment to an 8-bit boundary. When a system meets this criterion, treating a number as a sequence of bytes acting as digits maintains structural consistency. Due to this constraint, little-endian and big-endian are routinely classified as byte orders.</p>
- <p>
- A <RT·term>property</RT·term> is a pair, where the components are called the <RT·term-em>name</RT·term-em> and the <RT·term-em>value</RT·term-em>. A <RT·term>name</RT·term> is an instance of a symbol and it must uniquely identify the property within its context. The value is a variable that can be written then read back.
- </p>
+ <p>The following figure displays a stream of bytes arriving as data and being copied into a word. The digits of the word (the bytes) arrive in little-endian order and target a little-endian machine, so they are written in the exact order they are scanned off the channel.</p>
- <h2>Dictionary and reference</h2>
+ <figure>
+ <img src="Hindu-Arabic number fig 3.png" class="rt-diagram" alt="Figure 12: In-address-order byte by byte copy">
+ <figcaption>Figure 12: In-address-order byte by byte copy</figcaption>
+ </figure>
- <p>A <RT·term>dictionary</RT·term> is a property context object. It is a set of properties, where a property may be selected from the set given its name, which is a symbol instance. The corresponding property value is then the <RT·term>referenced object<RT·term>.</p>
+ <p>In the second case, the identical data stream arrives with words serialized as bytes in little-endian order, but the receiving machine is big-endian. The system must reverse the bytes strictly on a word-by-word basis.</p>
- <p>A <RT·term>reference</RT·term> is a dictionary key which uniquely identifies a property in the dictionary. A dictionary is also known as a symbol table. In hardware design, the symbols are often unsigned integers, AKA Natural Numbers, and a symbol table is implemented using an array that is indexed into using the key.</p>
+ <figure>
+ <img src="Hindu-Arabic number fig 4.png" class="rt-diagram" alt="Figure 13: Reverse order byte copy for words">
+ <figcaption>Figure 13: Reverse order byte copy for words</figcaption>
+ </figure>
- <p>Any programming object that holds other objects is a container, whereas a dictionary is a specific type of key access container.</p>
+ <p>When the data arrives, there is no way to know where the word boundaries are. Serialization destroys that structural boundary information. Without knowing where the words are, a system cannot determine when to perform the byte order reversal. Therefore, resolving this requires a different approach than the one used for bit order in bytes; the problem transfers into the software layer.</p>
- <h2>A Turing Machine tape cell</h2>
+ <p>Byte order within words becomes an application level design consideration, even though it possesses no structural relevance to applications.</p>
- <p>
- A <RT·term>cell</RT·term> is the <RT·term>square</RT·term> from Alan Turing's 1936 paper <RT·endnote>Alan M. Turing, "On Computable Numbers, with an Application to the Entscheidungsproblem," <em>Proceedings of the London Mathematical Society</em> s2 42, no. 1 (1936): 230 265.</RT·endnote>. Mathematically, a cell is a referencable distinct identifiable set, with one to three property members depending on the type of cell.
- </p>
+ <p>In protocols like the Internet Protocol (IP), the specification explicitly defines the data offset for the packet header. Code executes stably and efficiently. However, IP lacks context regarding the word locations in the data payload it carries, so IP routes the payload up the abstraction stack, preserving all byte order conflicts.</p>
- <p>
- A <RT·term>leftmost cell</RT·term> is a set of two properties named 'right neighbor' and 'data'. A <RT·term>rightmost cell</RT·term> is a set of two properties named 'left neighbor' and 'data'. An <RT·term>medial cell</RT·term> is a set holding three properties: a 'left neighbor', a 'right neighbor', and 'data'. While an <RT·term>island cell</RT·term> owns a single compulsory property, that of 'data'.
- </p>
+ <p>JSON provides a standard for expressing tagged structured data built from primitive types while using character-only data. The specification dictates how numeric character strings evaluate, allowing both little-endian and big-endian machines to safely exchange JSON character-encoded numeric data. However, converting all numbers to strings and back wastes computational overhead, and transmitting them over a channel consumes excess bandwidth. Both processes expend unnecessary energy.</p>
- <p>
- The value of a neighbor property is a non-null single reference to a cell. The value of a data property is strictly an instance of an alphabet symbol, or alternatively, an instance of the empty symbol.
- </p>
+ <p>Various file formats and data communications standards serve specific classes of applications by explicitly defining where words that require reversal are located during machine-to-machine data transfers.</p>
- <h2>Tape</h2>
+ <h2>TTCA</h2>
- <p>
- A tape is a set containing exactly one leftmost cell and an infinite number of medial cells. For each cell in the set, called cell A, if cell A has a right neighbor that is cell B, then cell B's left neighbor is cell A. Similarly, if cell A has a left neighbor of cell B, then cell B's right neighbor is cell A. Furthermore, any cell in the set must be reachable by traversing right neighbors starting from the leftmost cell, in a finite number of steps.
- </p>
+ <p>The native integer data type for TTCA utilizes a high radix online number system. This number system functions as an extension of online arithmetic. Similar to online arithmetic, it employs serial most-significant-digit-first signed digit arithmetic. In contrast to standard signed arithmetic, the radix scales significantly higher, causing a digit to span at least a byte in length. The numbers are highly scalable, and the architecture mandates an analysis step at compile time to establish rigorous precision requirements and exact range bounds. This process is detailed in the next chapter. The architecture provides explicit conversion instructions to generate alternate number formats.</p>
- <p>
- To say that a tape is infinite, and to simultaneously require that any cell can be reached in a finite number of steps, means that after reaching said cell, there will always be further cells to the right. Consequently, though <em>any</em> cell can be reached in finite hops starting at the leftmost cell, a finite traversal of the tape can not visit <em>all</em> of the cells. This seeming contradiction is at the very heart of the definition of the countable infinity in mathematics.
- </p>
+ <!--------------------------------------------------------------------------------->
+ <RT·chapter>Address</RT·chapter>
- <p>
- In conventional computation theory, once a tape is defined, the cell neighbor properties values are fixed. Cells don't move, new cells cannot be added, and cells existing on the tape cannot be removed. This fits the definition of a <RT·term>space</RT·term>, so we can say that a Turing Machine tape has a constant, fixed, linear <RT·term>topology</RT·term>. This permanence of cells matches the reality of hardware memories. On the other hand, it does not track well with general memory containers such as linked lists where <RT·term>destructive</RT·term> operations are often permitted.
- </p>
- <p>
- Nor can a cell data property be removed; however, unlike for cell neighbor property values, the cell data property value can be changed while the Turing Machine is running. In fact some people would say this is the whole point of running a Turing Machine.
- </p>
+ <h2>Unary Representation address</h2>
<p>
- An empty tape is filled with empty symbols. However, as we noted above, a Turing Machine cannot visit all the cells on a tape, so a Turing Machine cannot erase a tape in advance for another Turing Machine to use. Say a Turing Machine tried to erase a tape by writing the empty symbol, then stepping right, then repeating. At some point it must halt. When it halts some number of cells will have been written with the empty symbols, but there will be further cells to the right of the cell the machine halted at, which have not yet been erased. So for example, if a machine wrote 10 cells with the empty symbol, then it might be the case that the prior user of the tape had written data to 11 of the cells. Without historical knowledge the eraser machine can not know where to stop. So here we see another meaning of infinity, it speaks to the situation of not having historical knowledge. And thus, we are left to decree into existence an empty tape, or as the mathematicians say, empty tapes are available <em>by definition</em>.
+ A person can define a Turing Machine that is identical to the recursive definition of Natural Numbers as given by Peano. <RT·endnote>Giuseppe Peano, <em>Arithmetices principia, nova methodo exposita</em> (Turin: Fratres Bocca, 1889).</RT·endnote> If a person were to run the Natural Number Machine and observe as it writes to the tape, that person would watch as the Natural Numbers are printed one after another: '·s·ss·sss·ssss· ...'. Here '·' represents zero, and 's·' represents one, etc. Because the Natural Number Machine never halts, the machine cannot be used to initialize a tape, but an analyzer can evaluate the machine to make conclusions about what it would write if it were run.
</p>
<p>
- Mathematically, a Turing Machine tape is a specialized path graph. The neighbor properties are the edges. In this special form, properties are attached to the edges. A Turing Machine has a clock based state controller. Taking a step is an atomic operation. The machine is either in state <RT·math>q_i</RT·math> or in state <RT·math>q_{i+1}</RT·math>, there is no mathematical meaning given to the concept of <em>during a step</em>, which is why no properties are added to the edges of the tape path graph. This is not to say that some analysis of a Turing Machine program won't take pairs of nodes from the path graph and give them meaning, thus effectively giving properties to the arcs. However, this would not be part of the machine definition, such a program must go through the additional effort of making node pairs, because the machine itself does not provide the program with a feature for attaching properties directly to the neighbor property itself.</p>
-
- <p>An example of a non-Turing tape like model is the Emacs vertical line cursor model, where a cursor is said to be between characters. An ASCII file offers no such feature as 'in between' characters. Like a Turing Machine tape, a medial character in an ASCII file has a left neighbor and a right neighbor character. Any attempt to represent an in between cursor within the file itself would require inserting more characters into the file under the same model of every medial character having a left and a right character. Consequently, though emacs presents a model where cursor is located in between two characters, this model is only due to the interpretation of the functions' actual effects presented to users in the documentation. For example, instead of saying a cursor is located upon a character, and that inserting a character inserts the character to the right of the given character, the manual gives the description that the new character is inserted at the cursor location, where said cursor is in between the given character and its right neighbor. Thus the documentation presents the user with one model, which by necessity of using standard library calls to work with files, is built upon another model.</p>
-
-
-
- <!--------------------------------------------------------------------------------->
- <RT·chapter>Computational Analysis</RT·chapter>
-
- <h2>Definition</h2>
-
- <p>
- In mathematics, <RT·term>analysis</RT·term> is the rigorous study of limits, continuity, rates of change, and bounds. It encompasses several specialized branches. Real analysis studies the behavior of real numbers, sequences, and continuous functions. Complex analysis extends these principles to functions of complex variables. Functional analysis examines vector spaces where the elements themselves are functions. Numerical analysis focuses on the design of algorithms to yield approximate solutions for continuous mathematical problems. Harmonic analysis studies the representation of functions or signals as the superposition of basic waves, such as Fourier series. Across all these branches, analysis provides a formal framework for evaluating mathematical objects.
+ In contrast, a function extension version of the Natural Number sequence generator can be run. The function extension machine would be given as input a prior function extension result, or an initial empty tape. It would then modify the tape to contain the next Natural Number, as per the sequence that the Natural Number machine would write, if it could be run. This is accomplished through two subroutine calls: <RT·code>find_empty</RT·code> and <RT·code>increment</RT·code>. The <RT·code>find_empty</RT·code> machine checks the symbol under the head. Upon finding it is not the empty symbol, the machine steps right and checks the next cell, repeating until it finds the empty symbol, whereupon it halts. The <RT·code>increment</RT·code> machine then writes an 's' onto the tape and halts. Recurrent calls to the Natural Number extender then produce a sequence of result tapes: <RT·code>[], [s], [ss], …</RT·code>. Similarly, a programmer can write a machine called <RT·code>decrement</RT·code>, though the programmer must note that decrementing can return the <RT·code>left-of-leftmost</RT·code> symbol.
</p>
<p>
- In computation theory, <RT·term>computational analysis</RT·term> is defined as the static evaluation of a formal system or machine definition to deduce its absolute boundaries and properties. This process remains entirely distinct from dynamically executing the machine to yield a computed result.
+ To say that Natural Number A is smaller than Natural Number B is to say that A would occur on the Natural Number Machine tape to the left of B, if the machine were run. Conversely, if B were said to be greater than A, that would mean B occurs further to the right. It is a simple matter for a programmer to write a decider machine for this. The decider is given two input tapes for the two numbers to compare, and it keeps a head on each. It then steps forward until neither head has an 's' under it. If empty symbols are found simultaneously on both tapes, then the two numbers are equal; otherwise, the number with an empty symbol under the head is the lesser number.
</p>
<p>
- This analytical perspective has been present from the very beginning of the field. Alan Turing's formulation of the halting problem relies explicitly on one machine examining another. To properly evaluate the limits of this analytical capacity, the examining machine must necessarily be given definitions of programs that loop infinitely. Consequently, the foundational proofs of computer science formally establish non computational programs as legitimate objects of analytical study.
+ As such, a person can assign a Natural Number to each cell of a given tape by using a mechanical procedure. Given a machine, say <RT·code>P</RT·code>, and an address machine, say <RT·code>A_P</RT·code>, each time <RT·code>P</RT·code> is stepped right, a call is made to run <RT·code>increment</RT·code> on <RT·code>A_P</RT·code>. Similarly, each time <RT·code>P</RT·code> is stepped left, a call is made to run <RT·code>decrement</RT·code> on <RT·code>A_P</RT·code>. In this manner the tape on <RT·code>A_P</RT·code> will always hold the address that machine <RT·code>P</RT·code>'s head is on.
</p>
<p>
- We call a program that examines another program so as to deduce properties of its results an <RT·term>analyzer</RT·term>. The program or machine definition being subjected to this evaluation is called the <RT·term>studied program</RT·term> or <RT·term>studied machine</RT·term>.
- </p>
-
- <p>
- Turing Machines that halt in a finite number of steps for any finite input within a stipulated domain are said to be <RT·term>computational</RT·term> over that domain.
+ The Natural Number found on <RT·code>A_P</RT·code> is then called the address for the cell that machine <RT·code>P</RT·code>'s head is on. As each increment and decrement of the address is a constant time operation, keeping the address of the cell the head is on is computationally inconsequential.
</p>
<p>
- By definition, first order analysis is the running of programs, and it has its place.
- Generally, when we want to know what output a computational machine will produce, the fastest route to this knowledge is to run the program. Most programs written to solve problems are most effectively run to solve those problems rather than analyzed to deduce what they will produce.
+ An <RT·term>address space</RT·term> is a set of addresses for contiguous cells. The tape's address space is the set of addresses for all the cells on the tape. Typically the address of 0 is given to the leftmost among the contiguous cells.
</p>
- <p>
- Now suppose we quantify the inputs to a computational machine over a domain and want to know a property of the machine. Perhaps, say, that it always produces an even number. If the quantification is over a large set, then it might be faster to study the machine than it would be to run the machine on every input in the domain while checking its output.
- </p>
- <p>
- Further suppose that the quantification is over an infinite input space. Then the only option for answering a question about the properties of results from such a machine is to analyze it. This is second order analysis, also called simply <RT·term>analysis</RT·term>.
- </p>
+ <h2>Binary Hindu-Arabic representation address</h2>
<p>
- Famously, we know that universally an analyzer cannot determine if a machine is computational. This knowledge was derived by reasoning about the properties of a hypothetically existing analyzer machine. This is a third order analysis activity.
+ Would it be computationally consequential if binary Hindu-Arabic representation were used instead of unary for addresses? For unary representation each increment was constant time, but with Hindu-Arabic addresses, increments have a carry. Would the carry cause stepping to the right, combined with calling the address machine, to become slower and slower as the machine steps further and further?
</p>
<p>
- As an analyzer does not run the machine being studied, it is not required to be a machine that halts. Suppose we have a machine that produces an infinite sequence of digits to a tape without halting. A limit analyzer could examine that machine and, in some cases, determine if it has asymptotic behavior. For example, recognizing that appending a binary fractional sequence of <RT·math>0.1111...</RT·math> indefinitely evaluates in the limit to <RT·math>1.0</RT·math>. In this manner, the use of analyzers facilitates using computation theory for deriving higher order mathematics.
+ For this discussion, the Hindu-Arabic representation address is variable precision, so it will grow as the count becomes larger. The empty symbol will serve as the terminator.
</p>
-
- <h2>Second order Arithmetic</h2>
-
<p>
- In their 1990 paper, "Exact Real Arithmetic: Formulating Real Numbers as Functions," Hans-Juergen Boehm and Robert Cartwright presented a system using Turing Machines to represent 'constructive real numbers', or exact real arithmetic. This directly implements a core concept from Alan Turing's 1936 paper of the "computable number." <RT·endnote>Hans-J. Boehm and Robert Cartwright, "Exact Real Arithmetic: Formulating Real Numbers as Functions," in <em>Design and Implementation of Symbolic Computation Systems</em> (Berlin: Springer, 1990), 43 52.</RT·endnote> Boehm translated this computation theory into a practical software architecture. Instead of storing a real number as a fixed width floating point approximation, Boehm's system represents a real number as a lazy evaluation function.
+ For <RT·code>Hindu-Arabic_increment</RT·code>, the machine reads the cell under the head; upon finding a 0 or the terminator, it writes a 1 and halts. Upon finding a 1, it writes a 0, steps right, and repeats the procedure.
</p>
- <p>
- Let us employ Boehm and Cartwright's constructive real numbers, though in <RT·term>second order arithmetic</RT·term> rather than a lazy evaluation system. Because we are doing second order arithmetic, our programs need not be in the form of generators that yield a new value when called, but can be arbitrary programs containing complex control logic. If run, they evaluate physical conditions and produce results. As we are in the second order, however, we will not run them, but rather analyze and manipulate their structural logic.
- </p>
+ <RT·code>
+ increment:
+ a = TTU.read()
+ if a == 0 or a == □:
+ TTU.write(1)
+ halt
+ TTU.write(0)
+ TTU.step()
+ goto increment
+ </RT·code>
- <p>
- Consider an illustrative example. We start by defining a fundamental language for communicating with the head and tape transport unit. The statements we communicate to our tape transport unit will be explicit physical instructions: <RT·code>(step)</RT·code>, <RT·code>(left)</RT·code>, <RT·code>(read)</RT·code>, and <RT·code>(write symbol)</RT·code>. In the first order, evaluating these instructions physically actuates the tape.
- </p>
+ <p>Here <RT·code>TTU</RT·code> is the tape transport unit. It has the number to be incremented mounted on it. This number is either zero, which would mean the leftmost cell is empty, or it is of the same form as a result from a Natural Number extension machine. A <RT·code>TTU.read</RT·code> places a copy of the symbol instance found in the cell under the head into the read buffer automatically when the machine enters a new state on the programmed controller, so it is not counted as a step. In contrast, the other actions are associated with the state of the programmed controller, so when the machine arrives at a <RT·code>write</RT·code>, <RT·code>step</RT·code>, or <RT·code>halt</RT·code> node, it means that the programmed state controller has taken a step. Sequential instructions mean unconditional next state choices, whereas an <RT·code>if</RT·code> signals a conditional next state choice. The <RT·code>if</RT·code> condition is a logical proposition based on the read symbol.</p>
+
+ <p>The loop form here is worth taking note of, as it will come up again. The controller executes a first action, that of a <RT·code>read</RT·code>, followed by a middle break test, and then the recursive form actions.</p>
<p>
- Suppose our goal is to subtract 3 from 5 in the second order. Consider a Turing Machine representation named <RT·code>five</RT·code> that outputs the number 5, i.e., it prints to the tape, 'sssss', using unary notation. Let us assume that the tape is initially empty and that the empty symbol '□' terminates the string. To preserve the code for second order analysis, we define the program's Abstract Syntax Tree (AST) as a quoted <RT·code>progn</RT·code> block. This block can contain any native Lisp control structures, though for this generator it is a simple sequence:
+ Each row shows an input tape, and actions taken to write the result tape. For any given row, the result tape is the same as the input tape on the next row down. The table lists 7 increments, which is sufficient to reach the maximum count that can be held by a 3 bit counter.
</p>
- <RT·code>
- (defparameter *five-ast*
- '(progn
- (write s) (step)
- (write s) (step)
- (write s) (step)
- (write s) (step)
- (write s) (step)
- ))
- </RT·code>
-
- <p>Similarly, the AST for the number 3:</p>
-
- <RT·code>
- (defparameter *three-ast*
- '(progn
- (write s) (step)
- (write s) (step)
- (write s) (step)
- ))
- </RT·code>
+ <table>
+ <tr>
+ <th>input</th>
+ <th>action</th>
+ <th>cost (steps)</th>
+ </tr>
+ <tr>
+ <td>0</td>
+ <td>write 1, halt (result is maximum 1 bit count)</td>
+ <td>2</td>
+ </tr>
+ <tr>
+ <td>1 </td>
+ <td>write 0, step, write 1, halt</td>
+ <td>4</td>
+ </tr>
+ <tr>
+ <td>01</td>
+ <td>write 1, halt (result is maximum 2 bit count)</td>
+ <td>2</td>
+ </tr>
+ <tr>
+ <td>11</td>
+ <td>write 0, step, write 0, step, write 1, halt</td>
+ <td>6</td>
+ </tr>
+ <tr>
+ <td>001</td>
+ <td>write 1, halt</td>
+ <td>2</td>
+ </tr>
+ <tr>
+ <td>101</td>
+ <td>write 0, step right, write 1, halt</td>
+ <td>4</td>
+ </tr>
+ <tr>
+ <td>011</td>
+ <td>write 1, halt (result is maximum 3 bit count)</td>
+ <td>2</td>
+ </tr>
+ <tr>
+ <td>111</td>
+ <td></td>
+ <td></td>
+ </tr>
+ </table>
<p>
- If we were to dynamically evaluate these ASTs in the first order, the Hindu-Arabic representation of each number would be physically written in unary code on the tape.
+ The number of programmable controller state transition steps required to reach a maximum 1 bit, 2 bit, and 3 bit count are: <RT·math>[2, 8, 22]</RT·math>. Note these are cumulative. The three bit maximum count cost includes that of the two bit maximum count cost, etc. Each maximum count is exponentially larger, so a better measure is the average cost per step: <RT·math>[2/1, 8/3, 22/7] = [2, 2.667, 3.143]</RT·math>
</p>
<p>
- For the second order difference operation, we compose the ASTs to create a new program. Here we extract the body of the second operand's AST and substitute every <RT·code>(write s)</RT·code> followed by a <RT·code>(step)</RT·code> instruction with an inverted pair: a <RT·code>(left)</RT·code> instruction followed by writing the empty symbol <RT·code>(write □)</RT·code>.
+ For an <RT·math>n</RT·math> bit counter, the sequence of costs follows a pattern. Half of the increments evaluate one bit (costing 2 steps), a quarter evaluate two bits (costing 4 steps), an eighth evaluate three bits (costing 6 steps), and so forth, over the <RT·math>2^n - 1</RT·math> increments required to reach the maximum <RT·math>n</RT·math> bit count:
</p>
- <RT·code>
- (defun invert-direction (cmds)
- (cond
- ((null cmds) nil)
- (
- (and
- (cdr cmds)
- (equal (first cmds) '(write s))
- (equal (second cmds) '(step)))
- (append
- '((left) (write □))
- (invert-direction (cddr cmds))
- ))
- (
- t
- (cons
- (first cmds)
- (invert-direction (cdr cmds))
- ))))
-
- (defun primitive-compose-subtract (ast-a ast-b)
- (let
- (
- (body-a (rest ast-a))
- (body-b (rest ast-b))
- )
- `(progn
- ,@body-a
- ,@(invert-direction body-b)
- )))
- </RT·code>
-
- <p>We pass our two natural number ASTs to this composer, and we get as a result a newly synthesized AST.</p>
-
- <RT·code>
- (defparameter
- *primitive-five-minus-3-ast*
- (primitive-compose-subtract *five-ast* *three-ast*)
- )
-
- ;; *primitive-five-minus-3-ast* evaluates to:
- ;; '(progn
- ;; (write s) (step)
- ;; (write s) (step)
- ;; (write s) (step)
- ;; (write s) (step)
- ;; (write s) (step)
- ;; (left) (write □)
- ;; (left) (write □)
- ;; (left) (write □)
- ;; )
- </RT·code>
+ <RT·math> \sum_{k=1}^{n} 2k · 2^{n-k} = 2^{n+2} - 2n - 4</RT·math>
<p>
- I called this 'primitive' because it is missing the simplification. This sort of simplification is also known as compiler code optimization. Optimizations can be operation specific, or general in nature. Here we will send the primitive composition's body to an analyzer which scans the steps and removes the redundant operations.
+ To find the average cost per increment to reach each maximum, an analyst divides by the total number of increments, which is <RT·math>2^n - 1</RT·math>:
</p>
- <p>
- Because of the purity of the Lisp syntax, we are able to present a remedial simplifier example here. A <RT·code>(step)</RT·code> followed by a <RT·code>(left)</RT·code> annihilate each other, resulting in zero net movement of the tape head. Similarly, on an initially empty tape, a <RT·code>(write s)</RT·code> followed immediately by overwriting with the empty symbol <RT·code>(write □)</RT·code> annihilate each other.
- </p>
+ <RT·math> \text{Average Cost} = \frac{2^{n+2} - 2n - 4}{2^n - 1} = \frac{4(2^n-1)+4-2n-4}{2^n-1}</RT·math>
<p>
- For a program containing branched control logic, the simplifier would require a deep recursive walk of the AST to ensure operations aren't annihilated across conditional boundaries. For our explicit natural number generators, a linear scan of the <RT·code>progn</RT·code> body suffices. It calls itself recursively until the scan fails to find any further reductions, returning its optimized AST.
+ This simplifies algebraically to:
</p>
- <RT·code>
- (defun remove-annihilations (cmds)
- (cond
- ((null cmds) nil)
- (
- (and
- (cdr cmds)
- (equal (first cmds) '(step))
- (equal (second cmds) '(left)))
- (remove-annihilations (cddr cmds)))
- (
- (and
- (cdr cmds)
- (equal (first cmds) '(write s))
- (equal (second cmds) '(write □)))
- (remove-annihilations (cddr cmds)))
- (
- t
- (cons
- (first cmds)
- (remove-annihilations (cdr cmds))
- ))))
-
- (defun simplify-machine (ast)
- (let*
- (
- (cmds (rest ast))
- (reduced (remove-annihilations cmds))
- )
- (if
- (equal reduced cmds)
- ast
- (simplify-machine `(progn ,@reduced))
- )))
-
- </RT·code>
-
- <p>After giving the difference program to the simplifier, we get:</p>
-
- <RT·code>
- (defparameter
- *simplified-five-minus-3-ast*
- (simplify-machine *primitive-five-minus-3-ast*)
- )
-
- ;; *simplified-five-minus-3-ast* evaluates to:
- ;; '(progn
- ;; (write s) (step)
- ;; (write s) (step))
- </RT·code>
-
- <p>This technique of composing Turing Machine programs in the presence of simplification is closely related to that of symbol computation and expression simplification that we find in math tools such as Mathematica. We can imagine our Turing Machines as functions with names, and then symbolic computation leaves them unevaluated as here. Then the Mathematica <RT·code>Simplify</RT·code> is the compiler optimization simplifier as above. A difference in these two systems is that of the functions being reduced to Turing Machine head and tape transport unit instructions.</p>
-
- <h2>The multiplicative inverse of the additive identity</h2>
-
- <p>
- Generally it is more efficient to perform arithmetic in the first order. Also, it is easier to write Turing Machine control programs if we take Natural Number arithmetic as already available, say, from a subroutine library. On processors fixed word length arithmetic is built into the hardware. Where second order computation becomes useful is in places where a result cannot be computed in the first order.
- </p>
+ <RT·math> \text{Average Cost} = 4 - \frac{2n}{2^n - 1} </RT·math>
<p>
- The IEEE 754 floating point standard includes error algebra tags. These are explored in depth by Popova, Evgenija D. "On a Formally Correct Implementation of IEEE Computer Arithmetic." Journal of Universal Computer Science 1, no. 7 (1995): 560 569. This is a form of second order arithmetic, because each of the error tags is standing in for a machine that did not complete. When a computation fails to result in a first order object, this becomes a gateway for replacing the result with a second order object.
+ As the address space grows, the bit width <RT·math>n</RT·math> increases. The polynomial term <RT·math>2n</RT·math> is outpaced by the exponential denominator <RT·math>2^n - 1</RT·math>, causing the fractional terms to converge to zero. The average work done by the machine head converges to exactly four tape actions per increment. Constant time, same as for unary. This is a surprising counter intuitive result.
</p>
<p>
- Let us take the multiplicative inverse of the additive identity as an example, AKA division by zero. In the second order, when we attempt to compute a division, say 6/0, the machine will not reduce.
+ Consequently, maintaining an Hindu-Arabic address on a secondary Address Machine remains a constant time operation in the amortized sense, making it computationally inconsequential to the complexity class of the base machine. However, if a problem were to increment and decrement repeatedly around a binary power count, the behavior would be the limiting logarithmic time due to the length of the counter. An unlucky situation like this is called <RT·term>aliasing</RT·term>.
</p>
- <RT·code>
- (divide 6 0)
- </RT·code>
-
- <p>Now consider the compiler optimization like simplification of this expression:</p>
-
- <RT·code>
- (divide (divide 6 0) (divide 3 0))
- </RT·code>
-
- <p>The result will be:</p>
-
- <RT·code>
- (multiply 2 (divide 0 0))
- </RT·code>
-
<p>
- There is no special help here. Hence, <RT·code>(divide 0 0)</RT·code> remains as an error flag.
+ The model used here to define an address is analogous to keeping a pointer into memory. Each step then increments or decrements the pointer. Conventional processors do the converse of the Turing Machine model presented here. For a processor, the <em>address</em> is the base object, while the <em>head location</em> is what is implied by the address. On processors, pointers will be one word in width. In small geometries where computation element delay dominates, two operand adders tend towards logarithmic complexity evaluation times against bit width, and the constants are small. Because of this, it is practical to perform word width addition operations atomically in one machine cycle. In fact, for some machines, the adder time likely sets the clock period. Such an adder will be used for address arithmetic; consequently, a programmer cannot arrange a program to expose aliasing in the address increment time to the real world. It remains a theoretical observation.
</p>
<p>
- The divide machine cannot be run to produce a value, as any value from the field assigned to it would lead to contradictions. Stated more precisely, for <RT·math>x · y = q</RT·math> operations, when given an <RT·math>x</RT·math> and a <RT·code>q</RT·code> value, there is only one possible <RT·math>y</RT·math> value, and it can be recovered with <RT·math>q/x</RT·math>. However, when <RT·math>x</RT·math> is zero, and only when it is zero, <RT·code>q</RT·code> is solely determined by <RT·math>x</RT·math> independent of <RT·math>y</RT·math>, so <RT·math>y</RT·math> is ignored; it could be any value. There is no way to recover it from <RT·math>q/x</RT·math>.
+ An address is sent on a trip through the memory hierarchy to locate the corresponding memory cell, ultimately landing on a memory decoder over a physically implemented memory. Address decoders resemble carry chains, and they also have logarithmic physical evaluation times as word width grows. Hence, a fixed width word holding an address gets decoded in approximately constant time measured in clock ticks. However, the trip through the memory hierarchy is not negligible.
</p>
- <h2>Evaluating and Extending</h2>
-
<p>
- When a machine is 1) programmed to perform a function <RT·math>f</RT·math>, 2) has its tape initialized with a constant input value, say <RT·math>t</RT·math> (which can be a vector of values), and 3) is then run, writing the result <RT·math>f(t)</RT·math> and halting; we say that the run <RT·term>evaluates</RT·term> the function at <RT·math>t</RT·math>.
+ Direct address decoding operates independently of reference locality. This fundamentally differs from a Turing Machine head, which incurs a linear computation cost strictly proportional to the distance traveled. However, direct decoding is only the base of the memory hierarchy.
</p>
<p>
- When an evaluator erases the input and leaves only the result in its place, we say it is a substitution evaluator. It is an inconsequential modification to add multiple tapes each with their own tape head to a Turing Machine, so it is possible that an evaluator will have a dedicated tape for the input, a dedicated tape for the output, and have zero or more additional tapes for scratch work. In which case, instead of substitution, one tape is mounted as an input, the machine is run until it halts, and then separately the result tape can be unmounted to potentially be used in a downstream computation.
+ It is possible that decoding occurs separately in a row and column manner, and changes in the lower bits of the address walk a buffered row that was found in a prior access. This makes access to neighbor cell addresses within a row a low expense operation. However, this depends on memory implementation. One layer up, it is common for memory to be made of interleaved banks, so neighbor access that remains in the leaf collection is generally faster than that which requires fetching a new leaf set.
</p>
<p>
- In a variation of analytical evaluation, we can instead mount an initial value for a function, and call a machine to repeatedly <RT·term>extend</RT·term> the function. To state this more precisely, when a machine tape is given the initial value of <RT·math>f(t_i)</RT·math> on its input tape, and then when run until it halts having written the result <RT·math>f(t_{i+1})</RT·math> to the tape, we say that running the machine extends the function. Typically extension machines are designed so they can be used recurrently to extend the function indefinitely, thus producing a sequence of function values, <RT·math>f(t_0), f(t_1), f(t_2), \ldots</RT·math>.
+ There will, however, be a more dominant effect. For programs with islands of locality of reference, instructions or data will be discovered in a cache. Caches bring in two types of locality: that of access within a cache line, which makes neighbor walks that stay in the line fast, and cache line prefetch and replacement policies, which assume for any given fetch address that a subsequent fetch will be nearby in memory. These effects lead to similar memory behavior as a head on tape, where a single step to a neighbor is faster than taking many steps to find memory further away, but there is not a one to one correspondence.
</p>
<p>
- The difference between adjacent function values in the above sequence is called the first finite difference. The forward first difference is defined as:
- <RT·math>δ_i = f(t_{i+1}) - f(t_i)</RT·math>.
- While the backwards first difference is
- <RT·math>δ_i = f(t_i) - f(t_{i-1})</RT·math>.
- If 'forward' or 'backwards' is not specified, then the difference is taken to be 'forward'. Hence, extending a function is identical to adding the first difference. Thus a sequence of function values implies a sequence of first differences.
+ Memory accesses into the processor local memory file are even faster. If locality of reference, even in an abstract sense, remains tight, then memory cells can be accessed in a minimal amount of time.
</p>
<p>
- A second difference can be defined as the difference between two adjacent first differences. A second difference can be added to a prior first difference to create the next first difference, which could then be added to a prior function value to extend the function to its next value. This pattern can be extended to any number of levels.
+ For programs with large memory footprints and sparse memory access patterns, the dominant factor becomes the virtual memory subsystem. Here locality again comes into play as the translation lookaside buffer provides stateful location context, and the neighbor relationship between pages is sometimes taken into account for performance reasons. However, once a program starts performing at virtual memory page fetch times instead of cache access, or even local system memory access times, the program is said to be <RT·term>page thrashing</RT·term>, and it becomes very slow. It is also possible for small programs to have memory access patterns that alias against the memory subsystem's heuristics and lead to page thrashing. For a typical system, a programmer can write a small program to demonstrate this.
</p>
<p>
- The mechanical application of finite differences was famously leveraged by Charles Babbage in the design of his Difference Engine. By examining the discrete changes between consecutive outputs of a polynomial function, a person can observe that the higher order differences eventually reduce to a constant. This structure allows the computation of subsequent sequential values using only addition, bypassing the need for complex multiplication machinery.
+ In summary then, using Hindu-Arabic representation for addressing is not statistically computationally consequential. On the other hand, the idea of using a decoded address in place of a tape head raises computational model issues. So much so that it would not be an exaggeration to say that the very goal of modern computer architecture is to ensure that, within an execution context, address decode does not become computationally consequential. Architects employ a memory hierarchy to do this, and that memory hierarchy rewards locality of reference, but access times are not linear in time relative to distance from prior access. Instead they are clumped into a constant time access abstract concept of locality groups where the time to access each group grows exponentially with the distance from the processor.
</p>
- <p>
- Let us construct a forward difference pyramid for the polynomial <RT·math>f(t) = t^2</RT·math>.
+ <p>
+ Let us put this into perspective. Suppose in ancient Roman times that a clock tick for a computer was scaled to be one day long, so that the action involves sending letters rather than variable values. Under this scale, a single nanosecond of real world execution time equates to three days. Suppose a program initiated a read request for a location in memory on the Ides of March, the date when Caesar was assassinated on -0043-03-15. Surely that was a big day for posting letters. The following table provides the historical date that the variable value would finally be loaded into the processor, depending on the memory tier being accessed:
</p>
<table>
<thead>
<tr>
- <th>Count (<RT·math>t</RT·math>)</th>
- <th><RT·math>f(t) = t^2</RT·math></th>
- <th>1st Diff (<RT·math>δ^1</RT·math>)</th>
- <th>2nd Diff (<RT·math>δ^2</RT·math>)</th>
+ <th>Memory Tier</th>
+ <th>Real World Latency</th>
+ <th>Scaled Delay</th>
+ <th>Scaled Arrival Era</th>
+ <th>Historical Context</th>
</tr>
</thead>
<tbody>
<tr>
- <td>0</td>
- <td>0</td>
- <td>1</td>
- <td>2</td>
- </tr>
- <tr>
- <td>1</td>
- <td>1</td>
- <td>3</td>
- <td>2</td>
+ <td>L1 Cache Hit</td>
+ <td>1 ns</td>
+ <td>3 days</td>
+ <td>-0043-03-18</td>
+ <td>Three days after the assassination.</td>
</tr>
<tr>
- <td>2</td>
- <td>4</td>
- <td>5</td>
- <td>2</td>
+ <td>DRAM (Main memory)</td>
+ <td>100 ns</td>
+ <td>300 days</td>
+ <td>-0042-01-09</td>
+ <td>Nearly a year later, during the Liberators' civil war.</td>
</tr>
<tr>
- <td>3</td>
- <td>9</td>
- <td>7</td>
- <td></td>
+ <td>NVMe SSD Page Swap</td>
+ <td>15 µs</td>
+ <td>45,000 days</td>
+ <td>0080</td>
+ <td>123 years later, exactly as the Colosseum is completed in Rome.</td>
</tr>
<tr>
- <td>4</td>
- <td>16</td>
- <td></td>
- <td></td>
+ <td>SATA SSD Page Swap</td>
+ <td>100 µs</td>
+ <td>300,000 days</td>
+ <td>0778</td>
+ <td>821 years later, during the reign of Charlemagne and the Frankish Empire.</td>
+ </tr>
+ <tr>
+ <td>Magnetic HDD Page Swap</td>
+ <td>10 ms</td>
+ <td>30,000,000 days</td>
+ <td>82092</td>
+ <td>Tens of thousands of years in the future, long after current human civilizations are dust.</td>
</tr>
</tbody>
</table>
<p>
- In order to design for indefinite extension, the Turing Machine tape must retain the function value alongside with its forward differences. Because the second difference is constant higher order differences are all zero. Hence the tape requires a three component vector to hold the differences.
+ In computer architecture, there is a rule that the slower the access time, the larger the amount of data that should be transferred. The idea is then that of access time per bit, instead of access time per fetch. A common example given is that of a station wagon of hard drives driven across the country. <RT·endnote>Andrew S. Tanenbaum famously coined this concept in his 1981 textbook <em>Computer Networks</em>, stating: "Never underestimate the bandwidth of a station wagon full of tapes hurtling down the highway." This physical data transfer method is universally referred to as a sneakernet.</RT·endnote> Ten terabits, at three days, equates to 26 nanoseconds per bit. Not bad, if a user can wait three days to continue the program, but aliasing between data sets would make a program very slow. The Turing Machine model does not capture the effects of clumped data accesses.
</p>
- <p>
- Here is the sequence of tape states as the machine is repeatedly called to extend the function from its initial conditions at <RT·math>t = 0</RT·math>. During each step, the machine adds <RT·math>δ^1</RT·math> to <RT·math>f</RT·math>, and <RT·math>δ^2</RT·math> to <RT·math>δ^1</RT·math>, as there is no <RT·math>δ^3</RT·math>, it is taken to be zero, so <RT·math>δ^2</RT·math> is merely copied down.
- </p>
+ <!--------------------------------------------------------------------------------->
+ <RT·chapter>The Universal Turing Machine</RT·chapter>
+
+ <p>The Computer Theoretic model chapter provided symbolic definitions for the Turing machine and the TTCA variation. Those definitions were written as strings of characters, which the reader scanned, and presumably understood, thus demonstrating the ability of those text strings to convey meaning. Furthermore the text explained in detail how an executor could make use of those definitions so as to perform the input string transformations. As Turing originally noted, the executor could be a person. Alternatively, as the book continued on to describe in detail, the executor could be a machine that applied the input transformations automatically.</p>
+
+ <p>In his original paper Alan Turing put these things together and explained that a Universal Turing Machine could read the definition of a Turing Machine from tape, and thus be a Turing Machine executor. Hence, say, a Turing Machine reads the symbolic definition of a Turing Machine from one tape, and then automatically performs the described input string transformations on another tape. Said Universal Turing Machine would then be chameleon-like, performing the function of any other Turing Machine so described on the first tape.</p>
+
+ <p>The only information that the first tape of a Universal Machine need contain is the variable part, <RT·math>\mathit{MP}</RT·math>, which the text established as the <em>program</em>, as the remainder of the definition is common to all machines and can thus be built into the controller. <RT·math>\mathit{MP}</RT·math> describes a state controller, it lists the states, the symbols of the alphabet, the instruction to be issued from each state, the symbol gated next state transitions, and the halting state. The Universal Machine control program would then have to interpret that information and send the universal machine through the same steps that a human executor would take while running the described machine.</p>
+
+ <p>However, the controller can be simplified if the program encoding is changed from the raw definition. Notice that the only information that leaves the state controller while it runs are the instructions issued per state, with that list terminating when the controller reaches the halt state.</p>
+
+ <p>Imagine then, mounting a tape on a given machine, running the machine, and recording the instructions that leave the controller up until it halts. Then taking that list of instructions, and the same input tape, and mounting them on a Playback Machine. The Playback Machine then takes the instructions from the list on the first tape and issues them out of its own controller one by one. The playback controller is quite simple. Though of course, this approach has the drawback of having to run the given machine first so as to observe it, thus making the playback run moot.</p>
+
+ <p>Consider then, inserting jump table instructions to handle the next state transitions. Then a state controller can be mechanically changed into an instruction sequence with embedded jump table instructions without having to run it and observe it. Accordingly, first examine the state diagram for the controller. Take all the sequential state runs, even those of length 1, from the controller, and list their instructions in the same sequence order. Then, after each such sequence, wherever a state has next state transition arcs based on the value of the status register, insert a jump table instruction, so that it jumps to the appropriate instruction sequence.</p>
+
+ <p>Applying this mechanical procedure to the TTCA two-headed reverse string example controller results in:</p>
<RT·code>
- Initial Tape (t=0): [0, 1, 2]
- Call 1 (t=1): [1, 3, 2]
- Call 2 (t=2): [4, 5, 2]
- Call 3 (t=3): [9, 7, 2]
- Call 4 (t=4): [16, 9, 2]
+ # Phase 1: Both heads scan right to the EOM pivot
+ L·initial:
+ read('s' ,0)
+ jump(s, {EOM: L·Check_Empty, def: L·Scan_Right})
+
+ L·Scan_Right:
+ right(0)
+ right(1)
+ jump(def: L·initial)
+
+ # Phase 2: Setup pointers or short-circuit on empty string
+ L·Check_Empty:
+ status(0)
+ jump(s, {leftmost: L·Empty_Setup, def: L·Setup_Write})
+
+ L·Empty_Setup:
+ right(1)
+ jump(def: L·Write_EOR_Done)
+
+ L·Setup_Write:
+ right(1)
+ left(0)
+ jump(def: L·Copy_Read)
+
+ # Phase 3: The Linear Copy Loop
+ L·Copy_Read:
+ read('d' ,0)
+ status(0)
+ jump(s, {leftmost: L·Copy_Last, def: L·Copy_Loop})
+
+ L·Copy_Loop:
+ write('σ' ,0 ,SP)
+ write('d' ,1)
+ right(1)
+ left(0)
+ jump(def: L·Copy_Read)
+
+ # Phase 4: Final character, advance, and clean halt
+ L·Copy_Last:
+ write('σ' ,0 ,SP)
+ write('d' ,1)
+ right(1)
+ jump(def: L·Write_EOR_Done)
+
+ L·Write_EOR_Done:
+ write('σ' ,1 ,EOR)
+ halt
+ </RT·code>
+
+ <p>The state labels have become addresses into the program tape, and the address of the cell indicated by the head on the first tape is now an Instruction Pointer (IP). In a sense the programmed controller has been replaced by a little Turing Machine of its own. One that controls the head on the first tape, and moves it in response to the control instructions found on the first tape. Such a controller on a processor is called a <RT·term>sequencer</RT·term>.</p>
+
+ <p>By separating the control path from the data path, utilizing an integrated symbol alphabet, replacing states with sequential instructions, and introducing an explicit addressable instruction pointer, the theoretical machine has physically crossed the bridge to conventional computing. The resulting architecture is a stored-program, von Neumann-style machine organization.</p>
+
+ <p>Some of the default cases for the jump table unnecessarily jump to the instruction at the next sequential address. This code lacks the regularity to use a computed jump table, so this style of jump table becomes a Lisp <RT·code>cond</RT·code> statement, i.e. sequential conditional tests. So then by using the instructions <RT·code>test</RT·code>, <RT·code>beq</RT·code> (for branch on equal), and <RT·code>jump</RT·code> as control instructions, and rearranging to favor sequential execution, this code becomes:</p>
+
+ <RT·code>
+ # Phase 1: Both heads scan right to the EOM pivot
+ L·initial:
+ read('s' ,0)
+ test('s' ,EOM)
+ beq L·Check_Empty
+
+ # L·Scan_Right (Implicit fall-through)
+ right(0)
+ right(1)
+ jump L·initial
+
+ # Phase 2: Setup pointers or short-circuit on empty string
+ L·Check_Empty:
+ status(0)
+ test('s' ,leftmost)
+ beq L·Empty_Setup
+
+ # L·Setup_Write (Implicit fall-through)
+ right(1)
+ left(0)
+
+ # Phase 3: The Linear Copy Loop
+ L·Copy_Read:
+ read('d' ,0)
+ status(0)
+ test('s' ,leftmost)
+ beq L·Copy_Last
+
+ # L·Copy_Loop (Implicit fall-through)
+ write('σ' ,0 ,SP)
+ write('d' ,1)
+ right(1)
+ left(0)
+ jump L·Copy_Read
+
+ # Phase 4: Final character, advance, and clean halt
+ L·Copy_Last:
+ write('σ' ,0 ,SP)
+ write('d' ,1)
+
+ L·Empty_Setup:
+ right(1)
+
+ # L·Write_EOR_Done (Implicit fall-through)
+ write('σ' ,1 ,EOR)
+ halt
</RT·code>
+ <p>This is an assembly-level code description of an instruction sequence. To get it into final form, the labels that appear as instruction arguments would be replaced with their addresses. Symbolic labels would not appear on the tape. As an alternative to using absolute branches, relative branches could be used. Performance will be greatly enhanced for a sequencer that performs relative branching if it contains an adder circuit; otherwise, the addition of offsets would be another TTCA program invocation.</p>
+
+ <!--------------------------------------------------------------------------------->
+ <RT·chapter>The software layer</RT·chapter>
+
+ <h2>Symbol in mathematics</h2>
+
<p>
- For Call 1, the input tape is the Initial Tape. In Call 1 the machine will take the function value on the input tape, 0, and add the first difference, 1, writing the result 1, to the result tape. The machine will then continue on and take the first difference on the input tape, 1, and add the second difference on the input tape, 2, writing the result first difference to the result tape, 3. Then finishing up, the machine will not find a third difference on the input tape, so it will copy the second difference found on the input tape as the second difference for the result tape.
+ A <RT·term>symbol</RT·term> is a distinct mathematical object capable of being instantiated. Within a given context, any instance of a specific symbol evaluates as equal to any other instance of that identical symbol, and evaluates as not equal to any instance of a different symbol.
</p>
<p>
- For Call 2, the input tape is the result tape from Call 1. Execution then proceeds identically to Call 1. This pattern repeats for all successive calls. A person can see the values of <RT·math>t^2</RT·math> as the first number on each result tape. Any polynomial can be computed in this manner, and the only ALU required is an adder that is also capable of subtraction.
+ Put more formally, given a set of instantiable objects and a collection of instances made from them, for these objects to be symbols, two conditions must be met. First, it must be possible to define an instance comparison operation, denoted <RT·math>=</RT·math>, that acts as an equivalence relation to partition the collection into discrete equivalence classes. There must be a one to one correspondence between the resulting equivalence classes and the original instantiable objects from which the member instances were derived.
</p>
- <h3>Lemma: Only row 0 is needed</h3>
+ <p>
+ It follows from this definition that the distinct equivalence classes can be used as a proxy for the instantiable objects themselves. That is, a person can name either the instantiable object or the equivalence class, and then through this correspondence, find the other.
+ </p>
+
+ <h2>Symbol in computing</h2>
<p>
- The extension function can be called any number of times when the initial tape is identical to row 0 of the table of finite differences. No other row is needed from the table of finite differences.
+ In the context of real machines, the symbol itself can be defined as a factory that produces symbol instances. A new symbol instance of the given symbol is then made, say, by calling the factory's <RT·code>make</RT·code> function. All of the symbol instances made by the factory constitute the members of the corresponding equivalence class.
</p>
<p>
- This follows from the mechanical procedure given above for computing the next row of the table from any given row.
+ A <RT·term>symbol instance</RT·term> newly minted by the factory is said to <RT·neologism>come direct from the factory</RT·neologism>. A symbol instance direct from the factory is also called an <RT·neologism>original</RT·neologism>.
</p>
- <h3>Lemma: Evaluating call <RT·math>\omega</RT·math> requires an initial tape populated with components 0 through <RT·math>\omega</RT·math> of row 0</h3>
+ <h3>Required properties of symbol factories</h3>
<p>
- Let the <RT·term>extent</RT·term> refer to the maximum index for accessing a component of a vector; the extent is often denoted as <RT·math>\omega</RT·math>. Performing extensions to calculate the function value at call <RT·math>\omega</RT·math> makes use of the initial tape up to index <RT·math>\omega</RT·math>, or as many nonzero values as are available up to that extent.
+ Any two symbol instances returned directly from two distinct factories will always evaluate to <RT·code>False</RT·code> during an equality comparison. In other words, two distinct originals will always be not equal.
</p>
<p>
- To make this precise, let us define the <RT·term>difference vector</RT·term> at call <RT·math>k</RT·math> as <RT·math>D_k</RT·math>. The components of this vector are <RT·math>D_{k, 0}, D_{k, 1}, \ldots</RT·math> up to the maximum available difference. The first component, <RT·math>D_{k, 0}</RT·math>, is the function value itself. The component <RT·math>D_{k, 1}</RT·math> is the first difference, and so on.
+ Given an original, all copies <RT·neologism>stemming from</RT·neologism> it will be equal to each other and to the original. By <RT·neologism>stemming from</RT·neologism>, this definition includes all direct copies and copies of copies.
</p>
<p>
- Thus, this lemma claims that to compute <RT·math>D_{\omega, 0}</RT·math>, the highest component index accessed from the initial tape vector <RT·math>D_{0, i}</RT·math> will be <RT·math>i = \omega</RT·math>.
+ Given any two originals, say <var>A</var> and <var>B</var>, it is established that <var>A</var> is not equal to <var>B</var>, as discussed above. Note also that <var>A</var> is not equal to any copy stemming from <var>B</var>, and <var>B</var> is not equal to any copy stemming from <var>A</var>.
</p>
<p>
- Note that to compute the <RT·math>D_{k, 0}</RT·math> result by the mechanical procedure given above requires adding the input tape first difference to the prior difference. That is, <RT·math>D_{k, 0} = D_{k-1, 0} + D_{k-1, 1}</RT·math>. Hence we see immediately the lemma holds for the first call where <RT·math>k = \omega = 1</RT·math>.
+ Though symbol instances are integer like in that copy and equality comparison operations can be used with them, symbol instances are disallowed from being used with other integer operators. Symbols cannot be compared for greater than or less than; they cannot be incremented, added, nor subtracted, etc.
</p>
+ <h3>Instance implementation</h3>
+
<p>
- For an arbitrary <RT·math>k</RT·math> value we discovered that the highest index required of the <RT·math>D_{k-1, i}</RT·math> vector is <RT·math>i=1</RT·math>.
- The value <RT·math>D_{k-1, 1}</RT·math> is computed from our mechanical procedure as
- <RT·math>D_{k-1, 1} = D_{k-2, 1} + D_{k-2, 2}</RT·math>. Given we already know that to compute the value merely requires the prior value and first difference, we find that the largest index needed from vector <RT·math>D_{k-2, i}</RT·math> is 2.
+ Within a process, a reference to the factory can be used as a symbol instance, which will cause the factory to become trivial. Making a new instance will merely require copying the factory reference, and there will be nothing in memory that the base factory reference is pointing to.
</p>
<p>
- There is a strict linear progression here. The computation of any component <RT·math>D_{k, i}</RT·math> inherently requires <RT·math>D_{k-1, i}</RT·math> and <RT·math>D_{k-1, i+1}</RT·math>. Therefore, each step backwards in time <RT·math>k</RT·math> expands the required index <RT·math>i</RT·math> by exactly one. By induction, computing the base value <RT·math>D_{\omega, 0}</RT·math> at call <RT·math>\omega</RT·math> requires tracing back <RT·math>\omega</RT·math> steps to the initial tape at <RT·math>k=0</RT·math>, which forces the maximum accessed index to be <RT·math>0 + \omega = \omega</RT·math>. Thus, the highest required component from the initial tape is <RT·math>D_{0, \omega}</RT·math>.
+ In general, memory addresses are built in symbol instances, hence within the context of a single process run, a program can make use of these symbols. However, this diminishes the size of the address space and leaves the memory at those addresses unused. A common hedge is then to use references into a dictionary, where the data looked up in the dictionary is the <RT·term>name</RT·term> of the symbol.
</p>
<p>
- The exact algebraic composition of this mechanical expansion is formalized by Newton's calculus of finite differences <RT·endnote>Isaac Newton formalized this interpolation method in 1675, later published in his <em>Methodus Differentialis</em> (1711). For a comprehensive foundational treatment, see George Boole, <em>A Treatise on the Calculus of Finite Differences</em> (Cambridge: Macmillan and Co., 1860), Chapter II.</RT·endnote>. Instead of executing the machine incrementally, a person can calculate the function value at call <RT·math>\omega</RT·math> directly as a linear combination of the initial tape components using Newton's forward difference formula:
+ Such symbol names are non structural strings, so they do not need to follow the rules of symbols. For example, a program written where references to strings were used as symbol instances, could give multiple, or all, strings the same name, and the program would function. Conventionally, the names are made to be distinct so as to avoid confusion. The hazard here is that a programmer will then conflate the string name with the symbol instance, and perform symbol operations with it.
</p>
<p>
- <RT·math>D_{\omega, 0} = \sum_{j=0}^{\omega} \binom{\omega}{j} D_{0, j}</RT·math>
+ An alternative implementation is to have the factory return an integer value. Each factory has a base integer that is distinct from that of other factories. Calling <RT·code>make</RT·code> then returns the base integer.
</p>
<p>
- Because the binomial coefficient <RT·math>\binom{\omega}{j}</RT·math> evaluates to exactly zero for any integer <RT·math>j > \omega</RT·math>, the summation naturally truncates at index <RT·math>\omega</RT·math>. This algebraic property perfectly mirrors the physical boundary established by the machine execution trace. Furthermore, the relationship is symmetric. A person can compute the specific components of the initial tape, <RT·math>D_{0, n}</RT·math>, directly from the sequence of evaluated function calls, <RT·math>D_{k, 0}</RT·math>, using the alternating binomial sum:
+ As another alternative, each factory can be given a base string, and then <RT·code>make</RT·code> returns a copy of the base string. Here we refer literally to the string as the symbol instance. There is no separate name, and the string data, not the reference to the string, becomes the symbol instance. This is however merely an architectural constraint, under the hood an implementation could use string references as long as it always appears to the programmer that the string value is being used.
</p>
<p>
- <RT·math>D_{0, n} = \sum_{k=0}^{n} (-1)^{n-k} \binom{n}{k} D_{k, 0}</RT·math>
+ At the time of this writing many machines use 64 bit words. This is equivalent to 8 ASCII characters, while the average size of an identifier is about 5 characters. Hence the approach of using a string as a symbol might not be as inefficient as it seems to be at first. Using strings has advantages. Symbol instances can carry semantic clues for the programmer. There is no hazard of conflating the string instance with the name, as they are the same. Also, a string instance will have integrity across contexts, such as between invocations or when passed between processes (note the section below on crossing context boundaries). A drawback is in cases there is no language support, the strings are typically ad hoc so the guarantee of distinctness becomes merely a contract with the programmer.
</p>
+ <h3>Distinctness across contexts</h3>
+
<p>
- As a consequence of this lemma, we know that for any finite number of calls, i.e. finite <RT·math>\omega</RT·math>, that a finite prefix of <RT·math>D_0</RT·math> is used.
+ If a symbol persists across contexts (such as across scopes or processes), it must remain distinct from all other symbols in its new context.
</p>
- <h3>Lemma: A polynomial function of degree <RT·math>\omega</RT·math> will have a <RT·math>D_0</RT·math> vector of extent <RT·math>\omega</RT·math></h3>
-
<p>
- A polynomial of degree <RT·math>\omega</RT·math> is defined by a highest order term <RT·math>a_\omega t^\omega</RT·math>. The first forward difference operator, <RT·math>δ f(t) = f(t+1) - f(t)</RT·math>, inherently cancels the <RT·math>t^\omega</RT·math> term. The binomial expansion of <RT·math>(t+1)^\omega</RT·math> yields <RT·math>t^\omega</RT·math> as its leading term, which subtracts out, leaving a new polynomial of exactly degree <RT·math>\omega - 1</RT·math>.
+ One way to meet this requirement is to find a scope encompassing both contexts and to place the symbol factory there. Another solution is to give each context a distinct root symbol and to use an array of symbols in place of the imported symbol. Yet another approach is to associate an imported symbol with a new symbol in the given new context using a correspondence map.
</p>
<p>
- Taking successive finite differences reduces the degree of the polynomial by exactly one at each step. Consequently, the <RT·math>\omega</RT·math>th difference evaluates to a constant, and the next difference evaluates to exactly zero.
+ When utilizing memory addresses as symbols in a virtual memory environment, the convention is to disallow addresses in one process from being used in another. If that isolation is insufficient, it is often adequate to use indexes instead of addresses, taking the address to the base of the data structure. Though the absolute address of the data structure might differ across contexts, the relative offset remains constant. Another approach is to reserve memory address blocks and to guarantee imported pages have the exact same addresses as before, though they might be imported sequentially to reuse the memory block. In architecture, this is generally known as the <RT·term>pointer swizzling problem</RT·term>.
</p>
- <h3>Lemma: A <RT·math>D_0</RT·math> vector of finite extent <RT·math>\omega</RT·math> generates a polynomial function of degree <RT·math>\omega</RT·math></h3>
+ <h2>Symbol copy consequentiality</h2>
<p>
- If an initial difference vector <RT·math>D_0</RT·math> has an extent of <RT·math>\omega</RT·math>, the sequence of function values generated by the machine corresponds to a polynomial of exactly degree <RT·math>\omega</RT·math>. A person can demonstrate this by algebraically recovering the polynomial constants, <RT·math>a_i</RT·math>, directly from the initial tape components, <RT·math>D_{0, i}</RT·math>.
+ Copying symbol instances is an integral part of the operation of the Turing machine. Typically the programmed control steps are used as a cost metric for running a program. Such steps integrate operations of copying symbols to and from the tape. Thus from a pure step count perspective the cost of the symbol copy is inconsequential, not due to any behavior of a symbol copy, but rather due to the symbol copy being subsumed within the definition of the step.
</p>
<p>
- Consider an extent of <RT·math>\omega = 0</RT·math>, forming a polynomial of degree 0, <RT·math>f(t) = a_0</RT·math>. Evaluating at <RT·math>t = 0</RT·math> yields <RT·math>f(0) = a_0</RT·math>. Because the machine's initial value is <RT·math>D_{0,0}</RT·math>, a person establishes <RT·math>a_0 = D_{0,0}</RT·math>.
+ However, this is feels unsatisfactory. Here we are tying to build a bridge from computation theory to architecture, and thus we want a step to be a constant time occurrence at least in the average, or at least approximately. To model the symbol copy time, a symbol could be copied a tranche of bits per step, thus giving the copy a step count. Then, if symbols were to grow in tranche count as a program ran longer, then symbol copy of ever longer symbols would be computationally consequential.
</p>
<p>
- When a person extends the extent to <RT·math>\omega = 1</RT·math>, the next term is added to form <RT·math>f(t) = a_0 + a_1 t</RT·math>. The first forward difference at <RT·math>t = 0</RT·math> is <RT·math>D_{0,1} = f(1) - f(0) = (a_0 + a_1) - a_0 = a_1</RT·math>. Therefore, <RT·math>a_1 = D_{0,1}</RT·math>. Note that the addition of the linear term did not change the evaluation at <RT·math>t = 0</RT·math>; the <RT·math>a_0</RT·math> constant remains firmly anchored to <RT·math>D_{0,0}</RT·math>.
+ Fortunately, the Turing Machine is defined with a finite alphabet and a couple of additional symbols. As these are part of the machine definition, and thus do not change at run time, there is no growth in size, and the time to copy them is inconsequential.
</p>
<p>
- Extending to <RT·math>\omega = 2</RT·math>, the polynomial becomes <RT·math>f(t) = a_0 + a_1 t + a_2 t^2</RT·math>. The second difference at <RT·math>t = 0</RT·math> is calculated as <RT·math>D_{0,2} = f(2) - 2f(1) + f(0)</RT·math>. Substituting the polynomial yields <RT·math>D_{0,2} = (a_0 + 2a_1 + 4a_2) - 2(a_0 + a_1 + a_2) + a_0 = 2a_2</RT·math>. Thus, a person recovers <RT·math>a_2 = D_{0,2} / 2</RT·math>. The lower coefficient then adjusts to <RT·math>a_1 = D_{0,1} - a_2</RT·math>.
+ On a real machine, the factory would be used to make the data alphabet and a couple of additional symbols. As these are set up before the machine runs, and thus before we count steps, these actions are computationally inconsequential.
</p>
+ <h2>Alphabet</h2>
+
<p>
- Continuing to <RT·math>\omega = 3</RT·math>, the polynomial is <RT·math>f(t) = a_0 + a_1 t + a_2 t^2 + a_3 t^3</RT·math>. Evaluating the third difference <RT·math>D_{0,3}</RT·math> isolates the highest order term, yielding <RT·math>D_{0,3} = 6a_3</RT·math>. This allows immediate recovery of the highest constant: <RT·math>a_3 = D_{0,3} / 6</RT·math>.
+ An alphabet is another name for 'a set of symbols'. Because it is simply a set, an infinite number of distinct alphabets can exist, and a set of symbols could be finite or infinite.</p>
+
+ <p>For a Turing machine there are a fixed number of alphabets that are all finite. The set of data symbols, control symbols, and controller state name symbols, are all finite and set in advance.
</p>
+ <h2>Symbol implementation examples</h2>
+
<p>
- Once <RT·math>a_3</RT·math> is known, the shifting nature of the lower order constants becomes apparent through back substitution. The second difference equation expands to <RT·math>D_{0,2} = 2a_2 + 6a_3</RT·math>. Substituting the known <RT·math>a_3</RT·math> allows recovery of <RT·math>a_2 = (D_{0,2} - 6a_3) / 2</RT·math>. Similarly, the first difference equation expands to <RT·math>D_{0,1} = a_1 + a_2 + a_3</RT·math>, which resolves to <RT·math>a_1 = D_{0,1} - a_2 - a_3</RT·math>. Through all of this shifting, <RT·math>a_0 = D_{0,0}</RT·math> remains entirely unperturbed.
+ The <RT·code>enum</RT·code> of C is used to make alphabets of named symbols. Each entry in the enum is a static symbol factory, and instances are distinct integers.
</p>
+ <RT·code>
+ /* The enum definition acts as the factory. */
+ typedef enum {
+ SYMBOL_EMPTY = 0,
+ SYMBOL_ZERO = 1,
+ SYMBOL_ONE = 2,
+ SYMBOL_A = 3,
+ SYMBOL_B = 4
+ } TapeAlphabet;
+
+ /* Instantiating copies of the symbols: */
+ TapeAlphabet cell_1 = SYMBOL_A;
+ TapeAlphabet cell_2 = SYMBOL_A;
+
+ /* Equality comparison over instances */
+ if(cell_1 == cell_2){
+ /* Evaluates to True */
+ }
+ </RT·code>
+
<p>
- To observe the structural integrity of this progression, a person can array these relationships into a system of equations mapping the initial tape to the polynomial constants:
+ The <RT·code>enum</RT·code> is a static alphabet made by the compiler, where symbol instances are integers. In the following example, the alphabet is made dynamically, where each symbol instance is a string pointer.
</p>
- <table>
- <thead>
- <tr>
- <th>Difference</th>
- <th>Equation</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td><RT·math>D_{0,0}</RT·math></td>
- <td><RT·math>= a_0</RT·math></td>
- </tr>
- <tr>
- <td><RT·math>D_{0,1}</RT·math></td>
- <td><RT·math>= a_1 + a_2 + a_3 + \ldots + a_\omega</RT·math></td>
- </tr>
- <tr>
- <td><RT·math>D_{0,2}</RT·math></td>
- <td><RT·math>= 2a_2 + 6a_3 + \ldots</RT·math></td>
- </tr>
- <tr>
- <td><RT·math>D_{0,3}</RT·math></td>
- <td><RT·math>= 6a_3 + \ldots</RT·math></td>
- </tr>
- <tr>
- <td><RT·math>\ldots</RT·math></td>
- <td><RT·math>\ldots</RT·math></td>
- </tr>
- <tr>
- <td><RT·math>D_{0,\omega}</RT·math></td>
- <td><RT·math>= \omega! a_\omega</RT·math></td>
- </tr>
- </tbody>
- </table>
+ <RT·code>
+ #include <string.h>
+ #include <stdlib.h>
+ #include <stdio.h>
- <p>
- A pattern emerges. Because each successive forward difference operator annihilates the lowest power of <RT·math>t</RT·math>, the resulting algebraic system is upper triangular. For any extent <RT·math>\omega</RT·math>, the <RT·math>\omega</RT·math>th difference equation reduces to <RT·math>D_{0,\omega} = \omega! a_\omega</RT·math>. This structural guarantee permits a person to reliably recover <RT·math>a_\omega = D_{0,\omega} / \omega!</RT·math>. Because an initial tape of extent <RT·math>\omega</RT·math> dictates that <RT·math>D_{0,\omega}</RT·math> is nonzero, <RT·math>a_\omega</RT·math> is guaranteed to be nonzero. All subsequent lower order constants are then systematically resolved through cascading back substitution.
- </p>
+ // maximum legal index into the symbol list
+ #define LIST_EXTENT 3
+
+ typedef const char *Instance;
+ typedef struct{
+ Instance *head;
+ Instance *tail;
+ Instance *extent;
+ } List;
+
+ static List SYM_LIST = {NULL ,NULL ,NULL};
+
+ Instance make_list(const char *name){
+ size_t size = LIST_EXTENT + 1;
+ SYM_LIST.head = (Instance *)malloc( size * sizeof(Instance) );
+ SYM_LIST.tail = SYM_LIST.head;
+ SYM_LIST.extent = SYM_LIST.head + LIST_EXTENT;
+ *SYM_LIST.head = strdup(name);
+ return *SYM_LIST.head;
+ }
+
+ Instance make_symbol(const char *name){
+ if(!SYM_LIST.head) return make_list(name);
+
+ Instance *pt = SYM_LIST.head;
+ while(1){
+ if( strcmp(*pt ,name) == 0 ) return *pt;
+
+ if(pt == SYM_LIST.extent){
+ fprintf(stderr ,"symbol list overflow for %s\n" ,name);
+ return NULL;
+ }
+
+ if(pt == SYM_LIST.tail){
+ *++SYM_LIST.tail = strdup(name);
+ return *SYM_LIST.tail;
+ }
+
+ pt++;
+ }
+ }
+
+ int main(){
+ Instance a = make_symbol("a");
+ Instance b = make_symbol("b");
+ Instance c = make_symbol("c");
+ Instance d = make_symbol("d");
+ Instance e = make_symbol("e"); // overflows table
+
+ Instance *pt = SYM_LIST.head;
+ Instance *pt_tail = SYM_LIST.tail;
+ while(1){
+ puts(*pt);
+ if(pt == pt_tail) break;
+ pt++;
+ }
+
+ if(e == NULL) printf("e is NULL\n");
+ }
+ </RT·code>
+
+ <h2>Property</h2>
<p>
- To observe the formal mechanics of this progression, a person can alternatively array these relationships into a matrix equation mapping the polynomial constants, <RT·math>a_i</RT·math>, to the initial tape differences, <RT·math>D_{0,n}</RT·math>. The coefficients of this transformation are defined by the Stirling numbers of the second kind, denoted <RT·math>S(i,n)</RT·math>:
+ A <RT·term>property</RT·term> is a pair, where the components are called the <RT·term-em>name</RT·term-em> and the <RT·term-em>value</RT·term-em>. A <RT·term>name</RT·term> is an instance of a symbol and it must uniquely identify the property within its context. The value is a variable that can be written then read back.
</p>
- <RT·math>
- \begin{bmatrix} D_{0,0} \\ D_{0,1} \\ D_{0,2} \\ \vdots \\ D_{0,\omega} \end{bmatrix} =
- \begin{bmatrix}
- 0! S(0,0) & 0! S(1,0) & 0! S(2,0) & ⋯ & 0! S(\omega,0) \\
- 0 & 1! S(1,1) & 1! S(2,1) & ⋯ & 1! S(\omega,1) \\
- 0 & 0 & 2! S(2,2) & ⋯ & 2! S(\omega,2) \\
- \vdots & \vdots & \vdots & \ddots & \vdots \\
- 0 & 0 & 0 & ⋯ & \omega! S(\omega,\omega)
- \end{bmatrix}
- \begin{bmatrix} a_0 \\ a_1 \\ a_2 \\ \vdots \\ a_\omega \end{bmatrix}
- </RT·math>
+ <h2>Dictionary and reference</h2>
+
+ <p>A <RT·term>dictionary</RT·term> is a property context object. It is a set of properties, where a property may be selected from the set given its name, which is a symbol instance. The corresponding property value is then the <RT·term>referenced object<RT·term>.</p>
+
+ <p>A <RT·term>reference</RT·term> is a dictionary key which uniquely identifies a property in the dictionary. A dictionary is also known as a symbol table. In hardware design, the symbols are often unsigned integers, AKA Natural Numbers, and a symbol table is implemented using an array that is indexed into using the key.</p>
+
+ <p>Any programming object that holds other objects is a container, whereas a dictionary is a specific type of key access container.</p>
+
+ <h2>A Turing Machine tape cell</h2>
<p>
- For any extent <RT·math>\omega</RT·math>, the final row of this strictly upper triangular matrix again simplifies to <RT·math>D_{0,\omega} = \omega! a_\omega</RT·math>.
+ A <RT·term>cell</RT·term> is the <RT·term>square</RT·term> from Alan Turing's 1936 paper <RT·endnote>Alan M. Turing, "On Computable Numbers, with an Application to the Entscheidungsproblem," <em>Proceedings of the London Mathematical Society</em> s2 42, no. 1 (1936): 230 265.</RT·endnote>. Mathematically, a cell is a referencable distinct identifiable set, with one to three property members depending on the type of cell.
</p>
<p>
- The main diagonal contains strictly non zero factorials, ensuring the matrix is invertible. By inverting this matrix, a person replaces the cascading back substitution with a direct, closed form equation to recover any constant <RT·math>a_i</RT·math>. The inversion utilizes the signed Stirling numbers of the first kind, denoted <RT·math>s(n,i)</RT·math> (see the Appendix on Stirling numbers).
+ A <RT·term>leftmost cell</RT·term> is a set of two properties named 'right neighbor' and 'data'. A <RT·term>rightmost cell</RT·term> is a set of two properties named 'left neighbor' and 'data'. An <RT·term>medial cell</RT·term> is a set holding three properties: a 'left neighbor', a 'right neighbor', and 'data'. While an <RT·term>island cell</RT·term> owns a single compulsory property, that of 'data'.
</p>
<p>
- <RT·math>a_i = \sum_{n=i}^{\omega} \frac{s(n,i)}{n!} D_{0,n}</RT·math>
+ The value of a neighbor property is a non-null single reference to a cell. The value of a data property is strictly an instance of an alphabet symbol, or alternatively, an instance of the empty symbol.
</p>
+ <h2>Tape</h2>
+
<p>
- Thus, extending this procedure <RT·math>\omega</RT·math> times definitively recovers the constants for exactly a polynomial of degree <RT·math>\omega</RT·math>.
+ A tape is a set containing exactly one leftmost cell and an infinite number of medial cells. For each cell in the set, called cell A, if cell A has a right neighbor that is cell B, then cell B's left neighbor is cell A. Similarly, if cell A has a left neighbor of cell B, then cell B's right neighbor is cell A. Furthermore, any cell in the set must be reachable by traversing right neighbors starting from the leftmost cell, in a finite number of steps.
</p>
<p>
- This mechanical recovery of standard polynomial constants is completely analogous to Newton's interpolation formula <RT·endnote>Ibid.</RT·endnote>. Instead of resolving the standard constants <RT·math>a_i</RT·math> through an upper triangular matrix, a person can construct the polynomial directly by treating the initial tape components as the exact coefficients for a basis of binomial terms:
+ To say that a tape is infinite, and to simultaneously require that any cell can be reached in a finite number of steps, means that after reaching said cell, there will always be further cells to the right. Consequently, though <em>any</em> cell can be reached in finite hops starting at the leftmost cell, a finite traversal of the tape can not visit <em>all</em> of the cells. This seeming contradiction is at the very heart of the definition of the countable infinity in mathematics.
</p>
<p>
- <RT·math>f(t) = \sum_{n=0}^{\omega} D_{0, n} \binom{t}{n}</RT·math>
+ In conventional computation theory, once a tape is defined, the cell neighbor properties values are fixed. Cells don't move, new cells cannot be added, and cells existing on the tape cannot be removed. This fits the definition of a <RT·term>space</RT·term>, so we can say that a Turing Machine tape has a constant, fixed, linear <RT·term>topology</RT·term>. This permanence of cells matches the reality of hardware memories. On the other hand, it does not track well with general memory containers such as linked lists where <RT·term>destructive</RT·term> operations are often permitted.
</p>
<p>
- Because the <RT·math>n</RT·math>th binomial coefficient expands into a polynomial of exactly degree <RT·math>n</RT·math>, and the summation is bounded by the finite extent <RT·math>\omega</RT·math> where <RT·math>D_{0, \omega}</RT·math> is definitively nonzero, the constructed function <RT·math>f(t)</RT·math> is structurally guaranteed to be a polynomial of degree <RT·math>\omega</RT·math>.
+ Nor can a cell data property be removed; however, unlike for cell neighbor property values, the cell data property value can be changed while the Turing Machine is running. In fact some people would say this is the whole point of running a Turing Machine.
</p>
- <h2>Difference table for an exponential function</h2>
-
<p>
- Here is the table of finite differences for the function <RT·math>2^t</RT·math>.
+ An empty tape is filled with empty symbols. However, as we noted above, a Turing Machine cannot visit all the cells on a tape, so a Turing Machine cannot erase a tape in advance for another Turing Machine to use. Say a Turing Machine tried to erase a tape by writing the empty symbol, then stepping right, then repeating. At some point it must halt. When it halts some number of cells will have been written with the empty symbols, but there will be further cells to the right of the cell the machine halted at, which have not yet been erased. So for example, if a machine wrote 10 cells with the empty symbol, then it might be the case that the prior user of the tape had written data to 11 of the cells. Without historical knowledge the eraser machine can not know where to stop. So here we see another meaning of infinity, it speaks to the situation of not having historical knowledge. And thus, we are left to decree into existence an empty tape, or as the mathematicians say, empty tapes are available <em>by definition</em>.
</p>
- <table>
- <thead>
- <tr>
- <th>Count (<RT·math>t</RT·math>)</th>
- <th><RT·math>f(t) = 2^t</RT·math></th>
- <th>1st Diff (<RT·math>δ^1</RT·math>)</th>
- <th>2nd Diff (<RT·math>δ^2</RT·math>)</th>
- <th>3rd Diff (<RT·math>δ^3</RT·math>)</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>0</td>
- <td>1</td>
- <td>1</td>
- <td>1</td>
- <td>1</td>
- </tr>
- <tr>
- <td>1</td>
- <td>2</td>
- <td>2</td>
- <td>2</td>
- <td>2</td>
- </tr>
- <tr>
- <td>2</td>
- <td>4</td>
- <td>4</td>
- <td>4</td>
- <td></td>
- </tr>
- <tr>
- <td>3</td>
- <td>8</td>
- <td>8</td>
- <td></td>
- <td></td>
- </tr>
- <tr>
- <td>4</td>
- <td>16</td>
- <td></td>
- <td></td>
- <td></td>
- </tr>
- </tbody>
- </table>
+ <p>
+ Mathematically, a Turing Machine tape is a specialized path graph. The neighbor properties are the edges. In this special form, properties are attached to the edges. A Turing Machine has a clock based state controller. Taking a step is an atomic operation. The machine is either in state <RT·math>q_i</RT·math> or in state <RT·math>q_{i+1}</RT·math>, there is no mathematical meaning given to the concept of <em>during a step</em>, which is why no properties are added to the edges of the tape path graph. This is not to say that some analysis of a Turing Machine program won't take pairs of nodes from the path graph and give them meaning, thus effectively giving properties to the arcs. However, this would not be part of the machine definition, such a program must go through the additional effort of making node pairs, because the machine itself does not provide the program with a feature for attaching properties directly to the neighbor property itself.</p>
+
+ <p>An example of a non-Turing tape like model is the Emacs vertical line cursor model, where a cursor is said to be between characters. An ASCII file offers no such feature as 'in between' characters. Like a Turing Machine tape, a medial character in an ASCII file has a left neighbor and a right neighbor character. Any attempt to represent an in between cursor within the file itself would require inserting more characters into the file under the same model of every medial character having a left and a right character. Consequently, though emacs presents a model where cursor is located in between two characters, this model is only due to the interpretation of the functions' actual effects presented to users in the documentation. For example, instead of saying a cursor is located upon a character, and that inserting a character inserts the character to the right of the given character, the manual gives the description that the new character is inserted at the cursor location, where said cursor is in between the given character and its right neighbor. Thus the documentation presents the user with one model, which by necessity of using standard library calls to work with files, is built upon another model.</p>
+
+
+
+ <!--------------------------------------------------------------------------------->
+ <RT·chapter>Computational Analysis</RT·chapter>
+
+ <h2>Definition</h2>
<p>
- The first difference of <RT·math>2^t</RT·math> is also <RT·math>2^t</RT·math>, so row 0 of the table will have an infinite number of values. Fortunately, due to the lemma stating that evaluating call <RT·math>\omega</RT·math> requires an initial tape populated with components 0 through <RT·math>\omega</RT·math> of row 0, exactly those components are needed for the recurrence to proceed. Furthermore, when new values are needed, they are easily computed. A programmer need not store the entire infinite first row of the difference table on a static tape.
+ In mathematics, <RT·term>analysis</RT·term> is the rigorous study of limits, continuity, rates of change, and bounds. It encompasses several specialized branches. Real analysis studies the behavior of real numbers, sequences, and continuous functions. Complex analysis extends these principles to functions of complex variables. Functional analysis examines vector spaces where the elements themselves are functions. Numerical analysis focuses on the design of algorithms to yield approximate solutions for continuous mathematical problems. Harmonic analysis studies the representation of functions or signals as the superposition of basic waves, such as Fourier series. Across all these branches, analysis provides a formal framework for evaluating mathematical objects.
</p>
<p>
- Instead of attempting to write an infinite sequence to a physical tape, a person can encapsulate the generation logic within a dedicated Turing Machine. In the tradition of constructive arithmetic associated with Boehm, this generator machine acts as a virtual tape. When the primary extension machine requires the next difference component, it queries the generator machine, which computes and yields the value on demand. This lazy evaluation strategy elegantly handles functions with infinite difference sequences without exhausting finite memory.
+ In computation theory, <RT·term>computational analysis</RT·term> is defined as the static evaluation of a formal system or machine definition to deduce its absolute boundaries and properties. This process remains entirely distinct from dynamically executing the machine to yield a computed result.
</p>
- <h2>Composition of recurrence functions</h2>
-
- <p>If we desire to compose recurrence functions, in the first order or the second order, the call counts must first be synchronized.</p>
+ <p>
+ This analytical perspective has been present from the very beginning of the field. Alan Turing's formulation of the halting problem relies explicitly on one machine examining another. To properly evaluate the limits of this analytical capacity, the examining machine must necessarily be given definitions of programs that loop infinitely. Consequently, the foundational proofs of computer science formally establish non computational programs as legitimate objects of analytical study.
+ </p>
- <p>Say for example, we have a machine called fizz that adds 3 to the input on the tape, and another machine called buzz that adds 5 to the input on the tape, and we wanted to compose the two machines. We would have to define what a call to the resultant machine means to the machines in composition.</p>
+ <p>
+ We call a program that examines another program so as to deduce properties of its results an <RT·term>analyzer</RT·term>. The program or machine definition being subjected to this evaluation is called the <RT·term>studied program</RT·term> or <RT·term>studied machine</RT·term>.
+ </p>
- <p>If one call to the resultant machine makes the equivalent of one call to the fizz, and one to buzz, the the resultant machine can be simplified to add 8 to its input tape each time it is called. In contrast if one call to the resultant machine makes the equivalent of 5 calls to fizz, and 3 calls to buzz, then the resultant machine reduces to adding 30 to the input tape per call. The two machines are very different.</p>
+ <p>
+ Turing Machines that halt in a finite number of steps for any finite input within a stipulated domain are said to be <RT·term>computational</RT·term> over that domain.
+ </p>
- <p>Performing operations with recurrence functions requires integrating a call algebra in addition to integrating the machines. Often this is handled by considering the machines being composed to be subroutines, and the outer controller than explicitly calls them. The call algebra can then be dynamic depending on the input. Calls can be skipped due to being inside conditionals, or performed an arbitrary number of times in loops. Though simple control structures that are static are more math like, and are more likely to be more affected by optimizations (simplifications).</p>
+ <p>
+ By definition, first order analysis is the running of programs, and it has its place.
+ Generally, when we want to know what output a computational machine will produce, the fastest route to this knowledge is to run the program. Most programs written to solve problems are most effectively run to solve those problems rather than analyzed to deduce what they will produce.
+ </p>
- <h2>A pole and zero cancellation</h2>
+ <p>
+ Now suppose we quantify the inputs to a computational machine over a domain and want to know a property of the machine. Perhaps, say, that it always produces an even number. If the quantification is over a large set, then it might be faster to study the machine than it would be to run the machine on every input in the domain while checking its output.
+ </p>
<p>
- Here we circle back to the division by zero problem, though this time while extending functions. Let us begin with the two functions, <RT·math>f(t) = 2^t - 32</RT·math> and <RT·math>g(u) = u - 15</RT·math>. Both have <RT·math>D_0</RT·math> vectors, though the one for the exponential function is defined by a machine rather than by a value on a tape.
+ Further suppose that the quantification is over an infinite input space. Then the only option for answering a question about the properties of results from such a machine is to analyze it. This is second order analysis, also called simply <RT·term>analysis</RT·term>.
</p>
<p>
- We cannot do function based arithmetic on these until we synchronize them. Suppose that for each call of the <RT·math>f</RT·math> machine, there will be three calls to the <RT·code>g</RT·code> machine. So we can make a new machine that is called once, each time <RT·math>f</RT·math> is called, <RT·math>g(t) = 3t - 15</RT·math>.
+ Famously, we know that universally an analyzer cannot determine if a machine is computational. This knowledge was derived by reasoning about the properties of a hypothetically existing analyzer machine. This is a third order analysis activity.
</p>
<p>
- Now suppose we want to express the quotient of these two functions.
+ As an analyzer does not run the machine being studied, it is not required to be a machine that halts. Suppose we have a machine that produces an infinite sequence of digits to a tape without halting. A limit analyzer could examine that machine and, in some cases, determine if it has asymptotic behavior. For example, recognizing that appending a binary fractional sequence of <RT·math>0.1111...</RT·math> indefinitely evaluates in the limit to <RT·math>1.0</RT·math>. In this manner, the use of analyzers facilitates using computation theory for deriving higher order mathematics.
</p>
- <RT·math>
- h(t) = \frac{f(t)}{g(t)} = \frac{2^t - 32}{3t - 15}
- </RT·math>
- <img src="singularity_plot.png" class="rt-diagram" alt="Figure singularity plot">
+ <h2>Second order Arithmetic</h2>
+ <p>
+ In their 1990 paper, "Exact Real Arithmetic: Formulating Real Numbers as Functions," Hans-Juergen Boehm and Robert Cartwright presented a system using Turing Machines to represent 'constructive real numbers', or exact real arithmetic. This directly implements a core concept from Alan Turing's 1936 paper of the "computable number." <RT·endnote>Hans-J. Boehm and Robert Cartwright, "Exact Real Arithmetic: Formulating Real Numbers as Functions," in <em>Design and Implementation of Symbolic Computation Systems</em> (Berlin: Springer, 1990), 43 52.</RT·endnote> Boehm translated this computation theory into a practical software architecture. Instead of storing a real number as a fixed width floating point approximation, Boehm's system represents a real number as a lazy evaluation function.
+ </p>
<p>
- Evaluating this form in the first order at the point <RT·math>t=5</RT·math> returns a <RT·code>(divide 0 0)</RT·code>. It is a strange thing, because when we plot <RT·math>h(t)</RT·math> values, against <RT·math>t</RT·math> there is an obvious correct value on the curve at <RT·math>t=5</RT·math>. There is no special feature that would make it questionable.
+ Let us employ Boehm and Cartwright's constructive real numbers, though in <RT·term>second order arithmetic</RT·term> rather than a lazy evaluation system. Because we are doing second order arithmetic, our programs need not be in the form of generators that yield a new value when called, but can be arbitrary programs containing complex control logic. If run, they evaluate physical conditions and produce results. As we are in the second order, however, we will not run them, but rather analyze and manipulate their structural logic.
</p>
<p>
- Suppose we move to a second order evaluation, and for places the computation does not work, we return the machine that isn't evaluating. That is similar to what we did to get <RT·code>(divide 0 0)</RT·code>, but the zeros in that expression came from a prior step first order evaluation. Let us instead give the quotient composer two machines to compose, and have it return a value when reduction to the first order is possible, and return the full problem as posed to it when it can not be reduced.
+ Consider an illustrative example. We start by defining a fundamental language for communicating with the head and tape transport unit. The statements we communicate to our tape transport unit will be explicit physical instructions: <RT·code>(step)</RT·code>, <RT·code>(left)</RT·code>, <RT·code>(read)</RT·code>, and <RT·code>(write symbol)</RT·code>. In the first order, evaluating these instructions physically actuates the tape.
</p>
- <table>
- <thead>
- <tr>
- <th>Count (<RT·math>t</RT·math>)</th>
- <th>Quotient (<RT·math>f(t)/g(t)</RT·math>)</th>
- <th>1st Diff Ratio (<RT·math>δ^1 f(t) / δ^1 g(t)</RT·math>)</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>0</td>
- <td>31/15</td>
- <td>1/3</td>
- <td></td>
- </tr>
- <tr>
- <td>1</td>
- <td>30/12</td>
- <td>2/3</td>
- <td></td>
- </tr>
- <tr>
- <td>2</td>
- <td>28/9</td>
- <td>4/3</td>
- <td></td>
- </tr>
- <tr>
- <td>3</td>
- <td>24/6</td>
- <td>8/3</td>
- <td></td>
- </tr>
- <tr>
- <td>4</td>
- <td>16/3</td>
- <td>16/3</td>
- <td></td>
- </tr>
- <tr>
- <td>5</td>
- <td><RT·code>(divide f(5) g(5))</RT·code></td>
- <td>32/3</td>
- <td></td>
- </tr>
- <tr>
- <td>6</td>
- <td>32/3</td>
- <td>64/3</td>
- <td></td>
- </tr>
- <tr>
- <td>7</td>
- <td>96/6</td>
- <td>128/3</td>
- <td></td>
- </tr>
- <tr>
- <td>8</td>
- <td>224/9</td>
- <td>256/3</td>
- <td></td>
- </tr>
- <tr>
- <td>9</td>
- <td>480/12</td>
- <td>512/3</td>
- <td></td>
- </tr>
- </tbody>
- </table>
+ <p>
+ Suppose our goal is to subtract 3 from 5 in the second order. Consider a Turing Machine representation named <RT·code>five</RT·code> that outputs the number 5, i.e., it prints to the tape, 'sssss', using unary notation. Let us assume that the tape is initially empty and that the empty symbol '□' terminates the string. To preserve the code for second order analysis, we define the program's Abstract Syntax Tree (AST) as a quoted <RT·code>progn</RT·code> block. This block can contain any native Lisp control structures, though for this generator it is a simple sequence:
+ </p>
+
+ <RT·code>
+ (defparameter *five-ast*
+ '(progn
+ (write s) (step)
+ (write s) (step)
+ (write s) (step)
+ (write s) (step)
+ (write s) (step)
+ ))
+ </RT·code>
+
+ <p>Similarly, the AST for the number 3:</p>
+
+ <RT·code>
+ (defparameter *three-ast*
+ '(progn
+ (write s) (step)
+ (write s) (step)
+ (write s) (step)
+ ))
+ </RT·code>
<p>
- The quotient machine, <RT·code>(divide f(5) g(5))</RT·code>, is no more representative of the value we expected here, than was <RT·code>(divide 0 0)</RT·code>. However, as we have the whole program, we could pass it to a L'Hôpital evaluator and get a value back. But I am wondering if we can't design function extension form that extends to the value and beyond without any hiccups, as most apparently there is a value to extend to.
+ If we were to dynamically evaluate these ASTs in the first order, the Hindu-Arabic representation of each number would be physically written in unary code on the tape.
</p>
<p>
- I included the first difference along with the evaluation results in the table above. When building a first difference table an interesting thing happens at the singularity, the first difference and the function itself coincide, note calls 4 and 5. This makes sense because <RT·math>δ f(4) = f(5) - f(4)</RT·math>, which becomes <RT·math>δ f(4) = 0 - f(4)</RT·math>, and the same happens to <RT·code>g</RT·code> in the denominator, so the signs cancel. Note also <RT·math>δ f(5) = f(6) - f(5)</RT·math>, which becomes <RT·math>δ f(5) = f(6) - 0</RT·math>.
+ For the second order difference operation, we compose the ASTs to create a new program. Here we extract the body of the second operand's AST and substitute every <RT·code>(write s)</RT·code> followed by a <RT·code>(step)</RT·code> instruction with an inverted pair: a <RT·code>(left)</RT·code> instruction followed by writing the empty symbol <RT·code>(write □)</RT·code>.
</p>
- <p>Ostensibly it looks like we have happened upon a discrete version of L'Hôpital's rule, that we merely use the first difference quotient instead of the function quotient at the singularity. But alas, the coincidence occurs at <RT·math>h(4)</RT·math> rather than at <RT·math>h(5)</RT·math>. For the second coincidence, we find <RT·math>h(6)</RT·math> as the 5th first finite difference. In neither case did we learn anything about the value of <RT·math>h(5)</RT·math>.
+ <RT·code>
+ (defun invert-direction (cmds)
+ (cond
+ ((null cmds) nil)
+ (
+ (and
+ (cdr cmds)
+ (equal (first cmds) '(write s))
+ (equal (second cmds) '(step)))
+ (append
+ '((left) (write □))
+ (invert-direction (cddr cmds))
+ ))
+ (
+ t
+ (cons
+ (first cmds)
+ (invert-direction (cdr cmds))
+ ))))
+
+ (defun primitive-compose-subtract (ast-a ast-b)
+ (let
+ (
+ (body-a (rest ast-a))
+ (body-b (rest ast-b))
+ )
+ `(progn
+ ,@body-a
+ ,@(invert-direction body-b)
+ )))
+ </RT·code>
+
+ <p>We pass our two natural number ASTs to this composer, and we get as a result a newly synthesized AST.</p>
+
+ <RT·code>
+ (defparameter
+ *primitive-five-minus-3-ast*
+ (primitive-compose-subtract *five-ast* *three-ast*)
+ )
+
+ ;; *primitive-five-minus-3-ast* evaluates to:
+ ;; '(progn
+ ;; (write s) (step)
+ ;; (write s) (step)
+ ;; (write s) (step)
+ ;; (write s) (step)
+ ;; (write s) (step)
+ ;; (left) (write □)
+ ;; (left) (write □)
+ ;; (left) (write □)
+ ;; )
+ </RT·code>
+
+ <p>
+ I called this 'primitive' because it is missing the simplification. This sort of simplification is also known as compiler code optimization. Optimizations can be operation specific, or general in nature. Here we will send the primitive composition's body to an analyzer which scans the steps and removes the redundant operations.
</p>
<p>
- Perhaps if we were performing nonstandard analysis, and extending by increments of epsilon in the domain at each step, the nonstandard analysis naturalization function would find that the differential value can be used as the value at the singularity. But here we are discussing finite differences <RT·endnote>H. Jerome Keisler, <em>Elementary Calculus: An Infinitesimal Approach</em>, 2nd ed. (Boston: Prindle, Weber and Schmidt, 1986).</RT·endnote>. We are not doing a local analysis here so as to derive a principle, but rather are trying to evaluate a function in the macro view.
+ Because of the purity of the Lisp syntax, we are able to present a remedial simplifier example here. A <RT·code>(step)</RT·code> followed by a <RT·code>(left)</RT·code> annihilate each other, resulting in zero net movement of the tape head. Similarly, on an initially empty tape, a <RT·code>(write s)</RT·code> followed immediately by overwriting with the empty symbol <RT·code>(write □)</RT·code> annihilate each other.
</p>
<p>
- So then, perhaps we set the value at the singularity to <RT·math>x</RT·math> build out the difference table, then solve for <RT·math>x</RT·math>?
+ For a program containing branched control logic, the simplifier would require a deep recursive walk of the AST to ensure operations aren't annihilated across conditional boundaries. For our explicit natural number generators, a linear scan of the <RT·code>progn</RT·code> body suffices. It calls itself recursively until the scan fails to find any further reductions, returning its optimized AST.
</p>
- <img src="with_x_variable.png" class="rt-diagram" alt="Figure with x variable.">
+ <RT·code>
+ (defun remove-annihilations (cmds)
+ (cond
+ ((null cmds) nil)
+ (
+ (and
+ (cdr cmds)
+ (equal (first cmds) '(step))
+ (equal (second cmds) '(left)))
+ (remove-annihilations (cddr cmds)))
+ (
+ (and
+ (cdr cmds)
+ (equal (first cmds) '(write s))
+ (equal (second cmds) '(write □)))
+ (remove-annihilations (cddr cmds)))
+ (
+ t
+ (cons
+ (first cmds)
+ (remove-annihilations (cdr cmds))
+ ))))
+
+ (defun simplify-machine (ast)
+ (let*
+ (
+ (cmds (rest ast))
+ (reduced (remove-annihilations cmds))
+ )
+ (if
+ (equal reduced cmds)
+ ast
+ (simplify-machine `(progn ,@reduced))
+ )))
+ </RT·code>
- <p>
- As Newton pointed out, we can know the differences on the <RT·math>D_0</RT·math> row of the table from the quotient function:
- </p>
+ <p>After giving the difference program to the simplifier, we get:</p>
- <RT·math>
- D_{0,n} = \sum_{k=0}^n (-1)^{n-k} \binom{n}{k} \frac{f(k)}{g(k)}
- </RT·math>
+ <RT·code>
+ (defparameter
+ *simplified-five-minus-3-ast*
+ (simplify-machine *primitive-five-minus-3-ast*)
+ )
+
+ ;; *simplified-five-minus-3-ast* evaluates to:
+ ;; '(progn
+ ;; (write s) (step)
+ ;; (write s) (step))
+ </RT·code>
+
+ <p>This technique of composing Turing Machine programs in the presence of simplification is closely related to that of symbol computation and expression simplification that we find in math tools such as Mathematica. We can imagine our Turing Machines as functions with names, and then symbolic computation leaves them unevaluated as here. Then the Mathematica <RT·code>Simplify</RT·code> is the compiler optimization simplifier as above. A difference in these two systems is that of the functions being reduced to Turing Machine head and tape transport unit instructions.</p>
+
+ <h2>The multiplicative inverse of the additive identity</h2>
<p>
- For <RT·math>k=5</RT·math> the difference evaluates to be <RT·code>(divide 0 0)</RT·code>, as it did for the quotient, and it also fails for all values above <RT·math>k=5</RT·math> due to the summation. So we can not use this to solve for <RT·math>x</RT·math>.
+ Generally it is more efficient to perform arithmetic in the first order. Also, it is easier to write Turing Machine control programs if we take Natural Number arithmetic as already available, say, from a subroutine library. On processors fixed word length arithmetic is built into the hardware. Where second order computation becomes useful is in places where a result cannot be computed in the first order.
</p>
<p>
- As the second order evaluation returned <RT·code>(divide f(k)|k=5 g(k)|k=5)</RT·code>, we can use L'Hôpital evaluator to resolve this indeterminate form <RT·math>D_{0,5}</RT·math>. Note this is during the creation of the <RT·math>D_0</RT·math> vector, so it is part of the simplification step, rather than being part of the evaluation of the quotient. The result will thus be used in producing an extension machine that extends through the singularity, so the user will not see any exceptional higher order values while extending <RT·math>h(t)</RT·math>.
+ The IEEE 754 floating point standard includes error algebra tags. These are explored in depth by Popova, Evgenija D. "On a Formally Correct Implementation of IEEE Computer Arithmetic." Journal of Universal Computer Science 1, no. 7 (1995): 560 569. This is a form of second order arithmetic, because each of the error tags is standing in for a machine that did not complete. When a computation fails to result in a first order object, this becomes a gateway for replacing the result with a second order object.
</p>
<p>
- The L'Hôpital evaluator will discover that a transcendental constant is required. It will be returned as a higher order object, a machine definition, because the value can not be written to the tape.
+ Let us take the multiplicative inverse of the additive identity as an example, AKA division by zero. In the second order, when we attempt to compute a division, say 6/0, the machine will not reduce.
</p>
- <RT·math>
- T = \frac{32 \ln(2)}{3}
- </RT·math>
+ <RT·code>
+ (divide 6 0)
+ </RT·code>
- <p>The resulting <RT·math>D_0</RT·math> vector for the quotient is:</p>
+ <p>Now consider the compiler optimization like simplification of this expression:</p>
- <img src="D_10.png" class="rt-diagram" alt="Figure D_0 h(t)">
+ <RT·code>
+ (divide (divide 6 0) (divide 3 0))
+ </RT·code>
- <img src="h_of_t_D_table.png" class="rt-diagram" alt="Figure D table for h(t)">
+ <p>The result will be:</p>
+
+ <RT·code>
+ (multiply 2 (divide 0 0))
+ </RT·code>
<p>
- I find it fascinating to watch the transcendental difference values march down the table, then all cancel out after <RT·math>h(5)</RT·math> is generated.
+ There is no special help here. Hence, <RT·code>(divide 0 0)</RT·code> remains as an error flag.
</p>
<p>
- So the value of <RT·math>h(5)</RT·math> is transcendental, but all the machinery we were using, up until using L'Hôpital evaluator, was closed over the rational field. That is why no matter how we tried to organize the difference tables, there was no way to find <RT·math>h(5)</RT·math>, or to find a <RT·math>D</RT·math> table for extending through it. This situation occurred due to introducing the <RT·math>2^t</RT·math> function. Had we remained with finite length <RT·math>D</RT·math> tables, i.e. finite degree polynomials, we could have used deconvolution of the <RT·math>D_0</RT·math> vectors, or synthetic division on the polynomials, to create the quotient.
+ The divide machine cannot be run to produce a value, as any value from the field assigned to it would lead to contradictions. Stated more precisely, for <RT·math>x · y = q</RT·math> operations, when given an <RT·math>x</RT·math> and a <RT·code>q</RT·code> value, there is only one possible <RT·math>y</RT·math> value, and it can be recovered with <RT·math>q/x</RT·math>. However, when <RT·math>x</RT·math> is zero, and only when it is zero, <RT·code>q</RT·code> is solely determined by <RT·math>x</RT·math> independent of <RT·math>y</RT·math>, so <RT·math>y</RT·math> is ignored; it could be any value. There is no way to recover it from <RT·math>q/x</RT·math>.
</p>
+ <h2>Evaluating and Extending</h2>
- <p>And for the victory lap, evaluation versus higher order computation and function extension:</p>
+ <p>
+ When a machine is 1) programmed to perform a function <RT·math>f</RT·math>, 2) has its tape initialized with a constant input value, say <RT·math>t</RT·math> (which can be a vector of values), and 3) is then run, writing the result <RT·math>f(t)</RT·math> and halting; we say that the run <RT·term>evaluates</RT·term> the function at <RT·math>t</RT·math>.
+ </p>
- <table>
- <thead>
- <tr>
- <th>Count (<RT·math>t</RT·math>)</th>
- <th>Direct Evaluation (<RT·math>f(t)/g(t)</RT·math>)</th>
- <th>Extended Quotient Vector (<RT·math>H(t)</RT·math>)</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>0</td>
- <td>31/15</td>
- <td>31/15</td>
- </tr>
- <tr>
- <td>1</td>
- <td>30/12</td>
- <td>30/12</td>
+ <p>
+ When an evaluator erases the input and leaves only the result in its place, we say it is a substitution evaluator. It is an inconsequential modification to add multiple tapes each with their own tape head to a Turing Machine, so it is possible that an evaluator will have a dedicated tape for the input, a dedicated tape for the output, and have zero or more additional tapes for scratch work. In which case, instead of substitution, one tape is mounted as an input, the machine is run until it halts, and then separately the result tape can be unmounted to potentially be used in a downstream computation.
+ </p>
+
+ <p>
+ In a variation of analytical evaluation, we can instead mount an initial value for a function, and call a machine to repeatedly <RT·term>extend</RT·term> the function. To state this more precisely, when a machine tape is given the initial value of <RT·math>f(t_i)</RT·math> on its input tape, and then when run until it halts having written the result <RT·math>f(t_{i+1})</RT·math> to the tape, we say that running the machine extends the function. Typically extension machines are designed so they can be used recurrently to extend the function indefinitely, thus producing a sequence of function values, <RT·math>f(t_0), f(t_1), f(t_2), \ldots</RT·math>.
+ </p>
+
+ <p>
+ The difference between adjacent function values in the above sequence is called the first finite difference. The forward first difference is defined as:
+ <RT·math>δ_i = f(t_{i+1}) - f(t_i)</RT·math>.
+ While the backwards first difference is
+ <RT·math>δ_i = f(t_i) - f(t_{i-1})</RT·math>.
+ If 'forward' or 'backwards' is not specified, then the difference is taken to be 'forward'. Hence, extending a function is identical to adding the first difference. Thus a sequence of function values implies a sequence of first differences.
+ </p>
+
+ <p>
+ A second difference can be defined as the difference between two adjacent first differences. A second difference can be added to a prior first difference to create the next first difference, which could then be added to a prior function value to extend the function to its next value. This pattern can be extended to any number of levels.
+ </p>
+
+ <p>
+ The mechanical application of finite differences was famously leveraged by Charles Babbage in the design of his Difference Engine. By examining the discrete changes between consecutive outputs of a polynomial function, a person can observe that the higher order differences eventually reduce to a constant. This structure allows the computation of subsequent sequential values using only addition, bypassing the need for complex multiplication machinery.
+ </p>
+
+ <p>
+ Let us construct a forward difference pyramid for the polynomial <RT·math>f(t) = t^2</RT·math>.
+ </p>
+
+ <table>
+ <thead>
+ <tr>
+ <th>Count (<RT·math>t</RT·math>)</th>
+ <th><RT·math>f(t) = t^2</RT·math></th>
+ <th>1st Diff (<RT·math>δ^1</RT·math>)</th>
+ <th>2nd Diff (<RT·math>δ^2</RT·math>)</th>
</tr>
+ </thead>
+ <tbody>
<tr>
+ <td>0</td>
+ <td>0</td>
+ <td>1</td>
<td>2</td>
- <td>28/9</td>
- <td>28/9</td>
</tr>
<tr>
+ <td>1</td>
+ <td>1</td>
<td>3</td>
- <td>24/6</td>
- <td>24/6</td>
+ <td>2</td>
</tr>
<tr>
+ <td>2</td>
<td>4</td>
- <td>16/3</td>
- <td>16/3</td>
- </tr>
- <tr>
<td>5</td>
- <td><RT·code>(divide 0 0)</RT·code></td>
- <td><RT·math>\frac{32 \ln(2)}{3}</RT·math></td>
- </tr>
- <tr>
- <td>6</td>
- <td>32/3</td>
- <td>32/3</td>
+ <td>2</td>
</tr>
<tr>
+ <td>3</td>
+ <td>9</td>
<td>7</td>
- <td>96/6</td>
- <td>96/6</td>
- </tr>
- <tr>
- <td>8</td>
- <td>224/9</td>
- <td>224/9</td>
+ <td></td>
</tr>
<tr>
- <td>9</td>
- <td>480/12</td>
- <td>480/12</td>
+ <td>4</td>
+ <td>16</td>
+ <td></td>
+ <td></td>
</tr>
</tbody>
</table>
- <h2>Chapter discussion</h2>
+ <p>
+ In order to design for indefinite extension, the Turing Machine tape must retain the function value alongside with its forward differences. Because the second difference is constant higher order differences are all zero. Hence the tape requires a three component vector to hold the differences.
+ </p>
- <p>The fundamental claim of computational analysis is that all functions can be viewed as discrete entities. Take this simple function:</p>
+ <p>
+ Here is the sequence of tape states as the machine is repeatedly called to extend the function from its initial conditions at <RT·math>t = 0</RT·math>. During each step, the machine adds <RT·math>δ^1</RT·math> to <RT·math>f</RT·math>, and <RT·math>δ^2</RT·math> to <RT·math>δ^1</RT·math>, as there is no <RT·math>δ^3</RT·math>, it is taken to be zero, so <RT·math>δ^2</RT·math> is merely copied down.
+ </p>
<RT·code>
- f(t) = t^3
+ Initial Tape (t=0): [0, 1, 2]
+ Call 1 (t=1): [1, 3, 2]
+ Call 2 (t=2): [4, 5, 2]
+ Call 3 (t=3): [9, 7, 2]
+ Call 4 (t=4): [16, 9, 2]
</RT·code>
- <p>It is said to be a continuous function over the real field. However, from a computational perspective, it is a string of 8 discrete symbols. A person might ask then, "If the function is not continuous, then how can a person take a derivative?" Often people view a derivative as a tangent line to a curve drawn on a plot. The computational analyst's answer is to use a machine that manipulates the symbols; it will move the 3 down in front of the <RT·math>t</RT·math>, decrement the power, and write 2. Those are all discrete operations.</p>
-
- <p>The point of this chapter is to state that computational analysis exists, rather than to present a comprehensive computational analysis system. If a person is looking for practical systems for extending functions that expand precision, or for second order computation, there are options.</p>
-
- <p>Hans J. Boehm and Robert Cartwright's work of expanding the precision of a computation on demand continued to mature. Boehm, while working at Google, authored a Java constructive reals library that powers the bundled Android calculator. When a person types in an expression, the system builds an Abstract Syntax Tree (AST) of the expression. The display interface determines how many digits fit on the screen and demands exactly that much precision from the root of the tree. The root function then recursively demands progressively higher precision from its operand functions until it achieves the strict error bounds required to guarantee every digit displayed on the screen is mathematically correct <RT·endnote>Hans J. Boehm, "Small data computing: Correct calculator arithmetic," Google Research, 2015. This library uses constructive real arithmetic to guarantee fully accurate results through demand driven evaluation.</RT·endnote>.
- </p>
-
- <p>Wolfram Mathematica utilizes a true second order computation system by keeping expressions in their exact symbolic form for as long as possible. An expression like <RT·code>Sqrt[2] + Pi</RT·code> remains an AST of symbols. It then relies on an extensive library of algebraic rewrite rules to simplify the tree. However, unlike Boehm and Cartwright's method of pulling precision through back propagating extension calls, when a numeric result is required, Mathematica runs the computation forward using significance arithmetic. Should there be insufficient precision at the result, it employs an adaptive retry loop <RT·endnote>Fredrik Johansson, "The significance of arithmetic," 2008. Mathematica implements significance arithmetic by approximating numbers as a floating point value with an attached error estimate, allowing dynamic adaptation and iteration if precision falls below the required threshold. See also: Wolfram Language Documentation, "Arbitrary Precision Numbers" (https://reference.wolfram.com/language/tutorial/ArbitraryPrecisionNumbers.html).</RT·endnote>.
+ <p>
+ For Call 1, the input tape is the Initial Tape. In Call 1 the machine will take the function value on the input tape, 0, and add the first difference, 1, writing the result 1, to the result tape. The machine will then continue on and take the first difference on the input tape, 1, and add the second difference on the input tape, 2, writing the result first difference to the result tape, 3. Then finishing up, the machine will not find a third difference on the input tape, so it will copy the second difference found on the input tape as the second difference for the result tape.
</p>
- <p>The approach I used differs from both of these approaches. Like them, the first step is to build the AST, but then the tree is evaluated in two phases. The first phase analyzes the tree, while creating error propagation expressions either per computation step or for atomic computation blocks. These take the form of symbolic expressions with error <RT·math>\epsilon</RT·math> in and error <RT·math>δ</RT·math> out, over the domain of the input. Then instead of back propagation of precision requests at run time, there is a back propagation of precision requirements at analysis time. This approach was used on the AMD K5 processor transcendental microcode to guarantee that computations yielded specified accuracy. The method is also well suited for setting the bus widths for application specific processors, because the analysis step can occur once at design time. <RT·endnote>Thomas Walker Lynch, A. Ahmed, M. Schulte, T. Callaway, and R. Tisdale, "The K5 Transcendental Functions," <em>Proceedings of the 12th IEEE Symposium on Computer Arithmetic</em>, 1995. DOI: 10.1109/ARITH.1995.465368.</RT·endnote> <RT·endnote>Thomas Walker Lynch, "Numerical Analysis of Computer Approximations," September 2018. DOI: 10.13140/RG.2.2.10906.49601. License CC BY 4.0. https://www.researchgate.net/publication/329402329_Numerical_Analysis_of_Computer_Approximations</RT·endnote>.
+ <p>
+ For Call 2, the input tape is the result tape from Call 1. Execution then proceeds identically to Call 1. This pattern repeats for all successive calls. A person can see the values of <RT·math>t^2</RT·math> as the first number on each result tape. Any polynomial can be computed in this manner, and the only ALU required is an adder that is also capable of subtraction.
</p>
- <p>None of these computation systems, that of Boehm and Cartwright, Mathematica, nor my error analysis approach, makes use of the IEEE 754 standard floating point arithmetic. Rather they all require the use of variable precision. In the case of Boehm and Cartwright's this occurs through the serialization implied through lazy calls for more precision. In the case of Mathematica it is explicit in the significance arithmetic. In my error analysis approach, the means for variable precision was the High Radix Online Arithmetic <RT·endnote>Thomas W. Lynch, "High Radix On Line Arithmetic for Credible and Accurate Computing," Real Numbers and Computers, École des Mines de Saint Étienne, France, 1995, pp. 78 89.</RT·endnote> <RT·endnote>Thomas W. Lynch and Michael J. Schulte, "Software for High Radix On Line Arithmetic," Reliable Computing, vol. 2, no. 2, 1996, pp. 133 138. DOI: 10.1007/BF02425915.</RT·endnote>.
- </p>
+ <h3>Lemma: Only row 0 is needed</h3>
- <p>Hence what would be useful for a replacement of the IEEE 754, would be a variable precision number standard. There is one being proposed by John Gustafson called the Posit number representation <RT·endnote>John L. Gustafson, <em>The End of Error: Unum Computing</em>, Chapman and Hall/CRC, 2015. Posits serve as a hardware friendly alternative to IEEE 754 floating point numbers, providing variable precision mechanics.</RT·endnote>.
+ <p>
+ The extension function can be called any number of times when the initial tape is identical to row 0 of the table of finite differences. No other row is needed from the table of finite differences.
</p>
- <p>This section reviewed finite difference methods as a means to implement function extensions. A person should notice that finite differences are one among many methods for extending functions, and they are not always the best choice. In modern computing it is not common to implement function extensions. Rather it is more common to increment a value across a domain, while completely repeating the evaluation for each new domain value, for example when computing values to place on a plot. However, independent of their importance in applied computing, function extension and difference methods are of theoretical importance for analysis, particularly when applied to nonstandard analysis. These concepts speak directly to the thesis of computational naturalism—deriving mathematics from computational logic—rather than merely proposing practical methods for implementing software.
+ <p>
+ This follows from the mechanical procedure given above for computing the next row of the table from any given row.
</p>
- <RT·chapter>Unsigned integer representation</RT·chapter>
-
- <p>Charles Burnett published a very interesting book about the translation of Hindu-Arabic mathematics in Europe during the Middle Ages <RT·endnote>Charles Burnett, <em>Numerals and Arithmetic in the Middle Ages</em> (Farnham: Ashgate Variorum, 2010). DOI: 10.33137/aestimatio.v9i0.25990.</RT·endnote>. He describes a 9th-century treatise on Hindu-Arabic arithmetic authored by Muḥammad ibn Mūsā al-Khwārizmī, where "al-Khwārizmī" indicated he was from Khwarazm in Central Asia. When this manuscript was translated into Latin in the 12th century, European translators approximated his name as Algoritmi or Algorismi, thus giving us the word algorithm. He notes that the Arabs called such numbers "Indian Numbers". Another chapter, "Why we read Hindu-Arabic numerals backwards," shows that the <RT·term>little-endian</RT·term> and <RT·term>big-endian</RT·term> debate that has plagued programmers for decades has its origin in the Middle Ages <RT·endnote>Danny Cohen, "On Holy Wars and a Plea for Peace," <em>IEEE Computer</em> 14, no. 10 (October 1981): 48-54. DOI: 10.1109/C-M.1981.220208</RT·endnote>.</p>
-
- <p>The information about ENIAC in this chapter is found in a 1947 IRE article, "Electronic Computing Circuits of the ENIAC" by Arthur W. Burks <RT·endnote>Arthur W. Burks, "Electronic Computing Circuits of the ENIAC," <em>Proceedings of the IRE</em> 35, no. 8 (August 1947): 756-767. DOI: 10.1109/JRPROC.1947.234265</RT·endnote>. I found it interesting that some of the design issues for flip-flops made of vacuum tubes resemble those of designing static RAM cells in CMOS. Although ENIAC's implementation is electronic, its architecture is fundamentally that of a mechanical machine.</p>
-
- <h2>Hindu-Arabic Numbers</h2>
-
- <p>An Hindu-Arabic number consists of a sequence of digits, <RT·math>d_0 d_1 d_2 \ldots</RT·math> where, in base 10, each digit has a value ranging from 0 to 9. In this section, these sequences are represented using sequence notation. For example, <RT·math>X = [7, 8, 9]</RT·math> is a sequence with three components. Its zero index component, <RT·math>x_0</RT·math>, is 7, etc. Note that <RT·math>X</RT·math> is written as a capital letter to denote that it is a container, while its individual members use small letters.</p>
-
- <p>A person interprets these sequences as numbers by using a weighted sum.</p>
-
- <div style="margin-left: 2em;">
- <RT·math>a = \sum_{i=0}^n x_i \cdot ten^i</RT·math>
- </div>
-
- <p>Here <RT·math>a</RT·math> is a numeric value, so it is written in lower case. Each <RT·math>x_i</RT·math> is the <RT·math>i</RT·math>th component of the sequence <RT·math>X</RT·math>. The value <RT·math>x_i</RT·math> is also known as the '<RT·math>i</RT·math>th digit' of the number. The value ten is called the base or the radix of the number. It is fortunate that ten is so well known that it has a name, because otherwise an author is tempted to write the base while using the exact representation that he is trying to define. Although this function gives numeric meaning to our digit sequences, performing the suggested computation is pointless. The result is a number, and a person must represent that number, resulting in the exact sequence that was given as the input to the function in the first place.</p>
-
- <p>In many contexts, a person drops the container notation without causing ambiguity. Conventionally, dropping the notation reverses the order of the components. The sequence from the prior example, <RT·math>X = [7, 8, 9]</RT·math>, becomes <RT·math>x = 987</RT·math>. To be clear, note that this number is 13 away from a thousand.</p>
-
- <p>Independent of whether the number is represented with or without the bracket decoration, its most significant digit is the one with the greatest weight. In this example, it is the digit with the value 9, i.e., <RT·math>x_2</RT·math>. Analogously, the least significant digit is 7, i.e., <RT·math>x_0</RT·math>. Using a term from the previous chapter on arrays, the extent of our example number is 2. The digit count, or length, of this number is 3.</p>
-
- <h2>Meaning of the word digital</h2>
-
- <p>The first computing machines which performed discrete state computation made use of ten symbols for a digit’s potential values while mimicking how humans perform manual arithmetic. Examples include Pascal’s calculator, Babbage’s machine, the mechanical calculators that came after, Aiken’s Mark computers, and ENIAC.</p>
-
- <p>Mechanical machines used physical gears with ten positions, each being 36 degrees apart. An index, such as a metal pointer, pointed at one of the marks, indicating which digit value had been registered on the gear. To change which digit value was registered, the gear turned until the index pointed at the mark corresponding to the desired digit value. Instead of gears, the electronic computer ENIAC used circular shift registers of ten vacuum tube flip-flops. These were called ring counters or decade counters. ENIAC operated on ten-digit signed numbers, so there were ten such ring counters plus a flip-flop for each number. It is structurally important that a person does not conflate the number of available digit values with the length of the digit sequence.</p>
-
- <p>Mechanical machines, such as Howard Aiken’s Mark machines, gated rotational momentum with control linkages and clutches. ENIAC used an electronic analogy to the main rotating shaft: a central pulse clock. Pulses were sent to an electronic gate, and if the control to that gate allowed it, the pulses passed through to the corresponding ring counters to cause each flip-flop in the ring to flip in succession depending on the pulse count.</p>
-
- <p>To add digit values on a mechanical computer, the two gears in question were mechanically connected. When one rotated back to zero, the other rotated up to the sum, possibly tipping a carry bar along the way. On ENIAC, one ring counter gated pulses to its partner while counting down. While pulses were gated to it, the second ring counter counted up, optionally setting a carry flip-flop.</p>
-
- <p>Hence the term digital initially meant both being discrete and making use of ten-state logic. If a person examines the control levers of the mechanical machines, or the control signals of ENIAC, he finds two-state switch logic, but computation units processed numbers consisting of ten-state digits, mimicking human computers. Early computing work gave us more than the techniques we take for granted today; it gave us words such as gate, register, and index.</p>
-
- <p>Earlier, Claude Shannon and others wrote extensively on methods for simplifying switch logic, making it apparent that direct binary computation could be performed. The first electronic computer to do so was the Atanasoff-Berry machine developed in the late 1930s. Because there are two states in switch logic, it is maximally efficient to use a base two number system for arithmetic. In such an arithmetic system, each binary digit has the value of either zero or one. It is conventional to shorten the term binary digit to <RT·term>bit</RT·term>. John von Neumann assigned Atanasoff to audio work during World War II, so the Atanasoff-Berry machine remained unused in a basement at Iowa State University.</p>
-
- <p>Binary computation prevailed. By the 1950s almost all discrete state computers used two-state switch logic for computation. Still, the term digital continued to describe them. This leads to some curious naming conventions. For example, the company Digital Equipment Corporation, which began shipping computers in 1959, exclusively built binary computers. As another example, an engineer who studies modern digital electronics will never see wheels or decade counters used to implement operations in an ALU. Though the term digital continues to refer to computing with discrete state digits, the term has lost the connotation that there must be exactly ten of those states.</p>
+ <h3>Lemma: Evaluating call <RT·math>\omega</RT·math> requires an initial tape populated with components 0 through <RT·math>\omega</RT·math> of row 0</h3>
- <p>Today, computing based on ten-state digits, such as what is found on handheld calculators, is known as decimal computing. If a machine made use of 256-state digits, it would be neither decimal nor binary. Because we lack a special name for the number 256, a person states that such a computer uses base 256 digits and that it is a base 256 computer. Decimal computing, binary computing, and base 256 computing are all examples of digital computing. Independent of the base for computation, control signals are almost universally on or off. We say these are Boolean or binary valued, sometimes referring to their values as one or zero, but note they represent logical states rather than numbers.</p>
+ <p>
+ Let the <RT·term>extent</RT·term> refer to the maximum index for accessing a component of a vector; the extent is often denoted as <RT·math>\omega</RT·math>. Performing extensions to calculate the function value at call <RT·math>\omega</RT·math> makes use of the initial tape up to index <RT·math>\omega</RT·math>, or as many nonzero values as are available up to that extent.
+ </p>
- <p>As computer implementations moved from ten-state logic to two-state logic, it was difficult to completely abandon base ten number systems, particularly in business applications where users expect dollars and cents results that match manual computations, even when fractions and rounding are involved. Hence, IBM designed a scheme whereby groups of 4 bits represent the decimal digits; this code is called binary coded decimal or BCD. A machine which processes numbers using BCD has a decimal architecture.</p>
+ <p>
+ To make this precise, let us define the <RT·term>difference vector</RT·term> at call <RT·math>k</RT·math> as <RT·math>D_k</RT·math>. The components of this vector are <RT·math>D_{k, 0}, D_{k, 1}, \ldots</RT·math> up to the maximum available difference. The first component, <RT·math>D_{k, 0}</RT·math>, is the function value itself. The component <RT·math>D_{k, 1}</RT·math> is the first difference, and so on.
+ </p>
- <p>Most computer users never see a memory dump. Instead, they see the output of print functions. By default, print functions render numbers in a decimal format familiar to the user, independent of the internal representation used for numbers. In contemporary computing, the time it takes to convert a binary number to a decimal number is negligible compared to the time required to execute a long computation, so decimal printing is practical.</p>
+ <p>
+ Thus, this lemma claims that to compute <RT·math>D_{\omega, 0}</RT·math>, the highest component index accessed from the initial tape vector <RT·math>D_{0, i}</RT·math> will be <RT·math>i = \omega</RT·math>.
+ </p>
- <h2>Scanning-Order and Digit-Order</h2>
+ <p>
+ Note that to compute the <RT·math>D_{k, 0}</RT·math> result by the mechanical procedure given above requires adding the input tape first difference to the prior difference. That is, <RT·math>D_{k, 0} = D_{k-1, 0} + D_{k-1, 1}</RT·math>. Hence we see immediately the lemma holds for the first call where <RT·math>k = \omega = 1</RT·math>.
+ </p>
- <p>Had this book been written in Hindu-Arabic, the text would be written right to left. Let us explore what that looks like by using an example where we start with an English sentence and then reverse it. Notice that in this right to left string, the sequence [9, 8, 7] without notation is 987. Both strings match.</p>
+ <p>
+ For an arbitrary <RT·math>k</RT·math> value we discovered that the highest index required of the <RT·math>D_{k-1, i}</RT·math> vector is <RT·math>i=1</RT·math>.
+ The value <RT·math>D_{k-1, 1}</RT·math> is computed from our mechanical procedure as
+ <RT·math>D_{k-1, 1} = D_{k-2, 1} + D_{k-2, 2}</RT·math>. Given we already know that to compute the value merely requires the prior value and first difference, we find that the largest index needed from vector <RT·math>D_{k-2, i}</RT·math> is 2.
+ </p>
- <blockquote>
- Writing from right to left the sequence [7, 8, 9] without notation is 987.
- </blockquote>
+ <p>
+ There is a strict linear progression here. The computation of any component <RT·math>D_{k, i}</RT·math> inherently requires <RT·math>D_{k-1, i}</RT·math> and <RT·math>D_{k-1, i+1}</RT·math>. Therefore, each step backwards in time <RT·math>k</RT·math> expands the required index <RT·math>i</RT·math> by exactly one. By induction, computing the base value <RT·math>D_{\omega, 0}</RT·math> at call <RT·math>\omega</RT·math> requires tracing back <RT·math>\omega</RT·math> steps to the initial tape at <RT·math>k=0</RT·math>, which forces the maximum accessed index to be <RT·math>0 + \omega = \omega</RT·math>. Thus, the highest required component from the initial tape is <RT·math>D_{0, \omega}</RT·math>.
+ </p>
- <p>When reversed this becomes:</p>
+ <p>
+ The exact algebraic composition of this mechanical expansion is formalized by Newton's calculus of finite differences <RT·endnote>Isaac Newton formalized this interpolation method in 1675, later published in his <em>Methodus Differentialis</em> (1711). For a comprehensive foundational treatment, see George Boole, <em>A Treatise on the Calculus of Finite Differences</em> (Cambridge: Macmillan and Co., 1860), Chapter II.</RT·endnote>. Instead of executing the machine incrementally, a person can calculate the function value at call <RT·math>\omega</RT·math> directly as a linear combination of the initial tape components using Newton's forward difference formula:
+ </p>
- <blockquote>
- .987 si noitaton tuohtiw [9 ,8, 7] ecneuqes eht thgir ot tfel morf gnitirW
- </blockquote>
+ <p>
+ <RT·math>D_{\omega, 0} = \sum_{j=0}^{\omega} \binom{\omega}{j} D_{0, j}</RT·math>
+ </p>
- <p>Following the 1,000-year-old convention, I did not reverse the order of the digits of the number. For the reversed sentence to make sense, I changed 'tfel ot thgir' (right to left) to 'left to right', which is the sort of adaptation that Middle Ages translators did not universally apply. Also, I changed the brackets so that they still enclose the sequence components; otherwise, they point outward. When reading the number, the eye first lands on a digit of unknown weight. A person must scan further right to find the one’s place, then scan back while counting the digits to make sense of the number. Reversing the string is insufficient; a person must understand the sentence and change the phrase "right to left", to "left to right".</p>
+ <p>
+ Because the binomial coefficient <RT·math>\binom{\omega}{j}</RT·math> evaluates to exactly zero for any integer <RT·math>j > \omega</RT·math>, the summation naturally truncates at index <RT·math>\omega</RT·math>. This algebraic property perfectly mirrors the physical boundary established by the machine execution trace. Furthermore, the relationship is symmetric. A person can compute the specific components of the initial tape, <RT·math>D_{0, n}</RT·math>, directly from the sequence of evaluated function calls, <RT·math>D_{k, 0}</RT·math>, using the alternating binomial sum:
+ </p>
- <p>Now consider starting with the exact same right to left string as the early translators did, but instead of making an exception for numbers, we literally reverse the entire string:</p>
+ <p>
+ <RT·math>D_{0, n} = \sum_{k=0}^{n} (-1)^{n-k} \binom{n}{k} D_{k, 0}</RT·math>
+ </p>
- <blockquote>
- Writing from right to left the sequence [7, 8, 9] without notation is 789.
- </blockquote>
+ <p>
+ As a consequence of this lemma, we know that for any finite number of calls, i.e. finite <RT·math>\omega</RT·math>, that a finite prefix of <RT·math>D_0</RT·math> is used.
+ </p>
- <p>Now both the scan order of the sequence and the digits are identical. This maintains the property designed into the original Indian Numbers. Note, that 789 here is still thirteen away from one thousand.</p>
+ <h3>Lemma: A polynomial function of degree <RT·math>\omega</RT·math> will have a <RT·math>D_0</RT·math> vector of extent <RT·math>\omega</RT·math></h3>
- <p>To make the structure uniform, Western writers must either start writing right to left like the Arabs do, or reverse the order of digits in numbers. Computer architects implemented this latter option; for example, Intel processors utilize it.</p>
+ <p>
+ A polynomial of degree <RT·math>\omega</RT·math> is defined by a highest order term <RT·math>a_\omega t^\omega</RT·math>. The first forward difference operator, <RT·math>δ f(t) = f(t+1) - f(t)</RT·math>, inherently cancels the <RT·math>t^\omega</RT·math> term. The binomial expansion of <RT·math>(t+1)^\omega</RT·math> yields <RT·math>t^\omega</RT·math> as its leading term, which subtracts out, leaving a new polynomial of exactly degree <RT·math>\omega - 1</RT·math>.
+ </p>
- <p>In summary, this section defines two concepts: scanning-order when reading, and the digit-order of numbers. Changing the scanning order without changing the digit order effectively causes the number to be read in reverse.</p>
+ <p>
+ Taking successive finite differences reduces the degree of the polynomial by exactly one at each step. Consequently, the <RT·math>\omega</RT·math>th difference evaluates to a constant, and the next difference evaluates to exactly zero.
+ </p>
- <p>All conventional computers use low-to-high address scanning order for writing and reading text. Human operators universally find that low-to-high address scanning order for text feels natural. Relative to the low-to-high address scanning order, Big Endian computers follow the current Western ideal that the large digit comes first. In contrast, Little Endian computers are more faithful to the design of Hindu-Arabic numbers, storing numbers consistently with the scanning order matching the order of the digit weights.</p>
+ <h3>Lemma: A <RT·math>D_0</RT·math> vector of finite extent <RT·math>\omega</RT·math> generates a polynomial function of degree <RT·math>\omega</RT·math></h3>
- <p>Because a page of text is two-dimensional, there are additional writing direction conventions. However, computer memory is one-dimensional, so only the two discussed here are relevant to this architectural discussion. Programmers tasked with writing display drivers encounter the others.</p>
+ <p>
+ If an initial difference vector <RT·math>D_0</RT·math> has an extent of <RT·math>\omega</RT·math>, the sequence of function values generated by the machine corresponds to a polynomial of exactly degree <RT·math>\omega</RT·math>. A person can demonstrate this by algebraically recovering the polynomial constants, <RT·math>a_i</RT·math>, directly from the initial tape components, <RT·math>D_{0, i}</RT·math>.
+ </p>
- <h2>Binary, Octal, Hexadecimal, BCD</h2>
+ <p>
+ Consider an extent of <RT·math>\omega = 0</RT·math>, forming a polynomial of degree 0, <RT·math>f(t) = a_0</RT·math>. Evaluating at <RT·math>t = 0</RT·math> yields <RT·math>f(0) = a_0</RT·math>. Because the machine's initial value is <RT·math>D_{0,0}</RT·math>, a person establishes <RT·math>a_0 = D_{0,0}</RT·math>.
+ </p>
- <p>A person interprets a sequence of <RT·math>n</RT·math> bits as a number by using this function:</p>
+ <p>
+ When a person extends the extent to <RT·math>\omega = 1</RT·math>, the next term is added to form <RT·math>f(t) = a_0 + a_1 t</RT·math>. The first forward difference at <RT·math>t = 0</RT·math> is <RT·math>D_{0,1} = f(1) - f(0) = (a_0 + a_1) - a_0 = a_1</RT·math>. Therefore, <RT·math>a_1 = D_{0,1}</RT·math>. Note that the addition of the linear term did not change the evaluation at <RT·math>t = 0</RT·math>; the <RT·math>a_0</RT·math> constant remains firmly anchored to <RT·math>D_{0,0}</RT·math>.
+ </p>
- <div style="margin-left: 2em;">
- <RT·math>\sum_{i=0}^n x_i \cdot two^i</RT·math>
- </div>
+ <p>
+ Extending to <RT·math>\omega = 2</RT·math>, the polynomial becomes <RT·math>f(t) = a_0 + a_1 t + a_2 t^2</RT·math>. The second difference at <RT·math>t = 0</RT·math> is calculated as <RT·math>D_{0,2} = f(2) - 2f(1) + f(0)</RT·math>. Substituting the polynomial yields <RT·math>D_{0,2} = (a_0 + 2a_1 + 4a_2) - 2(a_0 + a_1 + a_2) + a_0 = 2a_2</RT·math>. Thus, a person recovers <RT·math>a_2 = D_{0,2} / 2</RT·math>. The lower coefficient then adjusts to <RT·math>a_1 = D_{0,1} - a_2</RT·math>.
+ </p>
- <p>As with sequences of decimal digits, a person drops the sequence notation to yield strings of bits. Similar to decimal digit strings, there are two options for writing the string: most-significant-digit-first, or least-significant-digit-first.</p>
+ <p>
+ Continuing to <RT·math>\omega = 3</RT·math>, the polynomial is <RT·math>f(t) = a_0 + a_1 t + a_2 t^2 + a_3 t^3</RT·math>. Evaluating the third difference <RT·math>D_{0,3}</RT·math> isolates the highest order term, yielding <RT·math>D_{0,3} = 6a_3</RT·math>. This allows immediate recovery of the highest constant: <RT·math>a_3 = D_{0,3} / 6</RT·math>.
+ </p>
- <p>Although both strings of binary digits and strings of decimal digits grow in length logarithmically when counting, binary strings grow more than twice as fast. The expansion to a length two string occurs immediately at the count of two. Length expands to three at a count of four, and then to a string of length four at a count of eight. While incrementing to eight, the binary digit string has already expanded to length four, yet for the exact same count, a decimal digit string remains one digit long.</p>
+ <p>
+ Once <RT·math>a_3</RT·math> is known, the shifting nature of the lower order constants becomes apparent through back substitution. The second difference equation expands to <RT·math>D_{0,2} = 2a_2 + 6a_3</RT·math>. Substituting the known <RT·math>a_3</RT·math> allows recovery of <RT·math>a_2 = (D_{0,2} - 6a_3) / 2</RT·math>. Similarly, the first difference equation expands to <RT·math>D_{0,1} = a_1 + a_2 + a_3</RT·math>, which resolves to <RT·math>a_1 = D_{0,1} - a_2 - a_3</RT·math>. Through all of this shifting, <RT·math>a_0 = D_{0,0}</RT·math> remains entirely unperturbed.
+ </p>
- <p>Writing such long strings is inefficient, so programmers group bits. When bits are grouped in threes, the base is eight instead of two. This is called octal notation, using one of the symbols 0, 1, 2, 3, 4, 5, 6, or 7 for the octal digit values.</p>
+ <p>
+ To observe the structural integrity of this progression, a person can array these relationships into a system of equations mapping the initial tape to the polynomial constants:
+ </p>
<table>
<thead>
<tr>
- <th>octal</th>
- <th>binary</th>
+ <th>Difference</th>
+ <th>Equation</th>
</tr>
</thead>
<tbody>
- <tr><td>0</td><td>000</td></tr>
- <tr><td>1</td><td>001</td></tr>
- <tr><td>2</td><td>010</td></tr>
- <tr><td>3</td><td>011</td></tr>
- <tr><td>4</td><td>100</td></tr>
- <tr><td>5</td><td>101</td></tr>
- <tr><td>6</td><td>110</td></tr>
- <tr><td>7</td><td>111</td></tr>
- </tbody>
- </table>
-
- <p>In the 1960s, computer panels featured switches and lights organized in threes, and coding forms were filled out in octal. Today, documents showing bit strings universally use groups of fours. Such a digit has 16 values, utilizing 0 through 9, and continuing with the letters a, b, c, d, e, and f. This is known as the hexadecimal system, often shortened to hex.</p>
-
- <table>
- <thead>
<tr>
- <th>hex</th>
- <th>binary</th>
+ <td><RT·math>D_{0,0}</RT·math></td>
+ <td><RT·math>= a_0</RT·math></td>
+ </tr>
+ <tr>
+ <td><RT·math>D_{0,1}</RT·math></td>
+ <td><RT·math>= a_1 + a_2 + a_3 + \ldots + a_\omega</RT·math></td>
+ </tr>
+ <tr>
+ <td><RT·math>D_{0,2}</RT·math></td>
+ <td><RT·math>= 2a_2 + 6a_3 + \ldots</RT·math></td>
+ </tr>
+ <tr>
+ <td><RT·math>D_{0,3}</RT·math></td>
+ <td><RT·math>= 6a_3 + \ldots</RT·math></td>
+ </tr>
+ <tr>
+ <td><RT·math>\ldots</RT·math></td>
+ <td><RT·math>\ldots</RT·math></td>
+ </tr>
+ <tr>
+ <td><RT·math>D_{0,\omega}</RT·math></td>
+ <td><RT·math>= \omega! a_\omega</RT·math></td>
</tr>
- </thead>
- <tbody>
- <tr><td>0</td><td>0000</td></tr>
- <tr><td>1</td><td>0001</td></tr>
- <tr><td>2</td><td>0010</td></tr>
- <tr><td>3</td><td>0011</td></tr>
- <tr><td>4</td><td>0100</td></tr>
- <tr><td>5</td><td>0101</td></tr>
- <tr><td>6</td><td>0110</td></tr>
- <tr><td>7</td><td>0111</td></tr>
- <tr><td>8</td><td>1000</td></tr>
- <tr><td>9</td><td>1001</td></tr>
- <tr><td>a</td><td>1010</td></tr>
- <tr><td>b</td><td>1011</td></tr>
- <tr><td>c</td><td>1100</td></tr>
- <tr><td>d</td><td>1101</td></tr>
- <tr><td>e</td><td>1110</td></tr>
- <tr><td>f</td><td>1111</td></tr>
</tbody>
</table>
- <p>The table of hexadecimal digits is twice as long as the table of octal digits because each added bit doubles the table size.</p>
+ <p>
+ A pattern emerges. Because each successive forward difference operator annihilates the lowest power of <RT·math>t</RT·math>, the resulting algebraic system is upper triangular. For any extent <RT·math>\omega</RT·math>, the <RT·math>\omega</RT·math>th difference equation reduces to <RT·math>D_{0,\omega} = \omega! a_\omega</RT·math>. This structural guarantee permits a person to reliably recover <RT·math>a_\omega = D_{0,\omega} / \omega!</RT·math>. Because an initial tape of extent <RT·math>\omega</RT·math> dictates that <RT·math>D_{0,\omega}</RT·math> is nonzero, <RT·math>a_\omega</RT·math> is guaranteed to be nonzero. All subsequent lower order constants are then systematically resolved through cascading back substitution.
+ </p>
- <p>Another common grouping is the <RT·term>byte</RT·term>. This name is word play on the term bit. Today a byte is universally a group of eight bits; however, historical computers used other lengths, ranging from six to twelve bits. Vestiges of that past remain. K&R’s "C Programming Language" leaves the length of a byte unspecified. The designers of UTF-8 sought clarity regarding groups of eight, naming such a group an octet rather than a byte.</p>
+ <p>
+ To observe the formal mechanics of this progression, a person can alternatively array these relationships into a matrix equation mapping the polynomial constants, <RT·math>a_i</RT·math>, to the initial tape differences, <RT·math>D_{0,n}</RT·math>. The coefficients of this transformation are defined by the Stirling numbers of the second kind, denoted <RT·math>S(i,n)</RT·math>:
+ </p>
- <p>When a group of eight bits, i.e., an octet, functions as a digit of a number, the arithmetic operates in base 256. Note that octet and octal are distinct concepts. An octet is a group of 8 bits, whereas octal is a number system based on digits that have values running from 0 to 7.</p>
+ <RT·math>
+ \begin{bmatrix} D_{0,0} \\ D_{0,1} \\ D_{0,2} \\ \vdots \\ D_{0,\omega} \end{bmatrix} =
+ \begin{bmatrix}
+ 0! S(0,0) & 0! S(1,0) & 0! S(2,0) & ⋯ & 0! S(\omega,0) \\
+ 0 & 1! S(1,1) & 1! S(2,1) & ⋯ & 1! S(\omega,1) \\
+ 0 & 0 & 2! S(2,2) & ⋯ & 2! S(\omega,2) \\
+ \vdots & \vdots & \vdots & \ddots & \vdots \\
+ 0 & 0 & 0 & ⋯ & \omega! S(\omega,\omega)
+ \end{bmatrix}
+ \begin{bmatrix} a_0 \\ a_1 \\ a_2 \\ \vdots \\ a_\omega \end{bmatrix}
+ </RT·math>
- <p>Groups of bits become larger. The organization of early RISC microprocessors specified that memory was always moved in groups of 32 bits, called words. Today, address variables are generally 64-bit words, while integer variables are either 32- or 64-bit words. Groups of bits found on internal buses scale larger yet.</p>
+ <p>
+ For any extent <RT·math>\omega</RT·math>, the final row of this strictly upper triangular matrix again simplifies to <RT·math>D_{0,\omega} = \omega! a_\omega</RT·math>.
+ </p>
- <p>Bits are also grouped to construct numbers with bases that are not powers of two. In the BCD code, bits are grouped in fours to form decimal digits.</p>
+ <p>
+ The main diagonal contains strictly non zero factorials, ensuring the matrix is invertible. By inverting this matrix, a person replaces the cascading back substitution with a direct, closed form equation to recover any constant <RT·math>a_i</RT·math>. The inversion utilizes the signed Stirling numbers of the first kind, denoted <RT·math>s(n,i)</RT·math> (see the Appendix on Stirling numbers).
+ </p>
+
+ <p>
+ <RT·math>a_i = \sum_{n=i}^{\omega} \frac{s(n,i)}{n!} D_{0,n}</RT·math>
+ </p>
+
+ <p>
+ Thus, extending this procedure <RT·math>\omega</RT·math> times definitively recovers the constants for exactly a polynomial of degree <RT·math>\omega</RT·math>.
+ </p>
+
+ <p>
+ This mechanical recovery of standard polynomial constants is completely analogous to Newton's interpolation formula <RT·endnote>Ibid.</RT·endnote>. Instead of resolving the standard constants <RT·math>a_i</RT·math> through an upper triangular matrix, a person can construct the polynomial directly by treating the initial tape components as the exact coefficients for a basis of binomial terms:
+ </p>
+
+ <p>
+ <RT·math>f(t) = \sum_{n=0}^{\omega} D_{0, n} \binom{t}{n}</RT·math>
+ </p>
+
+ <p>
+ Because the <RT·math>n</RT·math>th binomial coefficient expands into a polynomial of exactly degree <RT·math>n</RT·math>, and the summation is bounded by the finite extent <RT·math>\omega</RT·math> where <RT·math>D_{0, \omega}</RT·math> is definitively nonzero, the constructed function <RT·math>f(t)</RT·math> is structurally guaranteed to be a polynomial of degree <RT·math>\omega</RT·math>.
+ </p>
+
+ <h2>Difference table for an exponential function</h2>
+
+ <p>
+ Here is the table of finite differences for the function <RT·math>2^t</RT·math>.
+ </p>
<table>
<thead>
<tr>
- <th>BCD</th>
- <th>binary</th>
+ <th>Count (<RT·math>t</RT·math>)</th>
+ <th><RT·math>f(t) = 2^t</RT·math></th>
+ <th>1st Diff (<RT·math>δ^1</RT·math>)</th>
+ <th>2nd Diff (<RT·math>δ^2</RT·math>)</th>
+ <th>3rd Diff (<RT·math>δ^3</RT·math>)</th>
</tr>
</thead>
<tbody>
- <tr><td>0</td><td>0000</td></tr>
- <tr><td>1</td><td>0001</td></tr>
- <tr><td>2</td><td>0010</td></tr>
- <tr><td>3</td><td>0011</td></tr>
- <tr><td>4</td><td>0100</td></tr>
- <tr><td>5</td><td>0101</td></tr>
- <tr><td>6</td><td>0110</td></tr>
- <tr><td>7</td><td>0111</td></tr>
- <tr><td>8</td><td>1000</td></tr>
- <tr><td>9</td><td>1001</td></tr>
+ <tr>
+ <td>0</td>
+ <td>1</td>
+ <td>1</td>
+ <td>1</td>
+ <td>1</td>
+ </tr>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>2</td>
+ <td>2</td>
+ <td>2</td>
+ </tr>
+ <tr>
+ <td>2</td>
+ <td>4</td>
+ <td>4</td>
+ <td>4</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>3</td>
+ <td>8</td>
+ <td>8</td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>4</td>
+ <td>16</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
</tbody>
</table>
- <p>In BCD, some possible bit value combinations are unused. This is an unavoidable consequence of emulating a number base in binary switch logic when that base is not a power of 2.</p>
+ <p>
+ The first difference of <RT·math>2^t</RT·math> is also <RT·math>2^t</RT·math>, so row 0 of the table will have an infinite number of values. Fortunately, due to the lemma stating that evaluating call <RT·math>\omega</RT·math> requires an initial tape populated with components 0 through <RT·math>\omega</RT·math> of row 0, exactly those components are needed for the recurrence to proceed. Furthermore, when new values are needed, they are easily computed. A programmer need not store the entire infinite first row of the difference table on a static tape.
+ </p>
- <p>Octal and hexadecimal operate primarily as writing aids. Internally, the computer computes in binary, i.e., base 2. In contrast, BCD computation relies on the structural encoding boundaries. When two BCD digits are summed, the carry occurs when the sum of two digits reaches ten or more, which does not fall on a power of two boundary.</p>
+ <p>
+ Instead of attempting to write an infinite sequence to a physical tape, a person can encapsulate the generation logic within a dedicated Turing Machine. In the tradition of constructive arithmetic associated with Boehm, this generator machine acts as a virtual tape. When the primary extension machine requires the next difference component, it queries the generator machine, which computes and yields the value on demand. This lazy evaluation strategy elegantly handles functions with infinite difference sequences without exhausting finite memory.
+ </p>
- <p>Some processors lack native BCD computation, providing BCD to binary conversion operations instead. Consequently, numbers that appear as BCD in memory convert to binary before computation. Other computers, most notably many IBM machines, implement true BCD architectures featuring instructions for directly operating on BCD encoded numbers.</p>
+ <h2>Composition of recurrence functions</h2>
- <p>Although BCD encoding is less efficient, BCD encoded binary place values numbers still grow in length logarithmically while counting. Because numbers are represented in base ten, no number conversion is necessary when printing, though today this performance cost is insignificant. More importantly, BCD numbers divide by ten without generating infinite fractions.</p>
+ <p>If we desire to compose recurrence functions, in the first order or the second order, the call counts must first be synchronized.</p>
- <h2>Allocation</h2>
+ <p>Say for example, we have a machine called fizz that adds 3 to the input on the tape, and another machine called buzz that adds 5 to the input on the tape, and we wanted to compose the two machines. We would have to define what a call to the resultant machine means to the machines in composition.</p>
- <p>In most text documents, a number is written down once and remains unchanged. In contrast, a computing program frequently returns to the exact memory location and changes its value. This is equivalent to erasing an old value on a paper document and writing a new value in the same space.</p>
+ <p>If one call to the resultant machine makes the equivalent of one call to the fizz, and one to buzz, the the resultant machine can be simplified to add 8 to its input tape each time it is called. In contrast if one call to the resultant machine makes the equivalent of 5 calls to fizz, and 3 calls to buzz, then the resultant machine reduces to adding 30 to the input tape per call. The two machines are very different.</p>
- <p>Consider recording a count in real time, where the system must routinely erase the current count value and write a new one. The process begins at zero. When an event occurs, the count increments to 1. The sequence remains a single digit. The count grows to two digits in length upon reaching ten. The count grows by another digit in length upon reaching a hundred, and again at a thousand. A digit sequence representing the count grows in length against the log of the count value. The log function grows without bound, but it does so slowly, allowing relatively short digit sequences to represent exceedingly large numbers. This structural observation is central to understanding contemporary computer architecture.</p>
+ <p>Performing operations with recurrence functions requires integrating a call algebra in addition to integrating the machines. Often this is handled by considering the machines being composed to be subroutines, and the outer controller than explicitly calls them. The call algebra can then be dynamic depending on the input. Calls can be skipped due to being inside conditionals, or performed an arbitrary number of times in loops. Though simple control structures that are static are more math like, and are more likely to be more affected by optimizations (simplifications).</p>
- <p>If a paper document only provides space for a single digit count, an error occurs when the count grows to ten, escalating when it reaches a thousand. If a programmer anticipates counts in the thousands, he allocates space for four digits in advance. Such an allocation minimizes wasted space because the digit sequences for massive numbers are not significantly longer than those for small numbers.</p>
+ <h2>A pole and zero cancellation</h2>
- <p>For any allocated boundary, there remains the statistical possibility that a computed number will exceed the allocation, resulting in an overflow error. To establish rigorous allocations, a programmer must evaluate how much numbers grow via applied operations, and how many of those operations the system executes. This latter constraint ties directly to the projected lifespan of the task.</p>
+ <p>
+ Here we circle back to the division by zero problem, though this time while extending functions. Let us begin with the two functions, <RT·math>f(t) = 2^t - 32</RT·math> and <RT·math>g(u) = u - 15</RT·math>. Both have <RT·math>D_0</RT·math> vectors, though the one for the exponential function is defined by a machine rather than by a value on a tape.
+ </p>
- <p>Universally, a program cannot analyze another arbitrary program and determine how many steps it will take before stopping, or if it will ever stop. Even in specific bounded cases, the analysis is highly complex, and the programmer frequently skips it unless the code drives a life-critical application. Because data lengths expand during runtime, users operate under the threat of allocation overflow. If the allocation overflows, there is a high probability the program will stop and emit an error, or it will continue executing while outputting garbage results. This failure mode does not surprise contemporary users. The real-time computer user experiences catastrophic failure, and the error emerges during accident analysis.</p>
+ <p>
+ We cannot do function based arithmetic on these until we synchronize them. Suppose that for each call of the <RT·math>f</RT·math> machine, there will be three calls to the <RT·code>g</RT·code> machine. So we can make a new machine that is called once, each time <RT·math>f</RT·math> is called, <RT·math>g(t) = 3t - 15</RT·math>.
+ </p>
- <p>Conventional system memory is an array of allocation units called bytes. The indexes into this array are system memory addresses. Serving as the minimum addressable allocation unit defines the term byte. The number of bits in a byte is a parameter of the computer architecture, not the compiler.</p>
+ <p>
+ Now suppose we want to express the quotient of these two functions.
+ </p>
- <p>It follows that any larger allocation consists of multiple bytes. For a given allocation unit, the smallest address among the contained bytes establishes the address of the allocation unit. Each allocation unit is bounded by two parameters: its address, and its extent.</p>
+ <RT·math>
+ h(t) = \frac{f(t)}{g(t)} = \frac{2^t - 32}{3t - 15}
+ </RT·math>
- <p>Setting aside the scanning order variable, a number logically begins with its least-significant digit. This is justified because an analyst must read a number starting with its least-significant digit to derive the weights of the subsequent digits. The allocation scheme where the address of the allocation maps to the address of the least-significant digit is called <RT·term>little-endian</RT·term>. The definition of <RT·term>big-endian</RT·term> involves zero-padding to fulfill the allocation width. Relative to starting at the least-significant digit, the big-endian allocation address resolves to either the most-significant digit or the final zero in the padding.</p>
+ <img src="singularity_plot.png" class="rt-diagram" alt="Figure singularity plot">
- <p>In the architectural terminology utilized here, viewing memory as a horizontal tape with bytes in the cells and addresses increasing while moving to the right, little-endian numbers have the least-significant digit on the left, and big-endian numbers have the least significant digit on the right of an allocation. Stated structurally, little-endian numbers are zero padded on the right, and big-endian numbers are zero padded on the left.</p>
- <p>Figure 10 depicts a word featuring byte addresses represented in hexadecimal, running from c0 to c3. (In decimal these addresses represent 192, 193, 194, 195). The address of the byte before c0 is bf. The address after c3 is c4. The address for the word itself evaluates to c0, as it is the minimum byte address. This word holds a little-endian number. Treating a byte as an octet digit, the binary encoding for the least-significant digit of this number is 0001 1000. The most-significant digit is 1010 1110.</p>
+ <p>
+ Evaluating this form in the first order at the point <RT·math>t=5</RT·math> returns a <RT·code>(divide 0 0)</RT·code>. It is a strange thing, because when we plot <RT·math>h(t)</RT·math> values, against <RT·math>t</RT·math> there is an obvious correct value on the curve at <RT·math>t=5</RT·math>. There is no special feature that would make it questionable.
+ </p>
- <figure>
- <img src="Hindu-Arabic number fig 1.png" class="rt-diagram" alt="Figure 10: Left justified, least-significant-digit-first">
- <figcaption>Figure 10: Left justified, least-significant-digit-first</figcaption>
- </figure>
+ <p>
+ Suppose we move to a second order evaluation, and for places the computation does not work, we return the machine that isn't evaluating. That is similar to what we did to get <RT·code>(divide 0 0)</RT·code>, but the zeros in that expression came from a prior step first order evaluation. Let us instead give the quotient composer two machines to compose, and have it return a value when reduction to the first order is possible, and return the full problem as posed to it when it can not be reduced.
+ </p>
- <p>In Figure 11, the same number populates the word using big-endian architecture. For all but very large numbers, the digit pointed at by the allocation pointer evaluates to zero. A system continues to scan zeros until reaching either the end of the allocation or the most-significant digit. If it reaches the end of the allocation, the contained number evaluates to zero. Because this is the exact same number shown in the prior figure, it retains the identical least-significant digit and most-significant digit.</p>
+ <table>
+ <thead>
+ <tr>
+ <th>Count (<RT·math>t</RT·math>)</th>
+ <th>Quotient (<RT·math>f(t)/g(t)</RT·math>)</th>
+ <th>1st Diff Ratio (<RT·math>δ^1 f(t) / δ^1 g(t)</RT·math>)</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>0</td>
+ <td>31/15</td>
+ <td>1/3</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>1</td>
+ <td>30/12</td>
+ <td>2/3</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>2</td>
+ <td>28/9</td>
+ <td>4/3</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>3</td>
+ <td>24/6</td>
+ <td>8/3</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>4</td>
+ <td>16/3</td>
+ <td>16/3</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>5</td>
+ <td><RT·code>(divide f(5) g(5))</RT·code></td>
+ <td>32/3</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>6</td>
+ <td>32/3</td>
+ <td>64/3</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>7</td>
+ <td>96/6</td>
+ <td>128/3</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>8</td>
+ <td>224/9</td>
+ <td>256/3</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>9</td>
+ <td>480/12</td>
+ <td>512/3</td>
+ <td></td>
+ </tr>
+ </tbody>
+ </table>
- <figure>
- <img src="Hindu-Arabic number fig 2.png" class="rt-diagram" alt="Figure 11: Same number using big-endian digit order">
- <figcaption>Figure 11: Same number using big-endian digit order</figcaption>
- </figure>
+ <p>
+ The quotient machine, <RT·code>(divide f(5) g(5))</RT·code>, is no more representative of the value we expected here, than was <RT·code>(divide 0 0)</RT·code>. However, as we have the whole program, we could pass it to a L'Hôpital evaluator and get a value back. But I am wondering if we can't design function extension form that extends to the value and beyond without any hiccups, as most apparently there is a value to extend to.
+ </p>
- <p>If the specified word holds a count, the counting mechanics differ. When counting with the little-endian convention, a number grows into larger addresses as the count carries into new digits. In contrast, with big-endian architecture, counting carries into strictly smaller memory addresses.</p>
+ <p>
+ I included the first difference along with the evaluation results in the table above. When building a first difference table an interesting thing happens at the singularity, the first difference and the function itself coincide, note calls 4 and 5. This makes sense because <RT·math>δ f(4) = f(5) - f(4)</RT·math>, which becomes <RT·math>δ f(4) = 0 - f(4)</RT·math>, and the same happens to <RT·code>g</RT·code> in the denominator, so the signs cancel. Note also <RT·math>δ f(5) = f(6) - f(5)</RT·math>, which becomes <RT·math>δ f(5) = f(6) - 0</RT·math>.
+ </p>
- <p>Typically, processor registers and buses are one word in length, and a processor loads the entire contents of a memory word into a register as a single atomic operation. Contemporary processors are designed effectively for either little-endian or big-endian number representation; however, once designed for little-endian, the processor is fundamentally incompatible with big-endian, and vice-versa.</p>
+ <p>Ostensibly it looks like we have happened upon a discrete version of L'Hôpital's rule, that we merely use the first difference quotient instead of the function quotient at the singularity. But alas, the coincidence occurs at <RT·math>h(4)</RT·math> rather than at <RT·math>h(5)</RT·math>. For the second coincidence, we find <RT·math>h(6)</RT·math> as the 5th first finite difference. In neither case did we learn anything about the value of <RT·math>h(5)</RT·math>.
+ </p>
- <p>If an unconventional processor loads numbers sequentially as digit streams, the load instruction must possess a mechanism to detect the end of the loaded number, or it must receive the exact length prior to execution. This structural boundary is identical to the existing mechanism for loading character strings, where systems utilize length counts or explicit end terminators.</p>
+ <p>
+ Perhaps if we were performing nonstandard analysis, and extending by increments of epsilon in the domain at each step, the nonstandard analysis naturalization function would find that the differential value can be used as the value at the singularity. But here we are discussing finite differences <RT·endnote>H. Jerome Keisler, <em>Elementary Calculus: An Infinitesimal Approach</em>, 2nd ed. (Boston: Prindle, Weber and Schmidt, 1986).</RT·endnote>. We are not doing a local analysis here so as to derive a principle, but rather are trying to evaluate a function in the macro view.
+ </p>
- <p>For serial computation utilizing little-endian notation, the processor produces the first digit of the sum immediately after receiving the first digit of the operand. If big-endian is used, the least significant digit arrives last, forcing the system to buffer the entire number before computing the first digit of the sum. Attempting to load from the far ends of the operands fails because the physical location of the sum's far end remains undefined until the carry propagates. Resolving big-endian serial addition requires implementing signed digit arithmetic.</p>
+ <p>
+ So then, perhaps we set the value at the singularity to <RT·math>x</RT·math> build out the difference table, then solve for <RT·math>x</RT·math>?
+ </p>
- <h2>Bit Order Within Bytes</h2>
+ <img src="with_x_variable.png" class="rt-diagram" alt="Figure with x variable.">
- <p>Data transports between hardware nodes over bundles of wires called buses. Bus specifications explicitly define the order of bits in bytes, and for contemporary machines, bytes are strictly octets. All compute processors, channel processors, and attached devices must conform to the bus’s specifications.</p>
- <p>Channel processors bridge the gap between a computer's system bus and a storage bus connected to storage devices. The storage device designers adhere strictly to the storage bus specification without evaluating the host computer architecture. The compatibility burden relies entirely on selecting a channel processor that respects the established standard. Consequently, unless a programmer explicitly designs bus standards or hardware interfaces, the physical bit order within bytes remains invisible.</p>
+ <p>
+ As Newton pointed out, we can know the differences on the <RT·math>D_0</RT·math> row of the table from the quotient function:
+ </p>
- <p>Some processors physically store bits into memory bytes in different orders. However, the data read and written into the respective memories routes through a bus, and at that boundary, the bit order aligns with the applicable bus specification.</p>
+ <RT·math>
+ D_{0,n} = \sum_{k=0}^n (-1)^{n-k} \binom{n}{k} \frac{f(k)}{g(k)}
+ </RT·math>
- <p>On all contemporary computers, ALU hardware performs arithmetic on bytes. Carries propagate strictly from lower significant bits to more significant bits; therefore, the ALU imposes a logical bit order within bytes. That logical order conforms directly to the documentation for the processor’s internal bus, where bit 0 serves universally as the least significant bit.</p>
+ <p>
+ For <RT·math>k=5</RT·math> the difference evaluates to be <RT·code>(divide 0 0)</RT·code>, as it did for the quotient, and it also fails for all values above <RT·math>k=5</RT·math> due to the summation. So we can not use this to solve for <RT·math>x</RT·math>.
+ </p>
- <p>Because doing so is impossible in all but special cases, an address points at the entire byte, rather than pointing at the most significant bit or the least significant bit.</p>
+ <p>
+ As the second order evaluation returned <RT·code>(divide f(k)|k=5 g(k)|k=5)</RT·code>, we can use L'Hôpital evaluator to resolve this indeterminate form <RT·math>D_{0,5}</RT·math>. Note this is during the creation of the <RT·math>D_0</RT·math> vector, so it is part of the simplification step, rather than being part of the evaluation of the quotient. The result will thus be used in producing an extension machine that extends through the singularity, so the user will not see any exceptional higher order values while extending <RT·math>h(t)</RT·math>.
+ </p>
- <h2>Byte Order Within Words</h2>
+ <p>
+ The L'Hôpital evaluator will discover that a transcendental constant is required. It will be returned as a higher order object, a machine definition, because the value can not be written to the tape.
+ </p>
- <p>A specific processor architecture features native support for byte data alongside varied word lengths, most commonly 16, 32, and 64 bits (or 4, 8, and 16 bytes).</p>
+ <RT·math>
+ T = \frac{32 \ln(2)}{3}
+ </RT·math>
- <p>Communication channels and storage devices organize payloads strictly as octets, possessing no native structural support for words. Therefore, storing or transmitting a word requires serializing the word into a sequence of bytes, transferring the data, and subsequently reading the data while deserializing the byte stream back into the original word configuration.</p>
+ <p>The resulting <RT·math>D_0</RT·math> vector for the quotient is:</p>
- <p>Our Indian Number derived representations consist of sequences of digits. Words of allocation consist of consecutively addressed bytes. Hardware manages bytes atomically. Any bit encoding for the digits of a number must pack cleanly into bytes, otherwise the digits fracture. Achieving a clean packing requires padding the data with zeros to force alignment to an 8-bit boundary. When a system meets this criterion, treating a number as a sequence of bytes acting as digits maintains structural consistency. Due to this constraint, little-endian and big-endian are routinely classified as byte orders.</p>
+ <img src="D_10.png" class="rt-diagram" alt="Figure D_0 h(t)">
- <p>The following figure displays a stream of bytes arriving as data and being copied into a word. The digits of the word (the bytes) arrive in little-endian order and target a little-endian machine, so they are written in the exact order they are scanned off the channel.</p>
+ <img src="h_of_t_D_table.png" class="rt-diagram" alt="Figure D table for h(t)">
- <figure>
- <img src="Hindu-Arabic number fig 3.png" class="rt-diagram" alt="Figure 12: In-address-order byte by byte copy">
- <figcaption>Figure 12: In-address-order byte by byte copy</figcaption>
- </figure>
+ <p>
+ I find it fascinating to watch the transcendental difference values march down the table, then all cancel out after <RT·math>h(5)</RT·math> is generated.
+ </p>
- <p>In the second case, the identical data stream arrives with words serialized as bytes in little-endian order, but the receiving machine is big-endian. The system must reverse the bytes strictly on a word-by-word basis.</p>
+ <p>
+ So the value of <RT·math>h(5)</RT·math> is transcendental, but all the machinery we were using, up until using L'Hôpital evaluator, was closed over the rational field. That is why no matter how we tried to organize the difference tables, there was no way to find <RT·math>h(5)</RT·math>, or to find a <RT·math>D</RT·math> table for extending through it. This situation occurred due to introducing the <RT·math>2^t</RT·math> function. Had we remained with finite length <RT·math>D</RT·math> tables, i.e. finite degree polynomials, we could have used deconvolution of the <RT·math>D_0</RT·math> vectors, or synthetic division on the polynomials, to create the quotient.
+ </p>
- <figure>
- <img src="Hindu-Arabic number fig 4.png" class="rt-diagram" alt="Figure 13: Reverse order byte copy for words">
- <figcaption>Figure 13: Reverse order byte copy for words</figcaption>
- </figure>
- <p>When the data arrives, there is no way to know where the word boundaries are. Serialization destroys that structural boundary information. Without knowing where the words are, a system cannot determine when to perform the byte order reversal. Therefore, resolving this requires a different approach than the one used for bit order in bytes; the problem transfers into the software layer.</p>
+ <p>And for the victory lap, evaluation versus higher order computation and function extension:</p>
- <p>Byte order within words becomes an application level design consideration, even though it possesses no structural relevance to applications.</p>
+ <table>
+ <thead>
+ <tr>
+ <th>Count (<RT·math>t</RT·math>)</th>
+ <th>Direct Evaluation (<RT·math>f(t)/g(t)</RT·math>)</th>
+ <th>Extended Quotient Vector (<RT·math>H(t)</RT·math>)</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>0</td>
+ <td>31/15</td>
+ <td>31/15</td>
+ </tr>
+ <tr>
+ <td>1</td>
+ <td>30/12</td>
+ <td>30/12</td>
+ </tr>
+ <tr>
+ <td>2</td>
+ <td>28/9</td>
+ <td>28/9</td>
+ </tr>
+ <tr>
+ <td>3</td>
+ <td>24/6</td>
+ <td>24/6</td>
+ </tr>
+ <tr>
+ <td>4</td>
+ <td>16/3</td>
+ <td>16/3</td>
+ </tr>
+ <tr>
+ <td>5</td>
+ <td><RT·code>(divide 0 0)</RT·code></td>
+ <td><RT·math>\frac{32 \ln(2)}{3}</RT·math></td>
+ </tr>
+ <tr>
+ <td>6</td>
+ <td>32/3</td>
+ <td>32/3</td>
+ </tr>
+ <tr>
+ <td>7</td>
+ <td>96/6</td>
+ <td>96/6</td>
+ </tr>
+ <tr>
+ <td>8</td>
+ <td>224/9</td>
+ <td>224/9</td>
+ </tr>
+ <tr>
+ <td>9</td>
+ <td>480/12</td>
+ <td>480/12</td>
+ </tr>
+ </tbody>
+ </table>
- <p>In protocols like the Internet Protocol (IP), the specification explicitly defines the data offset for the packet header. Code executes stably and efficiently. However, IP lacks context regarding the word locations in the data payload it carries, so IP routes the payload up the abstraction stack, preserving all byte order conflicts.</p>
+ <h2>Chapter discussion</h2>
- <p>JSON provides a standard for expressing tagged structured data built from primitive types while using character-only data. The specification dictates how numeric character strings evaluate, allowing both little-endian and big-endian machines to safely exchange JSON character-encoded numeric data. However, converting all numbers to strings and back wastes computational overhead, and transmitting them over a channel consumes excess bandwidth. Both processes expend unnecessary energy.</p>
+ <p>The fundamental claim of computational analysis is that all functions can be viewed as discrete entities. Take this simple function:</p>
- <p>Various file formats and data communications standards serve specific classes of applications by explicitly defining where words that require reversal are located during machine-to-machine data transfers.</p>
+ <RT·code>
+ f(t) = t^3
+ </RT·code>
- <h2>TTCA</h2>
+ <p>It is said to be a continuous function over the real field. However, from a computational perspective, it is a string of 8 discrete symbols. A person might ask then, "If the function is not continuous, then how can a person take a derivative?" Often people view a derivative as a tangent line to a curve drawn on a plot. The computational analyst's answer is to use a machine that manipulates the symbols; it will move the 3 down in front of the <RT·math>t</RT·math>, decrement the power, and write 2. Those are all discrete operations.</p>
- <p>The native integer data type for TTCA utilizes a high radix online number system. This number system functions as an extension of online arithmetic. Similar to online arithmetic, it employs serial most-significant-digit-first signed digit arithmetic. In contrast to standard signed arithmetic, the radix scales significantly higher, causing a digit to span at least a byte in length. The numbers are highly scalable, and the architecture mandates an analysis step at compile time to establish rigorous precision requirements and exact range bounds. This process is detailed in the next chapter. The architecture provides explicit conversion instructions to generate alternate number formats.</p>
+ <p>The point of this chapter is to state that computational analysis exists, rather than to present a comprehensive computational analysis system. If a person is looking for practical systems for extending functions that expand precision, or for second order computation, there are options.</p>
+
+ <p>Hans J. Boehm and Robert Cartwright's work of expanding the precision of a computation on demand continued to mature. Boehm, while working at Google, authored a Java constructive reals library that powers the bundled Android calculator. When a person types in an expression, the system builds an Abstract Syntax Tree (AST) of the expression. The display interface determines how many digits fit on the screen and demands exactly that much precision from the root of the tree. The root function then recursively demands progressively higher precision from its operand functions until it achieves the strict error bounds required to guarantee every digit displayed on the screen is mathematically correct <RT·endnote>Hans J. Boehm, "Small data computing: Correct calculator arithmetic," Google Research, 2015. This library uses constructive real arithmetic to guarantee fully accurate results through demand driven evaluation.</RT·endnote>.
+ </p>
+
+ <p>Wolfram Mathematica utilizes a true second order computation system by keeping expressions in their exact symbolic form for as long as possible. An expression like <RT·code>Sqrt[2] + Pi</RT·code> remains an AST of symbols. It then relies on an extensive library of algebraic rewrite rules to simplify the tree. However, unlike Boehm and Cartwright's method of pulling precision through back propagating extension calls, when a numeric result is required, Mathematica runs the computation forward using significance arithmetic. Should there be insufficient precision at the result, it employs an adaptive retry loop <RT·endnote>Fredrik Johansson, "The significance of arithmetic," 2008. Mathematica implements significance arithmetic by approximating numbers as a floating point value with an attached error estimate, allowing dynamic adaptation and iteration if precision falls below the required threshold. See also: Wolfram Language Documentation, "Arbitrary Precision Numbers" (https://reference.wolfram.com/language/tutorial/ArbitraryPrecisionNumbers.html).</RT·endnote>.
+ </p>
+
+ <p>The approach I used differs from both of these approaches. Like them, the first step is to build the AST, but then the tree is evaluated in two phases. The first phase analyzes the tree, while creating error propagation expressions either per computation step or for atomic computation blocks. These take the form of symbolic expressions with error <RT·math>\epsilon</RT·math> in and error <RT·math>δ</RT·math> out, over the domain of the input. Then instead of back propagation of precision requests at run time, there is a back propagation of precision requirements at analysis time. This approach was used on the AMD K5 processor transcendental microcode to guarantee that computations yielded specified accuracy. The method is also well suited for setting the bus widths for application specific processors, because the analysis step can occur once at design time. <RT·endnote>Thomas Walker Lynch, A. Ahmed, M. Schulte, T. Callaway, and R. Tisdale, "The K5 Transcendental Functions," <em>Proceedings of the 12th IEEE Symposium on Computer Arithmetic</em>, 1995. DOI: 10.1109/ARITH.1995.465368.</RT·endnote> <RT·endnote>Thomas Walker Lynch, "Numerical Analysis of Computer Approximations," September 2018. DOI: 10.13140/RG.2.2.10906.49601. License CC BY 4.0. https://www.researchgate.net/publication/329402329_Numerical_Analysis_of_Computer_Approximations</RT·endnote>.
+ </p>
+
+ <p>None of these computation systems, that of Boehm and Cartwright, Mathematica, nor my error analysis approach, makes use of the IEEE 754 standard floating point arithmetic. Rather they all require the use of variable precision. In the case of Boehm and Cartwright's this occurs through the serialization implied through lazy calls for more precision. In the case of Mathematica it is explicit in the significance arithmetic. In my error analysis approach, the means for variable precision was the High Radix Online Arithmetic <RT·endnote>Thomas W. Lynch, "High Radix On Line Arithmetic for Credible and Accurate Computing," Real Numbers and Computers, École des Mines de Saint Étienne, France, 1995, pp. 78 89.</RT·endnote> <RT·endnote>Thomas W. Lynch and Michael J. Schulte, "Software for High Radix On Line Arithmetic," Reliable Computing, vol. 2, no. 2, 1996, pp. 133 138. DOI: 10.1007/BF02425915.</RT·endnote>.
+ </p>
+
+ <p>Hence what would be useful for a replacement of the IEEE 754, would be a variable precision number standard. There is one being proposed by John Gustafson called the Posit number representation <RT·endnote>John L. Gustafson, <em>The End of Error: Unum Computing</em>, Chapman and Hall/CRC, 2015. Posits serve as a hardware friendly alternative to IEEE 754 floating point numbers, providing variable precision mechanics.</RT·endnote>.
+ </p>
+
+ <p>This section reviewed finite difference methods as a means to implement function extensions. A person should notice that finite differences are one among many methods for extending functions, and they are not always the best choice. In modern computing it is not common to implement function extensions. Rather it is more common to increment a value across a domain, while completely repeating the evaluation for each new domain value, for example when computing values to place on a plot. However, independent of their importance in applied computing, function extension and difference methods are of theoretical importance for analysis, particularly when applied to nonstandard analysis. These concepts speak directly to the thesis of computational naturalism—deriving mathematics from computational logic—rather than merely proposing practical methods for implementing software.
+ </p>
<!-- rest of book goes here --->