Week 03

Variables, Formatted Output,
User Input, and the Math Library
Version 1


 


Variables: Named Compartments For Storing Values

 

In the last lecture we described a series of very simple programs to display text and the results of computations. For some uses these facilities may be adequate, but not for many. After all, you could get the results much more quickly on a calculator than by writing, compiling, and running a Java application.

The computer supplanted the calculator because it was better at very large calculations. Of course, large calculations are hard to write down as a single expression. For almost any application it is helpful to be able to store input values, intermediate results, and output values so that they may be referred to and updated in the course of a computation.

The computer, as we know, includes memory in which values can be stored. From the machine's point of view this memory is just a long list of storage positions which are accessed by specifying the number, or address of the desired cell. In higher-level languages, a variable is just a way to refer to a memory cell (or, often, a group of memory cells) by a meaningful name rather than by a numerical address.

Declaring Variables As we have said earlier, a program is a lot like a recipe. Now what is the first thing that you see in a recipe? The ingredient list. Putting the ingredients at the front allows you to skim the recipe to see roughly what it's going to be like, and also keeps you from discovering half way through the preparation that you are missing some crucial component.

Like a recipe, in most modern programming languages you begin a given block of a program with the variable declarations, a list of all the variables that will be used in that block. This serves two purposes, similar to those of an ingredients list. First, the reader can often get a rough sense of how a program works just by looking at the list of its variable declarations. Second, the compiler uses the declarations to determine how much storage it will need to ask the operating system for when it runs your program.

In order for the system to know how much space to set aside for a given variable, and how it should interpret the bit patterns in the space set aside, it needs to know what kind of value the variable is intended to refer to. Therefore, a variable declaration in Java specifies the type name and then the variable name, as in:

int foo; 
which declares a variable named foo which will be used to store an integer.

Grouping Declarations The beginning of a block of code can have as many variable declarations as you want, and they need not occur in any particular order. If you are declaring more than one variable of a given type you may put them together in a single declaration statement by giving the type name followed by a list of variable names separated by commas, as in:
int foo, bar, baz; 

Should you declare your variables individually or in groups? It's mostly a matter of taste. The style we often use is to group variables into a single declaration if they are somehow logically connected by their meaning in the program, i.e.:

int height, width, depth;
int volume, area; 


The Type of a Variable

  As we said above, the compiler needs to know what kind of values a variable will store so that it can determine how many bytes of memory it will need to ask the operating system to set aside for the variable. For example, an integer takes up four bytes of memory, while a floating point number takes six (since information must be stored for both the mantissa and the exponent).

The Integer Types: byte, short, int, and long Actually, it's not quite accurate to say that integer variables take four bytes of storage because, in fact, Java supports four different types of integer variables.

  Integer variables can be declared as either int, as above, or as byte, or short, or long. As you might expect, the difference between the four types is how much storage they use and, therefore, the range of values they can store. A byte variable, naturally, uses one byte of memory and stores values in the range -128 to 127; a short uses two bytes of memory (16 bits) and can represent values in the range -32,768 to 32,767; an int uses four bytes of memory (32 bits) and can represent values in the range -2,147,483,648 to 2,147,483,647; and a long uses eight bytes of memory (64 bits) and can represent a value in the range -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.

When you type an integer literal unadorned, it will be stored and treated as an int. If you would like to explicitly designate a literal as a long, you do it by appending the letter l to the number, as in the long literal 23456789876l.

It might seem best just to use the long type for all your integer variables. However, because most current processors work most quickly with 32-bit values, you should use int whenever its range is adequate. The shorter types should be used only when space considerations are paramount (since they may actually be slower than ints).

The Floating Point Types: float and double As with the integer types, there are actually two different types of floating point variables: float and double. In this case the types differ both in their range and their precision.

  A variable of type float is called single precision and uses four bytes (32 bits) of memory. Of that storage, the first bit determines whether the number is positive or negative, eight bits are used for the exponent (which is stored as a power of two, rather than as a power of ten), and the remaining bits are used to store the mantissa, This makes a float generally accurate to about seven digits. The smallest representable magnitude (i.e. positive or negative value) is 1.40129846432481707e-45, the largest is 3.40282346638528860e+38.

A double variable is (not suprisingly) double precision and uses eight bytes (64 bits) of storage. Values of type double are generally accurate to about fifteen digits. The smallest representable magnitude is approximately 4.94065645841246544e-324 and the largest is approximately 1.79769313486231570e+308.

While floats may be faster in some cases, doubles are probably the best choice for most applications. With only seven digits of accuracy, it is easy to get to the point of having significant round-off errors in calculations using float variables. For this reason, most of Java's buit-in math functions return double values. Java also assumes that floating point literals you write into a program are of type double. If you want to force a floating point literal to be of type float, add the letter f to the end of the literal.

  You may be wondering why we said that float values are accurate to about seven digits and then gave the minimum and maximum magnitudes to 18 digits. This has to do with the way in which decimal floating point values are encoded into binary floating point numbers. While the number of binary digits used for the mantissa is fixed (23 in the case of a float), the number of decimal digits represented can vary over a large range. Consider the fraction 1/256. In decimal this value is 0.00390625 and requires six digits of precision. But in binary it is 0.00000001 and takes only a single digit of precision. The upshot is that some values can be represented to great accuracy, but on average you can only rely on about 7 decimal digits.

In addition to the various numeric types, there are variable types for holding a variety of other kinds of data.

The Character Type The character type, char, is used to hold a single Unicode character. It is mostly used to hold user responses to prompts and the like, and when processing text data character by character. Character literals are much like string literals except that they are written with single quotes and, naturally, can include only a single character. That character can be written directly, as an escape sequence, as an ASCII code, or as a Unicode code. The following statement declares three character variables all initialized to hold the exclamation point:
char foo = '!', bar = '\041', baz = '\u0021'; 

