When we write a program to solve a particular problem, we are in effect communicating our solution to a computer in terms of a set of instructions for it to carry out.  The way we communicate with the computer is fashioned after the way we communicate with humans: that is we use "languages."    The languages that computers "understand" are called computer languages. There are many computer languages, each of which is designed to use for some specific problem domain.  Each language uses specific symbols and have specific rules, called syntax rules, on how these symbols should be put together.  There are also rules that prescribe what each specific syntactic construct means; they are called semantic rules.

Examples

Language

Syntax

Semantic

Scheme

(+ 4 7)

The sum of 4 and 7

Java

4 + 7

Excel

= 4 + 7

Scheme

(* (+ 2 3) 4)

First add 3 to 2, then multiply the result by 4.

Java

(2 + 3) * 4

Excel

= (2 + 3) * 4

In the above example, the arithmetic expressions in Scheme are said to be in prefix notation, i.e. the operator (+) precedes the operands (4 and 7), while the ones in Java and Excel are said to be in infix notation, i.e. the operator is in between the operands.

Writing a program to solve a problem entails:

  • making a model of the problem,
  • perform computations on the model, and
  • interpret the results as solutions to the problem. 

We will learn how to program by focusing on modeling various problems at hand and building the resulting models using an appropriate programming constructs in the some appropriate language.  The syntax and semantics of these constructs will be presented and explained as their needs arise.

Modeling is the process of building models.  A model of something is some sort of representation of the real thing.  For example, a street map is a model of a city.  Architects make mock-ups of shopping malls that they are going to build.  The law of supply and demand is a simple model of the economy.  Abstract painters use colors to represent feelings.  A model is something that is fundamentally less than the real thing that it represents, yet useful in helping solve the problem at hand.  For example, a street map is more useful to some one who needs to find a particular address than an aerial photo of a city.  An architect's foam mock-up is useful to the architect's client but is not so useful to the builder.

Essential to the modeling process is abstraction.  Abstraction is the process of hiding the details and exposing only the essential features of a particular concept or object.  Proper abstraction will result in a good model that helps solve the problem.  Random addition and removal of features constitute another strategy for modeling, usually does not result in a good model to solve the given problem. 

Computer Science is based on mathematics.  A primary modeling tool is to use mathematical entities such as numbers, functions and sets to model problems.  Since computers are originally designed to perform calculations, we illustrate the programming process with the following numerical example.

The Problem

...The cheap taxi that is taking Jimmy the Geek from Charles de Gaulle to his hotel in downtown Paris has no air conditioning.  Yet the air feels cool.  Jimmy catches a glimpse of a billboard display the sign 15o.  "Hmm, I wonder what temperature that is in Fahrenheit," he thinks to himself.  Jimmy pulls out his Tablet PC and starts writing a program to convert Celsius into Fahrenheit...

We shall outdo Jimmy by writing such a temperature conversion program using:

·         Excel

·         Scheme, and

·         Java.

What does it take to write such a program? 

·         First and foremost, it requires knowing the formula that converts Celsius to Fahrenheit, for without that knowledge, we cannot write the desired conversion program.  The conversion formula is the mathematical model of the problem. 

·         Second, it requires knowing how to express the model in a particular computer language.  The result is a program.  The computer program must closely represent the model in order for it to be correct and useful.

The Model

We use real numbers to represent the temperature readings in various temperature units.  The formula expressing a temperature measurement F in Fahrenheit in terms of the measurement C in Celsius is:

F = 9/5 *C + 32

Here C is called the variable, and F is called a function of C.

DrJava Solution 1

We can use our favorite tool DrJava and enter the formula with C replaced by 15, as shown below.

Welcome to DrJava.
> 9/5 * 15 + 32
47
>

As we enter the formula 9/5 * 15 + 32 in the Interactions Pane and hit Enter, DrJava immediately displays the result: 47. Is that what we expected? No, 9/5 * 15 + 32 should be 59. Our program didn't work, or more correctly: It didn't do what we had in mind. What happened? Java treated the numbers differently than we expected: It used integer arithmetic to perform this calculation, i.e. Java used only whole numbers, and dropped any decimal digits after each step of the calculation. Therefore, 9/5 was just 1, not 1.8. A computer only has a limited amount of memory, and therefore, numbers cannot always be represented exactly. Sometimes, the programmer has to make a compromise between memory usage and precision. Using integers instead of floating-point numbers, which can have decimals, is one of those compromises. If Java sees a number that doesn't have to be a floating-point number, it will treat it as an integer. All the numbers in our formula are whole numbers, so Java treats all of them as integers; therefore, integer arithmetic is used in the entire calculation. 9/5 * 15 + 32 evaluates to 1 * 15 + 32, which evaluates to 15 + 32, which results in 47. We can force Java to treat them as floating-point numbers by appending a .0 to each number. Let's try our formula again:

