Charles Babbage’s

Analytical Engine

A Java Simulation

Charles Bab­bage de­signed (but nev­er built) a com­put­ing ma­chine in the mid-1800s, a ma­chine that to­day would be called a dig­i­tal com­put­er. On this ma­chine mod­el, Ada Lovelace de­signed the first com­put­er pro­gram. Babbage’s ini­tial de­scrip­tions of the ma­chine pre­date Alan Turing's mod­el of a gen­er­al pur­pose com­put­er by about a hun­dred years. This Java sim­u­la­tion of the An­a­lyt­i­cal En­gine will help to pre­serve the lega­cy of these pi­o­neers of com­put­ing.

Hardware overview

The An­a­lyt­i­cal En­gine ma­chin­ery is es­sen­tial­ly the same as that of a (sim­ple) mod­ern dig­i­tal com­put­er. Here is a brief de­scrip­tion of the hard­ware in­clud­ing both the old and new ter­mi­nolo­gies.

Each in­struc­tion for the ma­chine comes on a card with holes punched in it to in­di­cate which in­struc­tion it rep­re­sents (that is, its “op­code”). A pro­gram is pro­vid­ed as a se­quence of cards.

The ran­dom ac­cess mem­o­ry of the ma­chine is called the store and the arith­metic log­ic unit is called the mill. The mill per­forms arith­metic op­er­a­tions on in­te­gers, each of which is stored in a reg­is­ter called an axis (plur­al axes). In­te­gers read from mem­o­ry are stored in ingress axes and the re­sult of arith­metic op­er­a­tions are stored in egress axes. The run-up lever is a flag that is set when an arith­metic op­er­a­tion caus­es an in­te­ger over­flow.

The ma­chine is con­trolled by an at­ten­dant who loads pro­gram­s, starts and op­er­ates the ma­chine while the pro­gram ex­e­cutes, and pro­vides a fi­nal re­port show­ing the out­put of the ma­chine. The ma­chine can pro­duce tex­tu­al out­put via a print­er as well as graph­i­cal out­put via a curve draw­ing ap­pa­ra­tus, a de­vice we would call a plot­ter.

Preparing the simulator

The source code for the An­a­lyt­i­cal En­gine sim­u­la­tor is avail­able from GitHub.

git clone https://github.com/jfinkels/analyticalengine

This project uses Maven for build man­age­ment and re­quires Java 1.8 to com­pile.

To cre­ate a shell script that runs the sim­u­la­tor, run

mvn package appassembler:assemble

This pro­duces a script that ex­e­cutes the sim­u­la­tor at target/appassembler/bin/analyticalengine. An An­a­lyt­i­cal En­gine pro­gram stored in a file named myprogram.ae, for ex­am­ple, can then be ex­e­cut­ed via

target/appassembler/bin/analyticalengine myprogram.ae

Programming the Engine

The pro­gram­ming lan­guage for this ma­chine is ar­cane, but rel­a­tive­ly sim­i­lar to mod­ern ma­chine lan­guages. For the pur­pos­es of this sim­u­la­tor, an An­a­lyt­i­cal En­gine pro­gram is a text file in which each line rep­re­sents a card.

To load a num­ber into the store, use the N card.

Example

Store the num­ber 123 in mem­o­ry lo­ca­tion 1.

N1 123

Blank lines, lines be­gin­ning with a pe­ri­od, and lines be­gin­ning with­spaces are treat­ed as com­ment card­s.

Example

In this ex­am­ple there are five com­ment card­s, three of which are blank.

   This program loads two numbers into the store.

N1 123

. This is another comment.

N2 456

Arith­metic op­er­a­tions are au­to­mat­i­cal­ly ex­e­cut­ed once the two operands are loaded from mem­o­ry into the ingress ax­es. The re­sult of the op­er­a­tion is au­to­mat­i­cal­ly ex­tract­ed from the egress axis and stored in a mem­o­ry lo­ca­tion in­di­cat­ed by a store card.

Example

Add two num­bers from mem­o­ry lo­ca­tions 1 and 2 and store the re­sult in mem­o­ry lo­ca­tion 3.

N1 123
N2 456

+
L1
L2
S3

Di­vi­sion can be per­formed on a div­i­dend of “dou­ble width” by load­ing the low­er-order dig­its of the div­i­dend in the first main ingress axis and the high­er-order dig­its of the div­i­dend into the “prime” ingress ax­is. The prime axis must be loaded af­ter the main ax­is.

The quo­tient of a di­vi­sion can be ac­cessed from the prime egress ax­is, and the re­main­der from the main egress ax­is. If the quo­tient is too large for the main ax­is, the run-up lever is set, and the quo­tient and re­main­der will be set to 0.