As with string literals, there is a problem writing quotes as character literals. The double quote is no problem. It is just '"'. But for the single quote we can't just write ''' since the middle single quote would appear to close the literal rather than being in the literal. This is why the escape code \' was introduced in the table of escape sequences last week.

The String Type Variables of type String (note the capitalization) are used more commonly than characters. They can hold names, addresses, or anything that is not appropriate for storing in a number. Even things you might ordinarily think of as being numeric values, like phone numbers, social security numbers and the like, may best be stored in strings as you will have more control over their formatting.

So, for example, we might declare:

String name = "Josh Hodas",
       phone = "1 (909) 621-8650",
       dog = "Rufus"; 

  To be totally precise, String is not a type, it is a class. But we'll ignore that subtlety for now since it makes little difference in how it is used.

The Boolean Type The boolean type is very simple, it has only two values: true and false. Boolean values most commonly occur in Java programs because they are the values computed by the relational operators and are expected by the conditional statements (both of which will be explained in the next lecture). You might use a variable of type boolean as a flag to control the behavior of your program, doing one thing if the variable holds the value true and another if it holds the value false. Such a variable might be declared as:
boolean flag = true; 

  Note that the values of the boolean type are not strings holding the words true and false. The literal values are written without quotes.


Assigning A Value To A Variable

  Once a variable has been declared you can use its name anywhere you could use a literal of the same type: in arithmetic expressions, as an argument to a method, etc. In each case the variable stands for whatever value is stored in it. Therefore, they are not much use until you have stored a value in them.

The Java assignment operator is the equals sign, =. An assignment statement consists of a variable name, followed by the assignment operator, followed by an expression. The effect of executing the statement is that the value of the expression on the right of the assignment operator is computed, and that value is stored in the variable named on the left.

For example, suppose we have the variable declaration:

int x, y, z; 
Then the effect of the statement:
y = 2; 
is to store the integer value 2 into the variable y. If this is followed by the statement:
x = y; 
the expression on the right is just the variable y which evaluates to 2 (since that is what we just stored in it) and that value is stored into x. To evaluate the statement:
z = 2 * x + 3; 
the computer looks up the value of x, multiplies that value by 2 and adds 3. The result, 7, is stored into z.

As a further example of using variables, the following program performs the same calculation of areas as the program in the last lecture, but using variables instead of literals:

Click Here To Run This Program On Its Own Page
// Program: VariableAreas
// Author:  Joshua S. Hodas
// Date:    July 26, 1996
// Purpose: To demonstrate the use of variables
 
class VariableAreas {
 
  public static void main(String args[]) {
 
    double squareSide, squareArea, circleRadius, circleArea;
	
    squareSide = 3;
    squareArea = squareSide * squareSide;
    
    circleRadius = squareSide / 2;
    circleArea = circleRadius * circleRadius * 3.141592653;
	
    System.out.println("Area of a " + squareSide + " by " +  
                     squareSide + "square: " + squareArea);
    System.out.println("Area of a circle of diameter " +  
                     2 * circleRadius + ": " + circleArea);
 
  }
 
}

The nice thing about this program is that now if we want to calculate the areas for a different size square and circle, all we need to change is the value assigned to the variable squareSide instead of looking for all the uses of the literals representing the side and radius.

Assignment Is Not Equality It is important to remember that, even though it may look like one, an assignment statement is not an algebraic equation. As we said before, it is executed by evaluating the expression on the right hand side using whatever the current values of the variables in the expression are, and then assigning the resulting value into the variable named on the left.
  So, even though it would be meaningless as an algebraic equation, the following is a perfectly reasonable assignment statement:
x = x + 1; 
If x is an int with the value 3 before this statement, then the expression on the right evaluates to 4 which is then assigned back into x.

Initializing A Variable You must always be careful that a variable contains a reasonable value by the time it is used in an expression. For example, in the last program the variable squareSide was assigned the value 3 before the variable was used to compute the areas. In this case we assigned it a fixed value that will be used every time the program is run. Another option, once we have covered input techniques below, is to assign an initial value based on something entered by the user.

If you attempt to use the value of a variable before a value has been assigned, the compiler will generally raise an error. It will show the offending line and point at the unitialized variable. For example, if you had the following lines starting at line 10 of the file foo.java:

int x, y;
y = x + 1; 
Then the compiler would generate an error that looked something like:
foo.java:11 Variable may not have been initialized.
    y = x + 1;
        ^ 

In many circumstances you can combine the declaration of the variable and assigning its initial value into one step. This can save a bit of typing and make things clearer, as in:

int squareSide = 3; 
We already used this technique in a couple of the examples of different types of variables above. It probably seemed perfectly natural, and you didn't think to question what it meant.

The expression used in the assignment is called the initializer, and can be any expression as long as it only uses literals and the variables already declared. Thus we could use the following declaration after, but not before, the last one:

int bigSquareSide = 2 * squareSide + 1; 
Initializations can be used in grouped declarations as well. The following statement declares two integer variables initialized to the values 3 and 4 respectively:
int y = 3, z = y + 1; 