Welcome to DrJava.
> 9.0/5.0 * 15.0 + 32.0
59.0
>

After we press Enter this time, DrJava displays 59.0.  This time, Java did what we expected: The above is a program that solves the stated problem.  It is an example of what is called a "constant" program. 

What is good about this program is that it is simple, easy to understand, and most importantly it correctly solves the original problem.  However, if we change slightly the problem to converting 17o Celsius to Fahrenheit, the program no longer is applicable.  Intuitively, the problem has not really changed at all: the "real" problem is about converting a temperature measurement in Celsius to the corresponding value in Fahrenheit.  What has changed is the value of the temperature measurement.  The mathematical formula expressing the conversion process is an invariant: it does not change.  The value of the variable C is a variant: something that can change arbitrarily.  The above constant program fails to capture the variant/invariant nature of the mathematical formula, and as a result, cannot be used to solve the same conversion problem with a different temperature measurement.

What we want to do is to write a program that has an invariant part that corresponds to the formula and a variant part that allows entering arbitrary values for C.

DrJava Solution 2

We redesign our Java solution and enter the following:

Welcome to DrJava.
> class CelsiusToFahrenheit {
    double convert(double celsius) {
        return 9.0/5.0 * celsius + 32.0;
    }
}
>

The middle three lines beginning with double convert(double C) { and ending with the first } define a function or method, as Java calls it. Mathematically, a function is a mapping of one set of values, called the domain, to another set of values, called the range.  In our example, we are taking temperatures in Celsius and mapping them to temperatures in Fahrenheit, i.e. the domain is temperatures in Celsius, and the range is temperatures in Fahrenheit.

Java is an object-oriented language, though, and that means every function exists in some context.  This context is called a class and may contain other methods and fields that store data.  Additional methods or fields are not necessary here, but we still have to put the convert method inside a class.  In our example, we called the class CelsiusToFahrenheit.

Classes definitions begin with the Java keyword class, followed by the name of the class and a pair of opening and closing braces { }.  Methods are defined by first specifying the type of the return value (double here, meaning floating-point values), then giving the method a name (this method is called convert), and then listing the method parameters inside a pair of parentheses ( ). The list method parameters consists of type-name pairs. convert only has one parameter whose type is double and whose name is celsius. Method parameters work as "input" of a function, while the return value works as the "output".  After the closing parenthesis, a pair of braces { } encloses the method body, which describes the actions the method should perform.  In this example, we want to return the result of the computation 9.0/5.0 * celsius + 32.0.

Now we have defined a class with a method that can perform the required calculation.  We have not actually used it, though.  To do that, we enter the following lines of code into the DrJava Interactions Pane:

> CelsiusToFahrenheit CtoF = new CelsiusToFahrenheit();
> CtoF.convert(15.0)
59.0
>

The first of these lines creates a new object of the class CelsiusToFahrenheit.  This object has the name CtoF.   You can think of the class as a blueprint of an airplane; it describes all the details of the airplane, but it is not an airplane itself.  The object is an instance of what is described in the blueprint; it is the real, tangible thing.  The second line then invokes the convert method of the CtoF object; it asks the object to perform the computation described in the method definition. The temperature in Celsius, 15.0 here, is provided as argument to the method.  Whatever value we provide as argument when we use a method becomes the value of the parameter inside the method body.  When we press return after the second line, DrJava immediately displays the result of that computation: 59.0.

In this solution, the user can enter any value for the temperature in Celsius as the argument to the convert method and press Enter, and DrJava will immediately compute the corresponding temperature in Fahrenheit.  The formula encoded in the method refers to the value of the parameter celsius for the temperature.  The number given as argument may change, it is variant; the formula in the method is always the same, it is invariant.  Therefore, this solution captures the mathematical formula more faithfully.

However, our program still cannot do very much.  We can convert all temperatures measured in Celsius to their corresponding temperatures in Fahrenheit, but that is all we can do. We cannot even convert Fahrenheit temperatures back to Celsius, even though the equation

F = 9/5 *C + 32

expresses the entire relation between Fahrenheit and Celsius.  Using our knowledge of algebra, we can solve this equation for the variable C and get:

