15-100 Lecture 3 (Wednesday, May 21, 2008)

Files From Today's Class

Vocabulary Words

Primitive (Simple) Types

A Quick Note on "floats" and "doubles"

As we mentioned earlier, Java uses "floating point" numbers to represent numbers that include a fractional component. As compared to "fixed point" numbers, the number of digits to the right of the decimal "floats" as the absolute magnitude of the number changes.

If the magnitude is very great, the precision of the fractional component is reduced. If the magnitude is very small, the precision of the fractional compentent is higher. This makes sense from a technical standpoint, becuase the same amount of memory is used, regardless of the magnitude. So, if more memory is used for the magnitude, less is available for the fraction. And, it makes sense from a user's perspective, because we care more about the fine details of the fraction on small numbers than we do on large ones. The fractions are more important when looking through a microscope than a telescope, I'd guess.

An old-school "floating point" number used a fixed number of digits to represent the number. So, there was a one-for-one trade-off between sigits used for the integer component and the fractional component. This isn't exactly the way it works with most modern computers. Instead, the decimal place doesn't necessarily move -- but the precision changes. In other words, the greater the magnitude, the more of an error might be present in the fractional component. This is sometimes called "rounding error", even though there isn't really any rounding involved. You'll learn all about the details once you get to 15-213.

For now, I'd like to note just one thing. This error means that, when doing computation, we need to think about the order in which we do it. In principle, when we do math, as long as we follow the precendence rules, the order doesn't matter. But, because of the roudning error, when it comes to floating point arithmetic, it sometimes does.

If we, for example, take a fraction, then multiply by a very large number, and then divide by another very large number and then add a fraction, we've made a mess. When we multiply the first fraction by the large number, we've encountered a "rounding error" by going from a "small number" to a larger one. When we divide by the larger number, we find ourselves back in the "small number range" and can support a lot of precision -- but we're just guessing. The real precision was "rounded away" when we multiplied by the large number. So, the result of the calculation is less precise than it need be. We are much better off doing the division fo the two large numbers first, to get the smaller result, then doing the multiplaction and addition.

Calculator Model

Today, we are going to construct a simple calculator. It works just like the "4 button" calculators many of you have seen (anyone seen one lately?) It can add, subtract, multiply, and divide.

In order to perform computation, the user enters one operand, then the operation, then the next operand. The calculator remembers the first operand and the operation until the second operand is entered, at which time it performs the computation. The result of the computation replaces the first operand in the calculator's memory.

In this way, the result of one computation can be carried forward to the next -- notice it is in the same place as was originally used for the first operand. For this reason, the place in memory where it is stored is often times called the "accumulator" -- it accumulates the results over time. In other words, it holds the present result.

The calculator displays the number most recently entered by the user, until the user asks to add, subtract, multiply, or divide. When this happens, the accumulator value is displayed. Please pay careful attention to this. It is a bit ticklish. If the user presses, "5 + 3", "3" is displayed. If the user continues "5 + 3 +", 8 is displayed.

So, we see that the calculator is, in effect, remembering three things: the accumulator value and the value that is most recently displayed. It also temporarily holds onto an input value -- but it doesn't need to remember it long, as it is promptly rolled into the accumulator by via the stored operation and also displayed.

Calculator Interface/Class Skeleton

Recall that, in English, an "interface" means, "The place where the outer surface of two things come together." In software, an "interface" describes how a piece of a program can be used by other pieces. In the context of object-oriented software, an "interface" is the set of "public" methods of an object that can be used by other objects.

Java has a special construct known as the interface that can be used to define, in a formal way, an interface that can be adopted when writing a class specification. But, we'll get to that later.

For now, we are going to begin our effort to write a calculator class specification by writing what is commonly known as a "class skeleton". It will be a class specification containing only empty method specifications. In so doing, it will define the interface of a calculator -- those methods that can be invoked by other objects.

  class Calculator {

    public void add() { }
    public void subtract() { }
    public void multiply() { }
    public void divide() { }
    public void equals() { }
    public void enterNumber(double input) { }
    public void display() { }
  }
  

What to notice? Well, a few things. First, notice that every class specification has a name. In this case, the name is "Calculator". By convention, we always capitalize the first letter of a class's name. If the class's name uses multipel words, such as "StudentAccount", we capitalize exactly the first letter of each word.

Notice the "reserved word" "class". A "reserved word" is a word that has a special meaning to Java. The reserved word, "class" indicates that we are wrtiting a class specification. It is always followed by the name of the class that we are specifying, in this case, "Calculator".

Also notice the {}-braces. When you see them, think "begin" and "end". Notice the set associated with the class specification. They enclose all of the methods specifications in the example above. They denote the beginning and the ending of the class specification. Everything within the {}-braces is said to be the "body".

Notice also the seven method specifications: add, subtract, multiple, divide, enterNumber, equals, and display. Each has its own set of {}-braces. In the next step, we will add the code within the braces to complete the methods. What goes in between those braces is said to be the body of the method specification.

Consider the method specification for enterNumber:

public void enterNumber(double input) { }