Being Careful About Type Ranges

  As we said earlier, the type of a variable determines how much memory it takes up, and, hence, the range of possible values it can hold. You need to be conscious of the limited range of each type, and be careful not to attempt to assign a value to a variable that would exceed the variable's type's range. The Java compiler will catch certain obvious mistakes. For example, if you have a variable foo of type int (which you'll recall accepts values from -2,147,483,648 to 2,147,483,647) and then include the statement:
foo = 2147483648; 
then when the compiler reaches that line it will generate a Numeric Overflow error.

On the other hand, if you use the statements:

foo = 2147483647;
foo = foo + 1; 
which have the same effect, the compiler will happily accept them. What happens when you run the program? Since it would be too costly (in terms of time) for Java to check every calculation for underflow and overflow conditions, the addition is computed without complaint. However, the result is unexpected: the variable foo is left with the value -2,147,483,648. Generally, when a value goes too far in one direction to be represented properly in a given type, it wraps around to the other end of the type's range.

Note that some languages (notably, ML, which is studied in CS 131) are entirely type safe and will stop your program with a runtime error whenever you violate a type's range.


Type Casting

  When an actor plays the same kind of role a few times in a row, he may find himself the victim of type casting, unable to get any other sort of role. This seems to be happening to Ron Silver, a brilliant character actor (I recommend Enemies: a Love Story in particular). Lately he has been cast as the villain in a series of science fiction movies (Lifepod, Time Cop, and The Arrival come to mind). If some other kinds of roles don't come along soon, game shows may be the next step.

Of course this has absolutely nothing to do with Java! In Java, type casting refers to the act of replacing a value of one type in an expression with an equivalent value of another type.

Automatic Type Casting We have already talked last week about type casting in the context of integer values being promoted to floating point values if the other argument to a binary operator is a floating point value. Promoting a value to a type with a broader range is always safe (since no information is lost) and there are several contexts in which Java will do it automatically. Automatic promotion follows these rules:
  • If a byte or short value is used with any of the Java operators, it is first promoted to an int. The exceptions to this rule are the increment and decrement operators, which are described below.

  • If an int value is combined with a long value in an expression, it is first promoted to a long.

  • If a value of any integer type is combined with a float value in an expression, it is first promoted to a float.

  • If any numeric value is combined with a double value, it is first promoted to a double.

    Recall that floating point literals, unless explicitly marked as a float are of type double. So, combining any expression with an unadorned floating point literal will produce a double.

  • If a value of one integer type is passed as an argument to a method (more on that in a couple of weeks) that is expecting a value of a "higher" integer type, the value is automatically promoted to that "higher" type.

  • If a value of type float is passed as an argument to a method that is expecting a value of type double, the value is automatically cast to double.

  • If a char value is combined with a numeric value in an expression, it is first converted to an int value holding the decimal Unicode code of the character.

    So, for example, since the exclamation point is ASCII and Unicode code decimal 33, the expression '!' + 2 evaluates to the int value 35.

  • If a String and a char are added together, the result is a String consisting of the original string with the given character either prepended or appended, depending on whether the character was on the left or right of the addition operator.

  • If a numeric value is added to a String, the result is a String consisting of the original String with the String representation of the numeric value either prepended or appended, depending on whether it was on the left or right of the addition operator.

  We have already relied on the last type casting rule in several circumstances where we have used it to simplify the structure of output statements. As we have said, when you write, for example:
System.out.println("This is the number thirty-three: " + 33); 
as an abbreviation for:
System.out.print("This is the number thirty-three: ");
System.out.println(33); 
This is not a special version of System.out.println. That method is just printing the single value it is sent. But in the first case, that single value is the result of the addition:
"This is the number thirty-three: " + 33 
which, using the type casting rule, involves first converting the number 33 to the string representation of that number, "33", and then appending that string to the string "This is the number thirty-three: " to get:
"This is the number thirty-three: 33" 

  All of these promotion rules also apply when a value of the "lower" type is simply assigned into a variable of the "higher" type. For example, if a numeric value is assigned to a string variable, it is the string representation of the numeric value that is actually assigned.

Manual Type Casting The rules listed above are the only circumstances in which Java will automatically cast a value of one type to another type. Sometimes it may be useful or necessary to cast a value downwards. If that is what is desired, it must be signaled explicitly by the programmer, since it may be an unsafe operation that will result in a loss of information. Other times, an upward cast is desired in a situation where it will not be done automatically by the system.

To signal an explicit cast request, you simply put the name of the type you wish to cast to in parentheses immediately before the expression whose value you want to cast. Here are a few typical examples:

  • Suppose the char variable c holds a given character, and we wish to advance it to the next character. The expression c + 1 is an int, due to the automatic casting rules. To assign this back into c, it must first be cast back to a char. The desired statement is:
    c = (char) (c + 1); 
    
    Notice that the expression c + 1 has been put in parentheses. If this were not done, then the cast would only have applied to the value of the variable c, which is already a char, and thus the cast would have had no effect.

  • When we first introduced automatic casting in last week's notes, we used the example of 4 / 3 which evaluated to 1. The solution was to type 4.0 / 3 instead. But suppose both these values were in int variables. You can't just add .0 after the variable name. The solution is to use an explicit cast, as in:
    int foo = 4, bar = 3;
    System.out.println((float) foo / bar); 
    
    Note that the explicit cast is being applied to one of the variables before the division is computed. If the expression were written (float) (foo / bar) the division would be done as integer division, and the result, 1, would then be cast to 1.0.

  • Since almost all computations involving byte or short values will cause a cast upwards to int, if you want to assign the result back into a small type you will need to manually cast it downwards. Thus if b is a byte variable, the assignment
    b = b + 1; 
    
    will lead to the following compiler error:
    Incompatible type for =.
    Explicit cast needed to convert int to byte. 
    
    because the value of b was cast to an int in order for the additon with 1 to be performed and the result can't necessarily be safely stored into a byte.

    The solution is to use the following assignment:

    b = (byte) (b + 1); 
    
    You'll notice that this example is alot like the one with char above. In fact, because of its behavior, many C and C++ programmers tend to think of char as just another small integer type. We do not see any advantage in that point of view, however, and recommend against it.

  Note that some books (and even these notes) may get sloppy and refer to casting as a process of converting a value from one type to another, but this is not accurate. The original value is unchanged. Rather, a new value in the new type is put in its place in the overall expression. In particular, if it is a variable that is being cast, it is the variable's value as used in the expression that is really cast; the variable retains its original type and value.


A Word About Variable Names

  When you pick a name for a variable, that name should be as helpful and descriptive as possible. While it may seem that long names are cumbersome to type, in the long run they will save you time and effort. Good variable names will often obviate the need to add a lot of comments to a program. Deep in the heart of a lengthy method, the assignment statement:
x3 = x2 * x1; 
is likely not to mean much to the reader. A comment would help:
x3 = x2 * x1;     // Compute tax on annual income 
But it would be much clearer if the statement just read:
tax = taxRate * annualIncome; 

As with program and class names, a variable name is subject to the rules for Java identifiers, so it can be any string of alphabetic characters, digits (other than as the first character), or the characters $ and _, as long as it is not one of the Java reserved words.


More Arithmetic Operators

  Last lecture we introduced the basic arithmetic operators. Now we will cover a few more.

Operator Assignment Shorthand Changing the value of a variable so that it is some amount more or less than its current value turns out to be a very common operation. For that reason, Java includes a special version of the assignment operator that simplifies typing and executing such operations. For any variable var and expression exp, the statement
var += exp; 
is equivalent to the statement
var = var + exp; 
There are variations of this shorthand for each of the arithmetic operators: +=, -=, *=, /=, and %=.

Increment and Decrement Operators C programmers (all these operators were born in C) hate to type. And the operation of replacing a variable's value with one more or less is so common (you'll see why in the section on loops a couple of weeks from now) that they found even the += method to much for them. Therefore, there is an even shorter shorthand for these operations: the increment operator and the decrement operator. For any variable var, the statement
var++; 
is equivalent to the statement
var = var + 1; 
Similarly,
var--; 
is equivalent to the statement
var = var - 1; 