C = 5/9 * (F - 32)

DrJava Solution 3

We can now use this information to write another class that provides two methods, so it can do both conversions:

Welcome to DrJava.
> class TemperatureConverter {
    double convertCtoF(double celsius) {
        return 9.0/5.0 * celsius + 32.0;
    }
    double convertFtoC(double fahrenheit) {
        return 5.0/9.0 * (celsius - 32.0);
    }
}
> TemperatureConverter converter = new TemperatureConverter();
> converter.convertCtoF(15.0)
59.0
> converter.convertFtoC(59.0)
15.0

We now have a program that can convert temperatures back and forth between the Celsius and the Fahrenheit scale. The pattern to extend this should be clear: For each conversion, we add another method to the TemperatureConverter class. There is a problem, though: To keep the TemperatureConverter class general enough to convert from any scale to any other scale, we have to add more and more methods. If we add the Kelvin scale, we have to write methods to convert from Kelvin to Celsius and back, and from Kelvin to Fahrenheit and back -- four new methods. If we then throw the Réaumur scale in the mix, we have to add Réaumur to Celsius and back, Réaumur to Fahrenheit and back, and Réaumur to Kelvin and back -- six new methods. Adding another scale would require eight new methods, and so on. This approach clearly does not scale well, i.e. a small change in one place necessitates a large change somewhere else. Here, in order to maintain the program's generality, adding one temperature scale requires us to add many conversion methods.

DrJava Solution 4

There has to be a solution that scales better, a design in which adding a temperature scale only requires a constant amount of work, not ever more and more. Let's consider the problem of converting a temperature in Celsius to a temperature in Kelvin, but without a function going directly from Celsius to Kelvin. Can you do it? Yes, you can, provided you have a way to convert Celsius to Fahrenheit, and Fahrenheit to Kelvin. The choice of Fahrenheit as intermediary step is arbitrary; we could be using Réaumur or any other scale as well. In fact, we can even have multiple steps in the middle, we can convert from Celsius to Fahrenheit, from Fahrenheit to Réaumur, and from Réaumur to Kelvin. Conceptually, the scales and conversions form a directed graph -- a bunch of circles with single-headed arrows from circle to circle: Each scale is a node -- a circle -- of the graph, and each conversion we have is an edge -- a single-headed arrow -- from one node to another. We can convert from one scale to another as long as there is a path -- as long as we can follow arrows -- from the first scale's node to the second scale's node, regardless of how long the path is.

If we don't put additional restrictions on the graph, we might run into the situation where we can convert some temperature scales to some others, but not all scales to all others. The easiest way to ensure that we can always make the desired conversion is to enforce that the graph is star-shaped: There is one node in the center, and all other nodes are placed around it. There are edges from the internal node to all other nodes, and all other nodes have edges to the internal node. For our temperature conversions, that means that the internal temperature scale can be converted to all other scales, and all other temperature scales can be converted back to the internal scale. This way, the desired conversion can be made either directly or via the internal temperature scale.

Right now, we're giving one temperature scale special treatment: The scale whose node is in the center is treated differently than all the other scales, because it can be directly converted to and from. From the normal ,human perspective of wanting to perform as few computations as possible, that makes sense. From a programmer's perspective, though, we want also want to design a system that is simple and elegant, and special cases that have to be treated in a different way usually detract from that goal. Additions and multiplications are cheap today, so we can afford to use a few more of them to obtain a cleaner design.

We already mentioned that the choice of the internal, intermediary temperature scale is arbitrary. It is, in fact, so arbitrary that it doesn't even have to be a temperature scale that already exists: We can just invent one, as long as we can provide conversion functions to and from all other temperature scales. An even better idea, though, is to just duplicate one of the temperature scales, i.e. let it be present once for the internal node, and a second time as a regular temperature scale with a node on the outside. The conversion functions back and forth between the two are really simple: They are simply the identity function. Using this design, we can use the Celsius scale as internal temperature scale, and have Celsius, Fahrenheit, and Kelvin as external temperature scales. To convert from any of these scales to any other scale, we always first convert to the internal scale and then to the destination scale. If we are converting from Celsius, then this means that we first "convert from Celsius to Celsius". This is an additional step, but it's a small price to pay for a unified design. The advantage of this setup with a star-shaped graph is that we always only need to add two conversion methods: If we add Réaumur, we just add the conversion from Réaumur to the internal temperature scale -- which happens to be Celsius -- and the conversion back from the internal temperature scale to Réaumur. Our solution scales well: A small change in one part of the program only has a small, constant impact.