We can see that the method name is "enterNumber". In other words, the "identifier" for this method is "enterNumber". It is "public", which means that it can be used from other classes of objects (we'll talk about private methods soon enough).

Within the ()-parenthesis, we see what is known as the "formal argument list". The formal argument list tells us what needs to be given to the object allong with the request to "enterNumber" -- in this case, the number to enter. Notice that it is a "double", as opposed to, for example, an "int". We'll soon see examples of methods specifications that use more than one argument.

Notice also the Java reserved word "void". It is basically a placeholder. It indicates that when this method is done, it doesn't give anything back to the caller. Remember, for this model of the calculator, the calculator will eventually display the result. As a constract, consider the example we did in class, where was asked, "add 5 and 4" and expected a reply so that the requestor could play with the value from there. Next class, we'll see methods that have more interesting return types. In these cases, the placeholder "void" will be one of replaced by "int", "float", "char", &c.

Calculator Example

Below is the calculator example we fleshed out in class:

  class Calculator {
    private double accumulator;
    private double display;
    private char operation;

    public Calculator() {
      accumulator = 0.0;
      display = 0.0;
      operation = '=';
    }


    public void add() {
      display = accumulator;
      operation = '+';
    }


    public void subtract() {
      display = accumulator;
      operation = '-';
    }


    public void multiply() {
      display = accumulator;
      operation = '*';
    }


    public void divide() {
      display = accumulator;
      operation = '/';
    }


    public void equals() {
      display = accumulator;
      operation = '=';
      display();
    }


    public void enterNumber(double input) {
      if ('='  ==  operation)
        accumulator = input;
  
      if ('+'  ==  operation)
        accumulator = accumulator + input;
  
      if ('-'  ==  operation)
        accumulator = accumulator - input;
  
      if ('*'  ==  operation)
        accumulator = accumulator * input;
  
      if ('/'  ==  operation)
        accumulator = accumulator / input;
  
      operation = '=';
      display = input;
    }


    public void display() {
      System.out.println (display);
    }
  
  }
  

The are a bunch of interesting things to notice here. The first are the declaration of the "instance variables", those variables that exist independently in each and every instance of the Calculator:

    private double accumulator;
    private double display;
    private char operation;
  

Notice that they are qualified as being "private". This means that they can only be accessed by methods described within the Calculator class -- not from methods of other classes. This type of access protection ensures that we control their access through the methods that we write. Most instance variables will be "private". You also see the general form of a declaration: " "

The next thing you might notice is the "constructor". It looks a lot like a method specification -- but you'll notice a few differences. First, it has exactly the same name as the class -- they are both called, "Calculator". The constructor always has the same exact name, including capitalization, as the class, itself.

Second, you might notice that there is no return type specification, not even a "void". Remember, the constructor is not "called", so it cannot "return". It is simply a description of how Java should go about initializing an instance -- it is not a generally callable method. This is also the reson that we left it out of the "class skeleton" that we created above.

The constructor's basic job is to give each of the instance variables an initial value and also to do any other necessary initialization. The constructor is shown below:

    public Calculator() {
      accumulator = 0.0;
      display = 0.0;
      operation = '=';
    }
  

The add() method, shown below, is a good example of a simple method. You can see the simple assignments performed within the method body. The "assignment operator", a single =-sign, works just as it did in your math classes -- it assigns the value from the right-hand side to the variable on theleft-hand side.

    public void add() {
      display = accumulator;
      operation = '+';
    }
  

The enterNumber() method, shown below, is a bit more sophisticated. Notice that the value "input" is passed in by the caller. Since input is declared within the formal argument list, we know that it's "scope" is local -- it can only be used within this method. We also know that it is initialized with a value passed in by the caller.

We also see, for the first time, the "if statement". The if statement allows for conditional execution. If the predicate is true, the body is executed. If not, the body is skipped. The predicate of the if statement is a "boolean expression". In other words, it is an expression that evaluates to true or false. If it evaluates to true, the body is executed. If it evaluates to false, the body is not executed.

  Consider the specific example below:
      if ('+'  ==  operation)
        accumulator = accumulator + input;
  

If the "operation" variable holds the "+" sign, then the assignment "accumulator = accumulator + input" is made. Otherwise it is skipped. The {}-braces are optional if only one statement is controlled by the if -- but required if there are more than one. We'll see examples of this later. But, below is an example:

Consider the specific example below: if ('+' == operation) { accumulator = accumulator + input; System.out.println ("Added the numbers."); }

The only other thing to notice is the use of '-single-quotes. Single quotes are used to enclose "char" types, single characters. We'll learn soon that "-double-quotes are used to enclose strings of characters, such as "Hello World". Since it is possible to have a "String" that only happens to contain a single character, we need to be careful to use the type fo quote that matches the declared type -- we can't base it on the lenght of the particular case.

The full method is shown below:

    public void enterNumber(double input) {
      if ('='  ==  operation)
        accumulator = input;
  
      if ('+'  ==  operation)
        accumulator = accumulator + input;
  
      if ('-'  ==  operation)
        accumulator = accumulator - input;
  
      if ('*'  ==  operation)
        accumulator = accumulator * input;
  
      if ('/'  ==  operation)
        accumulator = accumulator / input;
  
      operation = '=';
      display = input;
    }
  

Structure and Style

In Java, {}-braces denote the beginning and ending of blocks. a }-brace is never followed immediately by a semicolon. But, each "statement" within a block is ended with a semicolon. It is technically legal to place several statements on the same line -- but this is generally considered bad form as it makes the code hard to read.

Notice that the code is indented within each set of {}-braces. Each level of nexting gets anotehr level of indenting. To keep the code readable, indents should be fairly small -- just 2-3 characters. Otherwise, as the code becomes more complex and we find ourselves with nested if statements within loops within method specifications within classes, we'll waste too much real estatre on indents.

Vertical space, meaning blank lines, should be used to group related code together to make it more readable. Identifiers should always be meaningful and descriptive -- they should not be cryptic or so abbreviated as to be meaningless. As a matter of good style, when an identifier consists of multiple words, we combine them by running them together and capitalizing exactly the first letter of each word. We do not use _-underscores. The only exception is that we do not capitalize the first letter of an identifer -- except for the names of classes (and interfaces), where we do capitalize the first letter.