The increment and decrement operators can actually be put either before or after the variable name. If they come after, as above, they are called post-increment and post-decrement. If they come before, as in ++var or --var, they are called pre-increment and pre-decrement.

  As statements, there is no difference between the pre- and post- uses of the operators. But the operators can also be used as part of a larger expression, in which case there is a big difference. If the pre-increment operator is applied to a variable as part of a larger expression, then the variable is incremented and it is the incremented value that is used in the larger expression. If, on the other hand, the post-increment operator is used, then the variable is incremented, but it is the value from before the incrementing that is used in the larger expression. So, pre-increment means "increment me and then take my value", whereas post-increment means "take my value and then increment me." The same rules apply to pre- and post-decrement.

To make this more concrete, assume we have the declaration:

int foo, bar, baz; 
and consider the behavior of the following statements:
foo = 1;
bar = ++foo;
baz = foo++; 
The variable foo obviously starts with the value 1. The assignment to bar requires evaluating the expression ++foo. This increments foo to now hold the value 2, and that is the value of the expression, which is now assigned to bar. To make the assignment to baz, the expression foo++ must be evaluated. This increments the value held by foo to 3, but the value of the overall expression is 2, the value of foo from before it was incremented. Thus the final values of foo, bar, and baz are 3, 2, and 2, respectively.

  Be careful not to get confused and merge the two techniques now available for incrementing a variable:
foo = foo + 1; 
and
foo++; 
and end up writing something like:
foo = foo++; 
What is the result of executing that statement? Suppose foo starts out with the value 3. In order to determine the value to assign to the variable, we first evaluate the right hand side of the assignment. Evaluating foo++ increments the value of foo to 4. But as an expression its value is the value of foo before it was incremented. This value, 3, is then assigned into foo. So the net effect is as though no statement had been evaluated. The statement:
foo = ++foo; 
has a similar, though less problematic issue. Can you figure out what happens when it executes?

  Note that the increment and decrement operators are the only operators that do not cause a byte or short variable's value to be cast to an int. That is, if a is a byte variable, then while
a = a + 1; 
will not compile (you must cast the sum back to a byte first),
a++; 
compiles fine.

Bitwise Logical Operators Java includes a large set of operators designed to allow you to examine and manipulate the contents of integer variables on a bit-by-bit basis, ignoring the higher-level types they represent. As we will not have any use for such manipulations in this course, we will not cover them here. If you are interested in the details, they are covered in most Java reference books.


The Math Package

  In addition to the simple arithmetic operators, Java provides a large set of math functions in the package java.lang.Math. Like the java.lang.System package, Math is available automatically, without any special effort on the programmer's part. The Math package provides the following methods (which we have grouped together into families of related methods):

Trig Functions These methods each take a double value representing an angle size (in radians) as an argument. They each evaluate to a double value containing the appropriate result.
  • Math.sin -- Evaluates to the sine of the given angle.

  • Math.cos -- Evaluates to the cosine of the given angle.

  • Math.tan -- Evaluates to the tangent of the given angle.

These all behave just as you'd expect. So, for example,

double x = Math.cos(3.141592653); 
will assign the value -1.0 to the variable x.