Example

Com­pute 1050 + 1 di­vid­ed by two. By de­fault, each axis can store a dec­i­mal num­ber of fifty dig­it­s, so load­ing 1 into the low­er or­der ingress axis and an­oth­er 1 into the prime ingress axis rep­re­sents 1050 + 1.

N1 1
N2 1
N3 2

/
L2
L1'
L3

  Print the quotient.

S3'
P

  Print the remainder.

S4
P

Per­form­ing the same op­er­a­tion twice does not re­quire re-spec­i­fy­ing the op­er­a­tion.

Example

Mul­ti­ply two by two, and then mul­ti­ply that prod­uct by two. Store the re­sult in mem­o­ry ad­dress 1.

N1 2
N2 2

*
L1
L2
S1
L1
L2
S1

Num­bers can be print­ed to the print­er by us­ing the P card. To re­main his­tor­i­cal­ly ac­cu­rate, the sim­u­la­tor does not print di­rect­ly to stan­dard out­put when it en­coun­ters a print card; in­stead, the at­ten­dant col­lects all print­ed strings and makes them avail­able only af­ter the pro­gram has ter­mi­nat­ed.

Example

Print the sum of 123 and 456 to the print­er.

N1 123
N2 456

+
L1
L2
S3
P

What gives this ma­chine its com­pu­ta­tion­al pow­er is con­di­tion­al ex­e­cu­tion. A con­di­tion­al jump card checks whether the run-up lever has been set and ei­ther re­vers­es or ad­vances the card chain by a spec­i­fied num­ber of card­s.

Example

In this ex­am­ple, the En­gine com­putes a fac­to­r­i­al by ac­cu­mu­lat­ing the prod­uct of n with (n - 1)! it­er­a­tive­ly. At the end of each it­er­a­tion, the con­di­tion­al jump card de­cides whether to per­form an­oth­er it­er­a­tion of the loop or to ter­mi­nate and print the re­sult based on whether the run-up lever has been set.

  Computes 6! using a loop.

  Address 0 is the current value of n.
  Address 1 is the accumulator for the product.
  Address 2 represents the constant 1.

N0 6
N1 1
N2 1

*
L1
L0
S1
-
L0
L2
S0
   If 1 - n < 0, go back 12 cards (including this one!)
L2
L0
CB?12
L1
P

Programmatic access to the simulator

The sim­u­la­tor per­forms the fol­low­ing high­-lev­el al­go­rithm

  1. pre­pare a card chain,
  2. ask the at­ten­dant to load the card chain,
  3. run the En­gine.

The Attendant in­ter­face and the AnalyticalEngine in­ter­face are the most sig­nif­i­cant parts of the sim­u­la­tor in­ter­face. Once a card chain is pre­pared, the at­ten­dant loads the cards into the En­gine us­ing the Attendant.loadProgram() method. Once the cards are load­ed, call­ing the AnalyticalEngine.run() method ex­e­cutes the pro­gram. Any out­put is col­lect­ed by the at­ten­dant and can be ac­cessed via the Attendant.finalReport() method.

The ­An­a­lyt­i­calEngine must be pro­gram­mat­i­cal­ly as­sem­bled be­fore it can ex­e­cute a pro­gram. The En­gine needs a mil­l, a store, a card read­er, etc., each of which can be set us­ing the ap­pro­pri­ate­ly named set­ter meth­od­s. The code is de­signed so that each com­po­nent can be eas­i­ly reim­ple­ment­ed and re­placed.

Example

One might re­place the de­fault HashMap-based im­ple­men­ta­tion of ­S­tore with one backed by a re­mote key-­val­ue data store. In this ex­am­ple, we use Jedis, a Re­dis client for the Java pro­gram­ming lan­guage:

package example;

import java.math.BigInteger;
import java.net.URI;

import analyticalengine.components.Store;
import redis.clients.jedis.Jedis;

public class RedisStore implements Store {

    private Jedis client = null;

    public RedisStore(URI uri) {
        this.client = new Jedis(uri);
    }

    public BigInteger get(long address) {
        String addressString = String.valueOf(address);
        String valueString = this.client.get(addressString);
        BigInteger value = new BigInteger(valueString);
        return value;
    }

    public void put(long address, BigInteger value) {
        String addressString = String.valueOf(address);
        String valueString = String.valueOf(value)
        String status = this.client.set(addressString, valueString);
        if (!status.equals("OK")) {
            throw new RuntimeException(status);
        }
    }

    public void reset() {
        this.client.clear();
    }
}

Then make your AnalyticalEngine in­stance use this store:

AnalyticalEngine engine = new DefaultAnalyticalEngine();
engine.setStore(new RedisStore());