Implementation of DrJava Solution 4

In the section above, we have created a general, high-level, and language-independent solution. Now we need to write the Java program that implements this solution. There are several issues that we need to address:

1.      How do we represent the conversion functions in a general and extensible way?

2.      How do we associate the conversion functions with units, and how do we store them?

3.      Which scale do we use as intermediary temperature scale, the scale that forms the center of the star-shaped graph? As we have stated above, the choice is arbitrary, but we do have to make a decision.

We have already mentioned that in Java, everything is in a class.  It is therefore clear that create a class, but we still have to determine the exact decomposition of the program:  Do we put everything in one class? Do we write several classes that interact with each other? And if so, which parts of the solution belong in which class?

One way to think of a class is as a service provider, and to view the methods in a class as the particular services that provider offers.  In our DrJava Solution 2, we created a class called CelsiusToFahrenheit, which provided the service double convert(double celsius) to us.  When thinking of mathematical functions in general, what entity is the service provider, and which services does it offer?  In our object-oriented world, the function itself acts as service provider, and the service it provides is the mapping of values in the function domain to values in the function range.  Therefore, the function is represented by a class, and the ability to perform the mapping is a method in this class.  With our knowledge from the DrJava Solutions 2 and 3, we can write a class that represents a function already.  This class, for example, represents the function f(x) = 9 / 5 * x + 32:

Welcome to DrJava.
> class CelsiusToFahrenheit {
    double convert(double celsius) {
        return 9.0/5.0 * celsius + 32.0;
    }
}
> CelsiusToFahrenheit someConversionFunction = new CelsiusToFahrenheit();
> someConversionFunction.convert(100)
212.0
>

This class, CelsiusToFahrenheit, represents only one particular function, the function that maps temperatures in Celsius to temperatures in Fahrenheit.  It does not represent all functions, and this creates a problem.

We already mentioned that our program should be easily extensible, i.e. we want to be able to add as many temperature scales to the program as we want.  Therefore, our program has to work with whatever conversion function we hand it without knowing what this function actually does.  If we add another conversion function, KelvinToFahrenheit, to our program, we can demonstrate the issue:

> class KelvinToFahrenheit {
    double convert(double kelvin) {
        return kelvin * 9 / 5 – 459.67;
    }
}
> CelsiusToFahrenheit anotherConversionFunction = new KelvinToFahrenheit();
Error: Bad types in assignment

DrJava rejects the assignment of a KelvinToFahrenheit instance to a variable designed to hold a CelsiusToFahrenheit instance.  Even though to us it is clear that both of these classes represent conversion functions, which therefore can be used interchangeably, we haven’t told Java that they are interchangeable.  To find out which classes can be used instead of each other, the Java programming language uses a mechanism called nominal subtyping:  The names of the classes have to explicitly be in a special relationship to each other; it is not sufficient that both classes have the same structure, that both of them contain a double convert(double something) method (this mechanism would be called structural subtyping).  More specifically, if we want to assign an instance of class A to a variable designed for instances of class B, then A has to be a subtype of B.  Think of it this way:  You can put an apple (class A) into a basket made for fruits (class B), but you cannot put all kinds of fruits into a basket that’s only made for apples, and you definitely cannot put a loaf of bread into either basket.  That is because an apple is a subtype of a fruit, but not vice versa; and a loaf of bread is not a subtype of either an apple or a fruit.

To tell Java that these classes can be used interchangeably, we have to tell Java that “they are the same thing”, that they are both conversion functions mapping a real number (a double) to another real number.  That means we have to first describe the abstract notion of a function from real numbers to real numbers; up to this point, all of our classes have been concrete – they spelled out every little detail.  Below is an abstract description of a function in form of an abstract class.

Since we’re beginning to have quite a few classes around, this is a good time to stop using the Interactions Pane and switch to the Definitions Pane of DrJava.  We have therefore placed the abstract class AFunction below (the A stands for abstract) in a file named AFunction.java.  Unfortunately, that necessitated adding another keyword to our vocabulary:  public.  In the code below, you will see many classes and methods with keyword public in front of them.  This simply means that these classes and methods are available to the outside world.  We will discuss this in greater detail later.

// in AFunction.java
public abstract class AFunction {

    public abstract double convert(double input);
}