Inverse Trig Functions The first three of these methods each takes a double value and returns, as a double, the size of the corresponding angle in radians.
  • Math.asin -- Evaluates to the angle whose sine is the given value.

  • Math.acos -- Evaluates to the angle whose cosine is the given value

  • Math.atan -- Evaluates to the angle whose tangent is the given value

  • Math.atan2 -- This method takes two double arguments representing a cartesian coordinate pair. That pair of coordinates is equivalent to a polar coordinate pair consisting of a radius and angle. The angle (in radians) is the value returned (as a double).

    In general, Math.atan(y/x), where y and x are double values or variables, is equal to Math.atan2(y,x), even when x is zero. The presence of this method is largely a throwback to systems in which division by zero caused an overflow.

  While it is generally true that these functions are inverses of the ordinary trig functions, applying a trig function and then applying its inverse to the result will not always yield the original value back due to the potential for possible round-off errors due to the fixed number of bits used to store floating point values.

Exponential Functions
  • Math.exp -- Takes a double as an argument and evaluates to a double containing the value of e (the base of the natural logarithms) raised to the given value.

  • Math.pow -- Takes two double arguments and returns, as a double, the value of the first raised to the power given as the second argument. I.e., if x and y are variables of type double, the call Math.pow(x,y) exaluates to x raised to the y power.

Inverse Exponential Functions
  • Math.log -- Takes a double as an argument and evaluates to a double containing its natural logarithm.

  • Math.sqrt -- Takes a double as an argument and evaluates to a double containing its square root.

Rounding Functions These methods all perform some variation of rounding a floating point value to an integer. However, as you will see, by "integer" we mean in the mathematical sense, not necessarily the type sense.
  • Math.floor -- Takes a double as an argument and returns as double the largest integer value not greater than the argument.

    Note that while the return value is a whole number, it is returned as a double. That is, calling math.floor on 2.65 will return 2.0 rather than 2. This is because none of the integer types has great enough range to guarantee the ability to return the appropriate value.

    This method can also be used to truncate a floating point number to some set number of digits after the decimal point. For example, if you want to truncate the value in the double variable x to have at most two digits after the decimal, multiply it by 100, take the Math.floor of the result, and then divide by 100. That is:

    x = Math.floor(x * 100) / 100; 
    

  • Math.ceil -- Takes a double as an argument and returns as double the smallest integer value not less than the argument.

  • Math.rint -- Takes a double as an argument and returns as double the nearest integer value to the argument.

    If two double values that are mathematically integers are equally close, the method returns the one that is even. That is, Math.rint(3.5) returns the value 4.0, but Math.rint(2.5) returns 2.0.

  • Math.round -- Like rint this method's purpose is to round to the nearest integer value. However, it returns that value in an integer variable rather than a floating point variable. The method has slightly different behavior, depending on the type of the argument passed to it. (Because it accepts more than one type for an argument, but varies its behavior based on the type, this is another example of an overloaded method like System.out.println.)

    If the argument is a float, then the rounded value is returned as an int. If the rounded value is outside the range of the int type then the largest or smallest int value is returned, whichever is appropriate.

    If the argument is a double, then the rounded value is returned as an long. If the rounded value is outside the range of the long type then the largest or smallest long value is returned, whichever is appropriate.

Other Functions
  • Math.abs -- Takes a numeric argument and returns its absolute value. The type of the return value is the same as the argument type.

  • Math.min -- Takes two numeric arguments and returns the smaller one. If both arguments are of type int the result is also an int. Similarly if both arguments are, long, float, or double, then that is the type returned. If the types are mismatched, the argument of "lower" type (not to be confused with the argument with the lesser value) is promoted to the "higher" type, and that is the return type of the result.

  • Math.max -- Takes two numeric arguments and returns the larger one. The behavior is the same as min with respect to types.

  • Math.random -- This is a method of no arguments (i.e. you call it by sending the message Math.random()). Each time it is called it returns a double containing a random value greater than or equal to 0.0 and less than 1.0.

    While this may seem of limited use, you can use it to generate floating point or integer random numbers in any range you want by a little manipulation. For example, if we want an integer choice between 1 and 10 inclusive, take the random double value returned, multiply it by 10 (yielding a floating point value greater than or equal to 0.0 and less than 10.0), add 1.0 (yielding a floating point value greater than or equal to 1.0 and less than 11.0), apply Math.floor to truncate the part after the decimal (yielding 1.0, or 2.0, or ... 10.0), and, finally, cast the result to an integer type.

    Note, the random numbers generated by Math.random are actually psuedo-random numbers, meaning that they are generated algorithmically and the value of the next one depends on the value of the one before it. They are, however, uniformly distributed over the range, and should be adequate for most purposes.

Math Constants In addition to all of these functions, the Math package also defines two useful constants. These are both double variables. However, they are defined in such a way that you cannot assign new values to them.
  • Math.E -- An approximation of the base of the natural logarithms, this constant has the value 2.7182818284590452354.

  • Math.PI -- An approximation of the ratio between the circumference of a circle and its diameter, this constant has the value 3.14159265358979323846.

    Using this, in the program given earlier, we could replace the line:

    circleArea = circleRadius * circleRadius * 3.141592653; 
    
    with the line:
    circleArea = circleRadius * circleRadius * Math.PI; 
    


Formatted Output Using HMCSupport.out

 

Because Java was designed mostly with applet construction in mind, Java's designers did not bother to make the System.out object very powerful, since they didn't think it would get used much. In this course, however, we will be working almost exclusively with applications, and will use character-based input and output a fair amount. Therefore, we have chosen to develop a replacement for System.out, which we call HMC.HMCSupport.out. We could have just replaced System.out with our output object and never told you about the change, but this way you will not be mislead into thinking that System.out has features it does not, which could cause problems if you began to program in Java in another environment.

  Note that because of the way in which it is implemented, you shoud not mix the use of System.out and HMCSupport.out in a given program. The results may be unpredictable.

Importing the Package As mentioned earlier, the System package is a sub-package of the package java.lang. However, all of the sub-packages of the package java.lang are loaded automatically during compilation. Otherwise, it would be necessary to refer to System.out by its full name, java.lang.System.out each time you wanted to send that object a message. Since HMCSupport is a sub-package of HMC, to use its println method you need to refer to it as HMC.HMCSupport.out.println. This quickly becomes tiresome. The solution is to explicitly import the sub-package we want at the beginning of the compilation. This is done by adding the command:
import HMC.HMCSupport; 
below the introductory comments and above the rest of the program.

You can add as many import statements to a program as you'd like. If a package has many sub-packages and you'd like to import them all, you put an asterisk in place of the sub-package name. For instance, the effect of having all the sub-packages of java.lang available automatically is due to an implicit statement of the form:

import java.lang.*; 
at the beginning of every Java program.

Primitive Output HMCSupport.out provides the same basic output routines as System.out. If none of the special methods described below are used, then the behavior of the print, and println methods is essentially the same as those of System.out. However, several additional methods are provided that allow you to control the formatting of output in several ways.

Columnar Output It is often useful to be able to specify that a bit of output take up a set number of spaces on the line, no matter how wide the value being printed actually is. Consider, for example, the following program, which prints the multiplication table for the numbers 1, 2, 4, and 6:

Click Here To Run This Program On Its Own Page
// Program: BadMultTable
// Author:  Joshua S. Hodas
// Date:    August 26, 1996
// Purpose: To demonstrate formatting problems
 
class BadMultTable {
  public static void main(String args[]) {
 
    System.out.print(1*1);
    System.out.print(" " + 1*2);
    System.out.print(" " + 1*4);
    System.out.println(" " + 1*6);
 
    System.out.print(2*1);
    System.out.print(" " + 2*2);
    System.out.print(" " + 2*4);
    System.out.println(" " + 2*6);
 
    System.out.print(4*1);
    System.out.print(" " + 4*2);
    System.out.print(" " + 4*4);
    System.out.println(" " + 4*6);
 
    System.out.print(6*1);
    System.out.print(" " + 6*2);
    System.out.print(" " + 6*4);
    System.out.println(" " + 6*6);
 
  }
}

The output from this program is:

1 2 4 6
2 4 8 12
4 8 16 24
6 12 24 36 
which is not very attractive. To get the columns to line up using the standard output routines we would need to manually add extra spaces as needed, which is cumbersome, and awkward. However, HMCSupport.out has a method HMCSupport.out.setWidth which allows you to set the number of spaces the output from each subsequent call to print should use. If a value being printed is narrower than the previously specified field width, then the value will have enough spaces added before it to make the whole output the correct width. If the value being printed is wider than the specified field width, then the field width is ignored and the whole value is printed. Calling this method affects all further output with HMCSupport.out until it is invoked again with a new field width. To turn off this control, just call setWidth with zero as an argument (since all output will be at least zero characters wide).

We can use this method to fix the multiplication table. We will just specify that all the values be printed in fields three characters wide. This will make the columns line up, and will also do away with the need to include spaces explicitely in the output messages.

Click Here To Run This Program On Its Own Page
// Program: GoodMultTable
// Author:  Joshua S. Hodas
// Date:    August 26, 1996
// Purpose: To demonstrate formatting with setWidth
 
import HMC.HMCSupport;
 
class GoodMultTable {
  public static void main(String args[]) {
 
    HMCSupport.out.setWidth(3);
    
    HMCSupport.out.print(1*1);
    HMCSupport.out.print(1*2);
    HMCSupport.out.print(1*4);
    HMCSupport.out.println(1*6);
 
    HMCSupport.out.print(2*1);
    HMCSupport.out.print(2*2);
    HMCSupport.out.print(2*4);
    HMCSupport.out.println(2*6);
 
    HMCSupport.out.print(4*1);
    HMCSupport.out.print(4*2);
    HMCSupport.out.print(4*4);
    HMCSupport.out.println(4*6);
 
    HMCSupport.out.print(6*1);
    HMCSupport.out.print(6*2);
    HMCSupport.out.print(6*4);
    HMCSupport.out.println(6*6);
 
  }
}

The output from the new program is:

  1  2  4  6
  2  4  8 12
  4  8 16 24
  6 12 24 36 
Which is a considerable improvement.

Text Justification

By default, when you specify a field width using setWidth, the output is padded on the left, so that the value is right-justified in the column. You may find yourself in a situation in which you would like the padding to go on the right, so that the value is left-justified. The current justification setting can be changed using the methods HMCSupport.out.setLeftJustify, HMCSupport.out.setRightJustify, and HMCSupport.out.setCenterJustify. All of these methods are called with no arguments. As with setWidth the effect of a call to one of these methods persists until it is replaced by a call to one of the other two.

Pad Characters When the output is padded, the default padding character is the space. This can be changed with a call to HMCSupport.out.setPadChar with a single character argument. For example, the following program prints the first and twelfth entries in the table of contents for a non-existent book:

Click Here To Run This Program On Its Own Page
// Program: TableOfContents
// Author:  Joshua S. Hodas
// Date:    August 26, 1996
// Purpose: To demonstrate justification and padding
 
import HMC.HMCSupport;
 
class TableOfContents {
  public static void main(String args[]) {
 
    HMCSupport.out.setPadChar('.');
 
    HMCSupport.out.setWidth(20);
    HMCSupport.out.setLeftJustify();
    HMCSupport.out.print("Chapter 1");
 
    HMCSupport.out.setWidth(5);
    HMCSupport.out.setRightJustify();
    HMCSupport.out.println(1);
 
    HMCSupport.out.setWidth(20);
    HMCSupport.out.setLeftJustify();
    HMCSupport.out.print("Chapter 12");
 
    HMCSupport.out.setWidth(5);
    HMCSupport.out.setRightJustify();
    HMCSupport.out.println(123);
 
  }
}