We have to create a separate class for the abstract notion of a function.  You can see why this class is called abstract:  The convert method doesn’t have a body!  In this class, convert is an abstract method, and as soon as one of the methods in a class is abstract, the entire class has to be considered abstract – it just doesn’t describe every detail.

Now that we have our abstract class in place, we can tell Java that both CelsiusToFahrenheit and KelvinToFahrenheit are subtypes of AFunction, that instances of both can be assigned to a variable holding an AFunction instance.  Again, we place the two classes in separate files in the Definitions Pane:

// in CelsiusToFahrenheit.java
public class CelsiusToFahrenheit extends AFunction {

    public double convert(double celsius) {
        return 9.0/5.0 * celsius + 32.0;
    }
}

// in KelvinToFahrenheit.java
public class KelvinToFahrenheit extends AFunction {

    public double convert(double kelvin) {
        return kelvin * 9 / 5 – 459.67;
    }
}

The subtype relationship between these two classes and the AFunction class is created using the extends keyword:  These classes extend the AFunction class, they add detail to it.  In this case, they implement the convert method, i.e. they specify bodies for it.  Note that Java allows us to rename the parameter:  Instead of input, we called it celsius and kelvin, respectively.  Since there are no more abstract methods left in these classes, both of them are actually concrete again; therefore, they do not have the abstract keyword in front of them.  When we compile all three of these classes, we can finally do the following in the interactions pane:

Welcome to DrJava.
> AFunction someConversionFunction = new CelsiusToFahrenheit();
> someConversionFunction.convert(100)
212.0
> someConversionFunction = new KelvinToFahrenheit();
> someConversionFunction.convert(273.15)
32.0

A single variable of type AFunction can hold both instances of CelsiusToFahrenheit and instances of KelvinToFahrenheit class.  Now we can use variables of type AFunction whenever we want the program to work with all kinds of functions.

Unfortunately, our representation of a function doesn’t do everything we need to implement our conversion functions.  We mentioned earlier that we need to be able to convert from all our temperature scales to the intermediary scale and back.  Our conversion functions must be bijections, i.e. there must be an inverse function that maps elements in the range back to elements in the domain, or mathematically:  For a function f to be a bijection, there has to be a function f -1 such that f -1 ( f(x) ) = x.  Right now, the abstract AFunction class only provides the service of computing the function for a certain value; however, we also need a service that provides the inverse function.  Therefore, we create another abstract class, ABijection that extends AFunction (meaning we can put an ABijection into an AFunction basket) and adds a second abstract method getInverse.  This method also has to be abstract since the actual inverse function is different from bijection to bijection – the inverse of CelsiusToFahrenheit is not the same as the inverse of KelvinToFahrenheit.  Here are the new abstract class ABijection and the modified versions of the concrete classes.  We also throw in a FahrenheitToFahrenheit class so that we can treat all three temperature scales – Celsius, Fahrenheit, and Kelvin – exactly the same:

// in ABijection.java
public abstract class ABijection extends AFunction {

    public abstract ABijection getInverse();
}

// in CelsiusToFahrenheit.java
public class CelsiusToFahrenheit extends ABijection {
    public double convert(double celsius) {
        return 9.0/5.0 * celsius + 32.0;
    }

    public ABijection getInverse() {
        return new ABijection() {
            public double convert(double fahrenheit) {
                return (fahrenheit - 32) * 5 / 9;
            }

            public ABijection getInverse() {
                return new CelsiusToFahrenheit();
            }
        };
    }
}

// in KelvinToFahrenheit.java
public class KelvinToFahrenheit extends ABijection {
    public double convert(double kelvin) {
        return kelvin * 9 / 5 - 459.67;
    }

    public ABijection getInverse() {
        return new ABijection() {
            public double convert(double fahrenheit) {
                return (fahrenheit + 459.67) * 5 / 9;
            }

            public ABijection getInverse() {
                return new KelvinToFahrenheit();
            }
        };
    }
}

// in FahrenheitToFahrenheit.java
public class FahrenheitToFahrenheit extends ABijection {
    public double convert(double fahrenheit) {
        return fahrenheit;
    }

    public ABijection getInverse() {
        return this;
    }
}

Note that all three classes that extend ABijection now have to implement both the convert and the getInverse methods to become concrete; if they only implemented one of them, they would remain abstract.  The convert methods of CelsiusToFahrenheit and KelvinToFahrenheit still look the same.  The getInverse methods are new, though, and require some explaining.