It's output is the two lines:

Chapter 1...............1
Chapter 12............123 

Make sure you read the program carefully to understand how each line effects the output generated.

Controlling Numeric Output Another limitation of System.out is that you cannot control the number of digits of precision used when printing floating point values. (As mentioned earlier, this is compunded by the bug in early versions of Java that caused System.out to print double values as though they were float values.)

When using HMCSupport.out, two methods can be used to control the presentation of floating point values. Calling HMCSupport.out.setPrecision with an integer argument sets the maximum number of digits to the right of the decimal point that should be shown when floating point values are displayed. If a displayed value has fewer fractional digits than the number specified with setPrecision then the output is unaffected; if it has more fractional digits, then the number is truncated (not rounded) as necessary. To disable truncation, returning the HMCSupport.out to its default behavior, call setPrecision with zero as its argument.

The last additional output control method provided by HMCSupport.out is HMCSupport.out.setFixed, which expects a boolean argument. When set to false, which is the default setting, there is no special behavior. When it is called with the argument true, then from that point on, floating point values with fewer fractional digits than specified by the last call to setPrecision are printed with zeroes added to the right to bring them up to the correct number of digits.

The following program demonstrates the behavior of these commands:

Click Here To Run This Program On Its Own Page
// Program: PrecisionControl
// Author:  Joshua S. Hodas
// Date:    August 27, 1996
// Purpose: To demonstrate control of floating point output
 
import HMC.HMCSupport;
 
class PrecisionControl {
  public static void main(String args[]) {
 
    HMCSupport.out.setWidth(10);
 
    HMCSupport.out.println(3.4);
    HMCSupport.out.println(3.456);
    HMCSupport.out.println(34.56);
    HMCSupport.out.println();
 
    HMCSupport.out.setPrecision(2);
 
    HMCSupport.out.println(3.4);
    HMCSupport.out.println(3.456);
    HMCSupport.out.println(34.56);
    HMCSupport.out.println();
 
    HMCSupport.out.setFixed(true);
 
    HMCSupport.out.println(3.4);
    HMCSupport.out.println(3.456);
    HMCSupport.out.println(34.56);
    HMCSupport.out.println();
 
  }
}

It's output is:

       3.4
     3.456
     34.56

       3.4
      3.45
     34.56

      3.40
      3.45
     34.56 

  Note that both HMCSupport.out.setFixed and HMCSupport.out.setPrecision will only effect the formatting of numbers that are sent as the only parameter to a call to HMCSupport.out.print or HMCSupport.out.println. That is, if we have a double variable x then in the output from the statement:
HMCSupport.out.println(x); 
these controls will effect the formatting of the value of x printed, but in the statement:
HMCSupport.out.println("Some Label Text: " + x); 
they will not have any effect. The reason is that in the latter case the value of x will be converted to an equivalent string and appended to the label text before the println method ever gets a hold of it. By that time it is too late for the format of the number to be modified as it is buried inside the larger string. For the same reason, the rounding error discussed in the last lecture will occur in the latter example, even though HMCSupport.out is being used in favor of System.out

Output Is Buffered One thing that takes a little while for beginning programmers (in any language) to get used to is that in modern text-oriented systems the programs that you write don't generally have direct access to the input and output hardware of the computer. Instead, when you send a message to System.out to display something on the screen, it in turn makes a request of the operating system to do the job for it. In most cases, the operating system doesn't comply immediately, but instead takes the characters that are supposed to be displayed and puts them in an area of memory called a buffer that has been set aside for that purpose. At some later time of the system's choosing it will flush the buffer and display anything in it on the screen.

Sometimes it is important to insure that the buffer has been flushed at a certain point. Suppose you are having trouble with a program that keeps crashing, and are trying to figure out at which point the failure is occuring. The quick-and-dirty solution is to add a few debugging output statements to the program that just display an indication that the program got that far. Well, if the program crashes but the buffer has not been flushed, you can't tell how far it got before crashing. Maybe it didn't get as far as a given debugging statement, or maybe it did but the output was still stuck in the buffer when the program crashed.

Most languages therefore provide a command that either forces the operating system to flush the output buffers immediately, or forces the program to wait until they have been flushed. In the core Java output routines the method is System.out.flush. In order to simplify things, HMCSupport.out has been designed to force the flushing of the output buffers every time it prints something. If, however, you are concerned about output execution speed, this can be disabled using the method HMCSupport.out.setAlwaysFlush which expects a boolean argument. If you have disabled automatic flushing and wish to force the buffers to flush, use HMCSupport.out.flush, which is a method of no arguments.


User Input Using HMCSupport.in

 

User Input Makes Programs Interesting It is hard to imagine an interesting program that doesn't interact with the user in some way. After all, unless it is based on the use of random values generated at run time, it will always do exactly the same thing each time it is run. Unfortunately, while the output support in System.out is weak, the support for getting input from the user provided by System.in is almost non-existent.

System.in is designed almost exclusively for string input. Getting numerical input requires reading a string and then using various conversion methods to get the desired type out of it. This can be cumbersome, and, in this course, is a low-level detail that distracts from the tasks at hand. Therefore, we have provided HMC.HMCSupport.in for use in this course. It provides a much broader and easier-to-use range of input routines.

  Note that, as with the output objects, you should not mix the use of System.in and HMCSupport.in in a given program. The results may be unpredictable.

Input Is Buffered It is important to understand that, similar to the situation with output buffering, in general your program is not in direct contact with the keyboard. As the user types, the operating system copies their input into a buffer in memory. When you use the input methods to read tokens, they are taken from that buffer. This allows the user to type ahead of the program's requests. Also, because the user's input is not generally put into the input buffer until they hit the return key, it is possible for the user to back up within a line and fix errors without the program having to do anything special to accomodate that task.

While there are ways to establish a direct connection with the keyboard, the programming involved makes these techniques beyond the scope of this course..

Basic Input Methods There are five basic input methods provided by HMCSupport.in (all of which are methods of no arguments).Each begins its work in the same way. Starting at the position that the input pointer (which keeps track of how much of the buffer has already been processed by previous input commands) was left in the input buffer after the last input, the input routine first begins by looking at the character at the pointer to see if it is a whitespace character. If it is, the routine consumes it (i.e., advances the input pointer) and repeats the process consuming all the whitespace characters until it hits a non-whitespace character. It then reads the next token of input. That is, all the characters up to the next whitespace character. The behavior of each routine varies at that point:

  • HMCSupport.in.nextWord -- Takes the token of input from the input stream and returns it as a String.

  • HMCSupport.in.nextInt -- Takes the token of input from the input stream. If that string of characters can reasonably be interpreted as an int value, then that value is returned as an int.

    If the token cannot be interpreted as an int value, then the token is pushed back onto the front of the input stream (so that it will be read again the next time an input method is called) and the method returns the value 0.

  • HMCSupport.in.nextLong -- Takes the token of input. If that string of characters can reasonably be interpreted as a long value, then that value is returned as an long.

    If the token cannot be interpreted as a long value, then the token is pushed back onto the front of the input stream and the method returns the value 0.

  • HMCSupport.in.nextFloat -- Takes the token of input. If that string of characters can reasonably be interpreted as a float value, then that value is returned as an float.

    If the token cannot be interpreted as a float value, then the token is pushed back onto the front of the input stream and the method returns the value 0.0.

  • HMCSupport.in.nextDouble -- Takes the token of input. If that string of characters can reasonably be interpreted as a double value, then that value is returned as an double.

    If the token cannot be interpreted as a double value, then the token is pushed back onto the front of the input stream and the method returns the value 0.0.

The following program once again computes the areas of a square and a circle. This time it uses HMCSupport.in to get the cross-section from the user. Notice that a bit of informative output has been added to the beginning of the program. If a program will interact with the user, you should always give them an overview up front as to what the program does. Also note that the prompt that is printed to elicit input is displayed with HMCSupport.out.print (rather than HMCSupport.out.println) so that the user's entry follows it on the same line.

Click Here To Run This Program On Its Own Page
// Program: InputAreas
// Author:  Joshua S. Hodas
// Date:    August 26, 1996
// Purpose: To demonstrate the use of user input
 
import HMC.HMCSupport;
 
class InputAreas {
 
  public static void main(String args[]) {
 
    double squareSide, squareArea, circleRadius, circleArea;
 
    HMCSupport.out.println("This program computes the areas"
                           + " of a square and circle of a"
                           + " given cross-section.");
    HMCSupport.out.println();
 
    HMCSupport.out.print("Enter the cross-section: ");
 
    squareSide = HMCSupport.in.nextDouble();
    squareArea = squareSide * squareSide;
 
    circleRadius = squareSide / 2;
    circleArea = circleRadius * circleRadius * Math.PI;
 
    HMCSupport.out.println();
    HMCSupport.out.print("Area of a " + squareSide + " by " +
                     squareSide + " square: ");
    HMCSupport.out.println(squareArea);
    HMCSupport.out.print("Area of a circle of diameter " +
                     2 * circleRadius + ": ");
    HMCSupport.out.println(circleArea);
 
  }
 
}

Detecting Bad Input How can you tell if the user typed a legitimate input value in response to some prompt? Testing to see if the answer is not zero will only work if zero is not a valid response, otherwise you can't tell if the user typed zero, or if they typed an invalid response that caused the input method to return zero. Therefore, HMCSupport.in also provides a method that enables you to test if the last input attempt succeeded or not.

If you call the method HMCSupport.in.good (with no arguments) it will return true if the last input attempt was successful, and false if the token read was not of the appropriate type.


The CLASSPATH Environment Variable

  If you try to use the objects in HMCSupport and get the error message Undefined variable: HMCSupport from the compiler, it means that the Java compiler was unable to find the necessary classes. The Java compiler looks for classes in a variety of directories. The first place it looks is in a special subdirectory of the directory in which the Java compiler resides. After that, the list of places it looks is determined by the value of the CLASSPATH variable in your Unix environment.

When you first set up your account to use Java, the setup program should have configured your account so that your CLASSPATH variable has the value .:/home/cs/cs5/java. This tells the compiler to continue its search for classes first in the current directory, and then in the directory /home/cs/cs5/java. That is where we have stored any packages that we will Supply for use in the course.

You can confirm that your CLASSPATH variable is properly set by typing

printenv CLASSPATH 
at the Unix command line. If you get no response, or the wrong response, use emacs add the line:
setenv CLASSPATH  .:/home/osiris/cs/cs5/java 
to the end of the file .tcshrc in your home directory.

Last modified August 28 for Fall 99 cs5 by fleck@cs.hmc.edu


This page copyright ©1998 by Joshua S. Hodas. It was built with Frontier on a Macintosh . Last rebuilt on Wed, Sep 16, 1998 at 10:32:17 PM.
http://www.cs.hmc.edu/~hodas/courses/cs5/week_03/lecture/lecture.html