They each return an ABijection instance, since the inverse of a bijection is a bijection itself.  To create these instances in CelsiusToFahrenheit and KelvinToFahrenheit, we use a Java feature called anonymous inner class:  We create a new instance of some class and add to it without giving this new, more detailed class a name.  The only requirement is that the class with all the extensions is concrete.  Here, we create an instance of the abstract class ABijection, but since we implement both of its methods, the resultant class is concrete. The method definitions inside the anonymous inner classes look as expected:  The convert methods perform some arithmetic, and the getInverse methods return ABijection instances.  Since the inverses of the inverses are the original functions again – for which we do have names: CelsiusToFahrenheit and KelvinToFahrenheit – the getInverse methods simply return a new instance of them using the new keyword.  Do not despair, we will discuss anonymous inner classes in much greater detail later.

The FahrenheitToFahrenheit class is special, though:  The convert method simply returns the parameter since no conversion is actually necessary.  The getInverse method returns this – a new keyword which we haven’t encountered yet.  this always refers to the instance whose method is currently being executed.  The concept is less complicated than it sounds, especially if we anthropomorphize our objects, if we turn them into humans.  Let’s assume, for example, that we have a Person class with a walk method, and we create two instances, Alice and Bob, and ask them to walk:

Welcome to DrJava.
> class Person {
    void walk() {
        // some code that uses the this keyword
    }
}
> Person Alice = new Person();
> Person Bob = new Person();
> Alice.walk();
> Bob.walk();

While Alice.walk(); is executing, this is actually identical to Alice.  When Bob.walk(); is executing, this is identical to Bob.  this just stands for the instance that is doing the work right now.

In the getInverse method of the FahrenheitToFahrenheit class, this is identical to the instance of the FahrenheitToFahrenheit class that has been asked to provide its inverse.  But the conversion function from Fahrenheit to Fahrenheit is the identity function -- mathematically f(x) = x – so the function is its own inverse.  There is no need to create another instance, the same instance will do just fine, and for that reason, we return this.

Now we finally have everything in place to write code that converts any kind of temperature scale, represented by an ABijection, into any other scale, represented by a second ABijection.  Since Java is an object-oriented language, this code has to be part of a class:

// in Converter.java
public class Converter {
    public double convertAny(double temperature, ABijection fromScale, ABijection toScale) {
        double fahrenheit = fromScale.convert(temperature);
        ABijection inverseToScale = toScale.getInverse();
        return inverseToScale.convert(fahrenheit);
    }
}

In the convertAny method, the temperature parameter represents the numeric value of the temperature in the scale we are converting from, which is represented by the fromScale parameter.  The toScale parameter finally represents the scale we want to convert to.  The return value will be the numeric value of the temperature in that latter scale.  The first line of the body converts the temperature to Fahrenheit, which we use as intermediary scale.  The second line obtains the inverse of toScale.  This is necessary since all our classes represent bijections to the intermediary scale; here, we need the bijection away from the intermediary scale.  In the third line, we convert the temperature in Fahrenheit to the temperature in the desired scale and return that number.  In the Interactions Pane, we can now freely convert from temperature scale to temperature scale:

Welcome to DrJava.
> Converter conv = new Converter();
> ABijection c = new CelsiusToFahrenheit();
> ABijection k = new KelvinToFahrenheit();
> ABijection f = new FahrenheitToFahrenheit();
> conv.convertAny(100, c, f)
212.0
> conv.convertAny(212, f, k)
373.15000000000003
> conv.convertAny(373.15, k, c)
99.99999999999997
>

The results are not exact due to the limited precision that the computer uses to store numbers, but for most practical purposes, they are good enough.  Our solution is so extensible that we can add a new scale right here in the Interactions Pane:

> class FahrenheitPlusOneToFahrenheit extends ABijection {
    public double convert(double fplusone) {
        return fplusone - 1;
    }
    public ABijection getInverse() {
        return new ABijection() {
            public double convert(double fahrenheit) {
                return fahrenheit + 1;
            }
            public ABijection getInverse() {
                return new FahrenheitPlusOneToFahrenheit();
            }
        };
    }
}
> ABijection funky = new FahrenheitPlusOneToFahrenheit();
> conv.convertAny(212, f, funky)
213.0
> conv.convertAny(213, funky, f)
212.0

The new temperature scale that we have introduced has temperatures that are always by one degree higher than those in Fahrenheit.  We have added a new temperature scale without making any changes to any of the existing scales or the Converter class.  The user can define new scales while the program runs, or download them from the web.  This is a solution that truly scales well!