CoSTARS Note #6:
Doubles , Booleans, and Chars
(And Other Primitives, too!)

Reminder:  when we are not using graphics, we start from this gray/green code:

public class SampleCode {
    public static java.util.Scanner scanner = new java.util.Scanner(System.in);
    public static void main(String[] args) {
       // place your code here!
                               
    }
}

1.  Doubles
     (or "Floating Point Numbers")
     (or "Numbers with Decimals")

So far, all our variables have been integers.  Java supports other types of variables, too.  For example, you can use floating point numbers in Java.  These are numbers with decimals, such as 3.14159.  These values are not integers, and so cannot be stored in integer variables.  For example, this code will not work:
      int x = 3.14;  // WILL NOT WORK!
When we declare the variable x to be of type "int", we are requiring that it only hold integer values.  So it cannot hold the value 3.14.

So we need a new type of variable that can hold floating point numbers.  For historical reasons, this is called a "double".  Despite the peculiar name, this is a handy variable type.  It works like this:
      double d = 3.14;

Doubles differ from ints in several ways:

1)  Doubles can hold decimal values (like 3.14) and ints cannot.
In particular, doubles can hold fractions between 0 and 1 (like 0.5), and ints cannot.  For example:
      double d = 1;
      int x = 1;
      x /= 2;
      d /= 2;
      System.out.println(x);  // prints 0

      System.out.println(d);  // prints 0.5

2)  Doubles have a much larger range than ints.
The largest positive int is about 2 billion, but the largest positive double is about 10308 (which is way larger!).  For example:
      System.out.println(Integer.MAX_VALUE);  // prints 2147483647
      System.out.println(Double.MAX_VALUE);   // prints 1.7976931348623157E308

Also, the smallest positive int is 1, but the smallest positive double is about 10-324, also written as 1/10324 (which is very close to zero).  For example:
      System.out.println(Double.MIN_VALUE);   // prints 4.9E-324

3)  Doubles are approximate and ints are exact.
Unlike ints, Java stores doubles as approximate values rather than exact values.  For example:
      double d = (29 / 7.0) * 7.0;
Here, the value "d" seems like it should equal 29 (seeing as we divide 29 by 7 and then multiply the result by 7 again).  But this is not quite what happens, as we see when we print out the value:
      double d = (29 / 7.0) * 7.0;
      System.out.println(d); // prints 29.000000000000004, not 29
So we see that Java introduced a very small round-off error of 0.000000000000004.  This is very small indeed!  But it is non-zero, and leads to a serious problem when comparing doubles, as we will discuss in a moment (once we learn about boolean variables).

4)  Doubles have some special constant values that ints do not have.
Unlike ints which have no special constant values, doubles have three special constant values.  Two of these represent positive and negative infinity.  These are Double.POSITIVE_INFINITY and Double.NEGATIVE_INFINITY.  The third value is Double.NaN, which stands for "Not a Number", which is the result of various illegal operations. Perhaps strangely, these three values are all valid values for double variables to hold.  For example:
      double d1 = Double.POSITIVE_INFINITY;
      double d2 = Double.NEGATIVE_INFINITY;
      double d3 = Double.NaN;
      System.out.println(d1+d1);  // prints out Infinity (which is positive infinity)
      System.out.println(d2+d2);  // prints out -Infinity (which is negative infinity)
      System.out.println(d1+d2);  // prints out NaN (not a number)
      System.out.println(d1+d3);  // prints out NaN (not a number)
Do you see why each of the last two lines prints out NaN?

5)  Doubles do not throw exceptions the same way ints do.
As you know, in integer math, Java throws an exception when you divide by zero.  For example:
      System.out.println(5/0);
This code will throw a DivisionByZeroException, as expected.  However, this is not what happens with double math:
      System.out.println(5.0/0.0);
Instead of complaining about dividing by zero, this actually succeeds, and prints out Infinity!  Why?  Well, if you divide 5.0 by a very small number, say, 0.00000001, you will get a very large number, like 500000000.  The smaller the denominator, the larger the result.  By this logic, if we divide by zero, we should get infinity, which is just what Java does!  And by the same logic:
      System.out.println(-5.0/0.0);
prints out -Infinity.

There are other cases where Java gives surprising results:
      System.out.println(0.0/0.0);
In this case, Java has two different rules to follow:  0.0 divided by any number equals 0, but any (non-negative) number divided by 0.0 equals infinity.  Not knowing whether to evaluate to 0 or infinity, Java does neither, instead evaluating to NaN ("not a number").  Fascinating!


More practice:

2.  Booleans

So far, all our variables have been numbers -- ints or doubles.  Java supports non-numeric variables, too.  The simplest non-numeric type is "boolean".  A boolean variable can hold one of two values:  true or false.  For example:
      boolean b1 = true;
      boolean b2 = false;
      System.out.println(b1); // prints true
      System.out.println(b2); // prints false

Equality Operators

Just as we have operators that produce numeric results, like +, -, *, /, and %, we also have operators that produce boolean results (true or false).  For example, we can use an equality operator to test if ints are equal using "==" (two equal signs in a row):
      boolean b1 = (1 == 1);  // does 1 equal 1?  Yes!
      boolean b2 = (1 == 2);  // does 1 equal 2?  No!
      System.out.println(b1); // prints true
      System.out.println(b2); // prints false

And just as we can test whether two ints are equal using ==, we can use the other equality operator to test of ints are not equal using "!=" (an exclamation followed by an equals sign):
      boolean b1 = (1 != 1);  // does 1 NOT equal 1?  No!
      boolean b2 = (1 != 2);  // does 1 NOT equal 2?  Yes!
      System.out.println(b1); // prints false
      System.out.println(b2); // prints true

Equality Operators and Booleans

While it may seem strange at first, we can use equality operators to compare two boolean values in the same way that we compare two numeric values.  For example:
      boolean b1 = (true == true);  // does true equal true?  Yes!
      boolean b2 = (true == false); // does true equal false?  No!
      System.out.println(b1); // prints true
      System.out.println(b2); // prints false

Equality Operators and Doubles

As we noted above, doubles are stored as approximate values.  This can lead to very strange behaviors when doubles are combined with equality operators.  For example:
      double d1 = (28.0 / 7.0) * 7.0;
      double d2 = (29.0 / 7.0) * 7.0;
      boolean b1 = (d1 == 28.0);
      boolean b2 = (d2 == 29.0);
      System.out.println(b1); // prints true
      System.out.println(b2); // prints false
It is not surprising that b1 is true -- after all, to find d1, we take 28.0, divide it by 7.0, and multiply the result by 7.0.  It seems natural that we obtain 28.0 as our result, so we expect (d1 == 28.0) to be true.  And so it is!

However:  when we follow the same logic using 29.0 rather than 28.0, we still expect that (d2 == 29.0), but it does not!  Instead, d2 equals 29.000000000000004.  This is very close to 29.0, but not exactly 29.0.  And so (d2 == 29.0) is false!

Let's see this same example presented slightly differently:
      double d = 28.0;
      System.out.println(((d / 7) * 7) == d);  // prints true
      d = 29.0;
      System.out.println(((d / 7) * 7) == d);  // prints false

So we see that the expression (((d/7)*7) == d) is true for some values of d and false for others.  How confusing!  Indeed, it is very confusing and almost always a bad idea to use equality operators with double values.  So have this rule:

Rule:  Never use equality operators with double values!

Relational Operators

Besides testing for equality or inequality, we can compare numbers based on their order using the relational operators <, >, <=, and >=.  For example:
      System.out.println(1 <  0); // prints false
      System.out.println(1 >  0); // prints true
      System.out.println(1 <= 0); // prints false
      System.out.println(1 >= 0); // prints true

      System.out.println(1 <  1); // prints false
      System.out.println(1 >  1); // prints false
      System.out.println(1 <= 1); // prints true
      System.out.println(1 >= 1); // prints true

      System.out.println(1 <  2); // prints true
      System.out.println(1 >  2); // prints false
      System.out.println(1 <= 2); // prints true
      System.out.println(1 >= 2); // prints false

Boolean Operators (aka Logical Operators)

We can also combine boolean values using boolean operators (also called logical operators).  You may recall that all logical expressions can be constructed using some combination of AND's, OR's, and NOT's.  For this reason, Java includes these three operators:  the AND operator (&& -- two ampersands), the OR operator (|| -- two vertical bars), and the NOT operator (! -- an exclamation mark).  These work as expected, according to the rules of logic that we previously covered.  For example:
      System.out.println(true && true);   // true  AND true  is true
      System.out.println(true && false);  // true  AND false is false
      System.out.println(false && true);  // false AND true  is false
      System.out.println(false && false); // false AND false is false

      System.out.println(true || true);   // true  OR true  is true
      System.out.println(true || false);  // true  OR false is true
      System.out.println(false || true);  // false OR true  is true
      System.out.println(false || false); // false OR false is false

      System.out.println(!true);          // NOT true is false
      System.out.println(!false);         // NOT false is true

We can combine equality, relational, and boolean operators in interesting ways.  For example:
      int n = scanner.nextInt();
      boolean isOdd = (n % 2 != 0);
      boolean isPositive = (n > 0);
      boolean isOddPositive = isOdd && isPositive;
      System.out.println(isOddPositive);
This code reads in an integer, and prints out "true" if that integer is both positive and odd (as opposed to even), and false otherwise.  Study it carefully.

More practice:

3.  Scanning and Printing Doubles and Booleans

We already saw in the examples above that you can print the values of doubles and booleans using System.out.print and System.out.println  What about reading these values from the user?

Just as we can use the scanner.nextInt to read ints, we can use scanner.nextDouble to read doubles and scanner.nextBoolean to read booleans.  For example:
      System.out.print("Enter an integer: ");
      int x = scanner.nextInt();
      System.out.println("You entered: " + x);


      System.out.print("Enter a double: ");
      double d = scanner.nextDouble();

      System.out.println("You entered: " + d);

      System.out.print("Enter a boolean: ");
      boolean b = scanner.nextBoolean();

      System.out.println("You entered: " + b);
Run this a few times, and try various values for each type of variable.  Then answer these questions:

4.  The Math Class

So far, we have learned about various math operators, such as +, -, *, /, and %.  Java provides even more built-in math functions by way of the Math class.  For example, Java provides a method Math.sqrt() that computes the square root of its argument:
      System.out.println(Math.sqrt(5)); // prints 2.23606797749979

Note that you must preface the method name with "Math." when using these built-in methods in the Math class, so this will not work:
      System.out.println(sqrt(5)); // WILL NOT WORK -- must be Math.sqrt(5)

Besides useful methods like Math.sqrt, Java also provides two useful constants -- Math.E (Euler's number, the base of the natural logarithm) and Math.PI:
      System.out.println(Math.E);  // prints 2.718281828459045
      System.out.println(Math.PI); // prints 3.141592653589793

What built-in Math methods does Java provide?  Here is a table with some of them:

Method Description
Math.sqrt Returns the square root of its argument.  For example:
      System.out.println(Math.sqrt(5));  // prints 2.23606797749979
Math.random Returns a random number between 0 (inclusive) and 1 (exclusive).  For example:
      System.out.println(Math.random());
This prints a different random number every time you run it.
Math.min Returns the minimum of its two arguments.  For example:
      System.out.println(Math.min(3,4)); // prints 3
      System.out.println(Math.min(4,3)); // also prints 3

Note that the minimum of two ints is also an int, whereas the minimum of two doubles is a double.  So we have:
      System.out.println(Math.min(3,4));     // prints 3
      System.out.println(Math.min(3.0,4.0)); // prints 3.0 (not 3)
Math.max Returns the maximum of its two arguments.  For example:
      System.out.println(Math.max(3,4)); // prints 4
      System.out.println(Math.max(4,3)); // also prints 4

Note that the maximum of two ints is also an int, whereas the maximum of two doubles is a double.  So we have:
      System.out.println(Math.max(3,4));     // prints 4
      System.out.println(Math.max(3.0,4.0)); // prints 4.0 (not 4)
Math.abs Returns the absolute value of its argument.  For example:
      System.out.println(Math.abs(5));  // prints 5
      System.out.println(Math.abs(-5)); // also prints 5

Note that the absolute value of an int is also an int, whereas the absolute value of a double is a double.  So we have:
      System.out.println(Math.abs(5));    // prints 5
      System.out.println(Math.abs(-5.0)); // prints 5.0 (not 5)
Math.ceil Returns the "ceiling", or the smallest (closest to negative infinity) integer that is greater than or equal to its argument.  For example:
      System.out.println(Math.ceil(2.01)); // prints 3.0
      System.out.println(Math.ceil(2.00)); // prints 2.0
      System.out.println(Math.ceil(1.99)); // prints 2.0

Note that the value computed by Math.ceil cannot be assigned directly into an integer variable, because the value is actually a double value that happens to be an integer.  So:
      double d = Math.ceil(1.2); // ok
      int x = Math.ceil(1.2);    // WILL NOT COMPILE

Math.floor Returns the "floor", or the largest (closest to positive infinity) integer that is less than or equal to its argument.  For example:
      System.out.println(Math.floor(2.01)); // prints 2.0
      System.out.println(Math.floor(2.00)); // prints 2.0
      System.out.println(Math.floor(1.99)); // prints 1.0

Note that the value computed by Math.floor cannot be assigned directly into an integer variable, because the value is actually a double value that happens to be an integer.  So:
      double d = Math.floor(1.2); // ok
      int x = Math.floor(1.2);    // WILL NOT COMPILE

(int)
Math.round
Returns the rounded integer, or the closest integer value to its argument.  For example:
      System.out.println(Math.round(2.50)); // prints 3
      System.out.println(Math.round(2.49)); // prints 2
      System.out.println(Math.round(2.01)); // prints 2
      System.out.println(Math.round(2.00)); // prints 2
      System.out.println(Math.round(1.99)); // prints 2

Note that, unlike Math.ceil and Math.floor, the value computed by Math.round is not a double.  Curiously, it also it not an "int".  Instead, it is a "long", which is a special kind of integer.  This is a type of variable that we generally do not use.  As such, it is recommended that you always convert the result of Math.round from a long to an int, by preceding the call with "(int)", as such:
      System.out.println((int)Math.round(2.50)); // prints 3
      System.out.println((int)Math.round(2.49)); // prints 2
      System.out.println((int)Math.round(2.01)); // prints 2
      System.out.println((int)Math.round(2.00)); // prints 2
      System.out.println((int)Math.round(1.99)); // prints 2

Note that the value computed by Math.round cannot be assigned directly into an integer variable unless you convert it as just described.  So:
      double d = Math.round(1.2);   // ok
      int x = (int)Math.round(1.2); // also ok
      int y = Math.round(1.2);      // WILL NOT COMPILE

Math.pow Returns the value of the first argument raised to the power of the second argument.  For example:
      System.out.println(Math.pow(2,0)); // prints 1.0 (which is 2^0)
      System.out.println(Math.pow(2,1)); // prints 2.0 (which is 2^1)
      System.out.println(Math.pow(2,2)); // prints 4.0 (which is 2^2)
      System.out.println(Math.pow(2,3)); // prints 8.0 (which is 2^3)

Note that we can use Math.pow to compute the square root of a number:
      System.out.println(Math.sqrt(5));     // prints 2.23606797749979
      System.out.println(Math.pow(5,0.5));  // also prints 2.23606797749979

Note that the value computed by Math.pow cannot be assigned directly into an integer variable, even when raising an integer to an integer power, because the value is actually a double value that happens to be an integer.  So:
      double d = Math.pow(1,2); // ok
      int x = Math.pow(1,2);    // WILL NOT COMPILE

Math.exp Returns e (Euler's number, the base of the natural logarithms) raised to the power of its argument.  For example:
      System.out.println(Math.exp(1)); // prints 2.7182818284590455 (which is E^1)
      System.out.println(Math.exp(2)); // prints 7.38905609893065 (which is E^2)

Note that we can use Math.pow with the constant Math.E in place of Math.exp:
      System.out.println(Math.exp(2));        // prints 7.38905609893065 (which is E^2)
      System.out.println(Math.pow(Math.E,2)); // also prints 7.3890560989306495(which is E^2)

Note however that double math is approximate, and so computing Math.exp with Math.pow produces very close but not identical results:
      double d1 = Math.exp(2);
      double d2 = Math.pow(Math.E,2);
      System.out.println(d1 == d2); // prints false, they differ
      System.out.println(d1 - d2);  // prints 8.881784197001252E-16
So we see that the two approaches differ by about one-quadrillionth in this case.  They are very, very close.  But not identically the same.

Math.log Returns the natural logarithm (base e) of its argument.  That is, the number x such that ex equals its argument.  For example:
      System.out.println(Math.log(Math.E)); // prints 1.0 (since e1.0 == e)
      System.out.println(Math.log(1));      // prints 0.0 (since e0.0 == 1)
      System.out.println(Math.log(0));      // prints -Infinity (since e-infinity == 0)
      System.out.println(Math.log(-1));     // prints NaN (since ex is never negative)

You can use this method to compute the logab for any base a by applying this rule:
      logab = logeb / logea
For example, the following line prints out the log381, which is the value x such that 3x == 81 (which is 4):
      System.out.println(Math.log(81)/Math.log(3)); // prints 4.0 (since 34.0 == 81)
Actually, due to the approximate nature of double math, this prints out 4.000000000000001 rather than 4.0.  Close enough!
Math.log10 Returns the base-10 logarithm of its argument.   That is, the number x such that 10x equals its argument.  For example:
      System.out.println(Math.log10(100));  // prints 2.0 (since 102.0 == 100)
      System.out.println(Math.log10(0.01)); // prints -2.0 (since 10-2.0 == 0.01)
As noted above, you can use Math.log to compute Math.log10, so this method is provided merely as a convenience.
  Note:  in general, you do not need to know much trigonometry for this course.  However, we will make simple use of some trig functions in our graphics programs, so these methods are included here mostly for reference purposes.
Math.sin
Math.cos
Math.tan
These methods return the trigonometric functions of their argument, which is an angle in radians.  For example:
      System.out.println(Math.sin(0));          // prints 0.0
      System.out.println(Math.cos(Math.PI/3));  // prints (about) 0.5
      System.out.println(Math.tan(-Math.PI/4)); // prints (about) -1.0
Math.asin
Math.acos
Math.atan
These methods return the inverses of the trigonometric functions -- that is, the "arcsine", "arccosine", and "arctangent".  They return an angle in radians.  For example, we see how the previous examples are inverted:
      System.out.println(Math.asin(0.0));  // prints 0.0
      System.out.println(Math.acos(0.5));  // prints (about) π/3
      System.out.println(Math.atan(-1.0)); // prints (about) -
π/4
Math.toDegrees
Math.toRadians
These methods convert between degrees and radians, where 180 degrees equals π radians..  They are especially useful because many students are more comfortable dealing with degrees than radians.  For example:
      System.out.println(Math.toDegrees(Math.PI/3)); // prints (about) 60
      System.out.println(Math.toRadians(-30));       // prints (about) -π/6

Naturally, these are often used in conjunction with the trigonometric functions.  For example:
      System.out.println(Math.cos(Math.toRadians(60)));    // prints (about) 0.5
      System.out.println(Math.toDegrees(Math.acos(0.5)));  // prints (about) 60

More practice:

5.  The Random Class

While Math.random() may be useful in some cases, Java includes a more powerful collection of random methods in the Random class.  For example, this class not only generates random doubles between 0 and 1, but also random integers between 0 and a value of your choosing, and even random booleans (which are great for simulated coin flips!).

In order to use the Random class, however, we need to add a new line of gray code:
    public static java.util.Random random = new java.util.Random();

So here is all our gray/green code including this new line:
public class SampleCode {
    public static java.util.Scanner scanner = new java.util.Scanner(System.in);
    public static java.util.Random random = new java.util.Random();
    public static void main(String[] args) {
       // place your code here!
                              

    }
}

Starting from this gray/green code, here is a simple example that prints out a random double between 0 and 1:
      System.out.println(random.nextDouble());

Compile this code and run it.  You should notice three differences with this new approach:

  1. The Random class requires the new line of gray code;

  2. The Random method is prefaced with "random." rather than "Math." (and the "random" is lowercase whereas "Math" is capitalized); and

  3. This particular Random method is named "nextDouble" whereas the equivalent Math method is named "random"

With that in mind, here are some useful methods from the Random class:

Method Description
random.nextDouble This method is basically equivalent to Math.random().
Returns a random number between 0 (inclusive) and 1 (exclusive).  For example:
      System.out.println(random.nextDouble());
This prints a different random double between 0 and 1 every time you run it.
random.nextBoolean Returns a random boolean (true or false).  For example:
      System.out.println(random.nextBoolean());
This prints either true or false, with equal probability.
random.nextInt There are two forms of this method.
The first form takes no argument and returns a random integer between the smallest and largest possible values -- that is, roughly between negative 2 billion and positive 2 billion.  For example:
      System.out.println(random.nextInt());

The second form takes an argument, and returns a random integer between 0 (inclusive) and its argument (exclusive).  This is a very handy method for many tasks.  For example:
      System.out.println(random.nextInt(6));
This prints out a random number between 0 and 5 (because 6 is exclusive -- it will never occur).  Often, we want a random number between 1 and some value.  For example, to randomly choose a value when rolling a 6-sided die.  For this, we just add 1 to the result, as such:
      System.out.println(1 + random.nextInt(6));
This prints out a random number between 1 and 6, as desired.
random.setSeed It can be tricky to debug a program that uses random numbers, because its behavior may change every time you run it.  Java provides a way around this.  You can use the setSeed method to force Java to use the same sequence of random numbers each time you run your program.  This method takes one argument, an integer, thus allowing you to try different sequences of numbers that seem random and yet are predictable.

For example:
      System.out.println(random.nextInt(1000));
      System.out.println(random.nextInt(1000));
      random.setSeed(0);
      System.out.println(random.nextInt(1000)); // always the same!
      System.out.println(random.nextInt(1000)); // ditto!

Each time you run this, the first two numbers will indeed be random, but the second two numbers will always be the same!  Try it a few times to confirm this.

Some practice:

6.  Chars

Besides ints and doubles, Java includes another numeric type, char, that is mainly used to represent characters in Strings.  We will use chars much more once we learn about Strings (coming soon!), but here we will begin to study them.

To assign a specific character to a char variable, we enclose that character in single-quotes (not double-quotes), like this:
      char c1 = 'A'; // assigns uppercase-A to variable c1
      char c2 = 'B'; // assigns uppercase-B to variable c2
      char c3 = 'a'; // assigns lowercase-a to variable c3
      System.out.println(c1);  // prints A
      System.out.println(c2);  // prints B
      System.out.println(c3);  // prints a

Chars are special kinds of integers, actually, so we can compare chars using equality and relational operators.  The comparison works alphabetically, so 'A'<'B', except that all uppercase letters occur before any lowercase letters, to 'B'<'a', as we see here:
      char c1 = 'A';
      char c2 = 'B';
      char c3 = 'a';
      System.out.println(c1 < c2);  // prints true, as 'A' < 'B'
      System.out.println(c1 < c3);  // prints true, as 'A' < 'a'
      System.out.println(c3 < c2);  // prints false, as 'B' < 'a'

Not only are chars special kinds of integers, we can even find the specific integer value assigned to any particular character.  Java uses Unicode, which is a code that converts characters to numbers.  The Unicode value for 'A' is 65, 'B' is 66, 'C' is 67, and so forth.  Lowercase 'a' is 97, 'b' is 98, 'c' is 99, and so on.  We can see the Unicode values of chars by first assigning those chars into integers and then printing them out, like this:
      int i1 = 'A'; // we are assigning the char 'A' into an integer variable
      int i2 = 'B'; // same for 'B'
      int i3 = 'a'; // same for 'a'
      System.out.println(i1); // prints 65, the Unicode value of 'A'
      System.out.println(i2); // prints 66, the Unicode value of 'B'
      System.out.println(i3); // prints 97, the Unicode value of 'a'

Even though we can find the Unicode values for chars, we very rarely need them.  For example, say we need to check if a character is an uppercase letter.  This is true if its Unicode value is at least 'A' (65) and not greater than 'Z' (90).  We can perform this check as follows:
      ((c >= 'A') && (c <= 'Z')) // evaluates to true if c is an uppercase letter and false otherwise
Here we will use this code to confirm that 'A' is an uppercase letter and 'a' and '$' are not uppercase letters:
      char c = 'A';
      System.out.println((c >= 'A') && (c <= 'Z')); // prints true, as 'A' is uppercase
      c = 'a';
      System.out.println((c >= 'A') && (c <= 'Z')); // prints false, as 'a' is not uppercase
      c = '$';
      System.out.println((c >= 'A') && (c <= 'Z')); // prints false, as '$' is not an uppercase letter
Note that this code does not require that we know the actual values assigned to 'A' or 'Z'.

Some practice:

7.  Other Primitive Types

So now we know about ints, doubles, booleans, and chars.  In Java, these are called primitive types.  This is because while they have a value, we cannot use them to call any methods.  By contrast, a reference type can be used to call certain methods defined for that type.  We have already seen some reference types.  Consider the scanner -- we can use that object to call the nextInt() and nextDouble() methods among others.  Or consider the page variable in our graphics programs --- we can use that object to call the fillRect() and setColor() methods among others.  With primitive types, however, we cannot call any methods on them.  So we cannot have code like this:
      double d1 = 5;
      double d2 = d1.sqrt();  // Will not compile!
This code will not work because d1 is not an object, but a primitive type, and so we cannot call any methods on it.  Of course, we can compute the square root of d1, but we do it like this:
      double d1 = 5;
      double d2 = Math.sqrt(d1);

Here, we are not calling a method on d1, but providing the value of d1 as an argument to a method.  Be sure you understand this distinction!

Are there other primitive types?  Yes.  In fact, there are 8 primitive types in Java:  ints, doubles, booleans, chars, bytes, shorts, longs, and floats.  The AP exam only covers the first four types (ints, doubles, booleans, and chars), but you should be familiar with all 8 types, so we will describe them here:

Type

AP?

Description

Size Min Max
int yes integers between -2 billion and +2 billion. 4 bytes
(32 bits)
Integer.MIN_VALUE
= -231 = -2147483648
(about -2 billion)
Integer.MAX_VALUE
= 231-1 = 2147483647
(about +2 billion)
double yes floating-point numbers with a huge range. 8 bytes
(64 bits)
-Double.MAX_VALUE
= -1.7976931348623157E308
(about -1.8 x 10308).

Also, smallest positive is:
Double.MIN_VALUE
= 4.9E-324
(about 5 x 10-324)
Double.MAX_VALUE
= 1.7976931348623157E308
(about +1.8 x 10308).

Also, largest negative is:
-Double.MIN_VALUE
= -4.9E-324
(about -5 x 10-324)
boolean yes true or false values 1 byte
(8 bits)
n/a n/a
char yes characters (like 'A' or 'z' or '&')
(Technically:  the integer Unicode values representing characters, with values between 0 and +65535.)
2 bytes
(16 bits)
Character.MIN_VALUE
= 0
Character.MAX_VALUE
= 216-1 = +65535
(about 65 thousand)
byte no integers between -128 and +127 1 byte
(8 bits)
Byte.MIN_VALUE
= -27 = -128
Byte.MAX_VALUE
= 27-1 = +127
short no integers between -32768 and +32767 2 bytes
(16 bits)
Short.MIN_VALUE
= -215 = -32768
Short.MAX_VALUE
= 215-1 = +32767
long no integers between -9 billion billion and
+9 billion billion.
8 bytes
(64 bits)
Long.MIN_VALUE
=-263
= -9223372036854775808
= about -9 billion billion
Long.MAX_VALUE
= 263 - 1
= 9223372036854775807
= about +9 billion billion
float no floating-point numbers, like doubles, only with less accuracy 4 bytes
(32 bits)
-Float.MAX_VALUE
= -3.4028235E38
(about -3.4 x 1038).

Also, smallest positive is:
Float.MIN_VALUE
= 1.4E-45
(about 1.4 x 10-45)
Float.MAX_VALUE
= 3.4028235E38
(about +3.4 x 1038).

Also, largest negative is:
-Float.MIN_VALUE
= -1.4E-45
(about -1.4 x 10-45)

This may be daunting at first, but a few observations make it much easier to digest:

  1. Booleans are not numeric.
    Booleans can only be true or false, and not a number -- and all the other primitive types are numbers.

  2. Only floats and doubles are non-integer numeric primitives.
    All other numeric types are restricted to integers.

  3. The only unsigned integer type is char.
    There are no negative char's, so we say they are unsigned.  All other numeric types are signed -- they include both positive and negative values.

  4. We can find the min/max values with these rules:

    1. Signed values (all but char's) use 1 bit for the sign (positive or negative).
      So a k-bit signed value must use 1 bit for the sign, leaving (k-1) bits for the values.

    2. Unsigned values (just char's) do not use an extra bit for the sign
      So a k-bit unsigned value can use all k bits for the value.

    3. Even though 0 is not positive or negative, it is included with the positives.
      So there is one extra negative value than positive values in signed numerics.

    4. k bits can have 2k different values.

    5. Hence, we have these rules for integer types:

      1. k unsigned bits range from 0 to 2k-1.

      2. k signed bits range from -2(k-1) to +2(k-1)-1.

    6. We can apply these rules to each integer type like this:

      1. Bytes are 8-bit signed numerics, so k=8, and they range from -27 to +27-1.

      2. Shorts are 16-bit signed numerics, so k=16, and they range from  -215 to +215-1.

      3. ints are 32-bit signed numbers, so k=32, and they range form  -231 to +231-1.

      4. Longs are 64-bit signed numbers, so k=64, and they range form  -263 to +263-1.

      5. Chars are 16-bit unsigned numerics, so k=16, and they range from  0 to +216-1.

    Some practice:

8.  Converting Types

We often must convert between different numeric types, say converting a char into an int, or an int into a double.  Here we discuss the various types of conversions, and how to perform them.

Widening versus Narrowing Conversions

As we saw, chars can be between 0 and +65535, and ints can be between about -2 billion and +2 billion.  Thus, we can convert any legal char value into a legal int value.  For example:
      char c = 'A';           // assigns a char value (65, to be exact)
      int i = c;              // converts the char into an int
      System.out.println(i);  // prints 65

Great! However, the opposite is not true -- there are many legal int values that are illegal char values.  For example, say we are converting the int value -5 to a char.  Seeing as the smallest char value is 0, this is impossible.  So the compiler will not let you do it!  Here is the code that shows this failure:
      int i = -5;            // assigns an int value
      char c = i;            // fails to convert the int into a char (will not compile)
      System.out.println(c);

In fact, this code will fail for any value assigned to the int variable, even if it within the legal range of a char.  For example:
      int i = 'A';           // assigns an int value (65, which is a legal char value)
      char c = i;            // STILL fails to convert the int into a char (will not compile)
      System.out.println(c);

So we see that the compiler will convert chars into ints, but it will not convert ints into chars.

Because every char value is a legal int value, we say that ints are wider than chars (or, if you prefer, that chars are narrower than ints).  A conversion from a narrower type to a wider type is called a widening conversion (and the other way around is a narrowing conversion).

The key:  Java will automatically perform widening conversions, but will not automatically perform narrowing conversions.

So:  which conversions are widening, and which are narrowing?  We already saw that chars are narrower than ints, because some legal int values are not legal char values.  To answer this question, we will construct the following table which shows the ranges for each primitive numeric type:

value double float long int char short byte
-1.8 x 10308              
-3.4 x 1038              
 about -9 billion billion              
about -2 billion              
-32768              
-128              
0              
+127              
+32767              
+65535              
about +2 billion              
about +9 billion billion              
+3.4 x 1038              
+1.8 x 10308              

This table clearly shows which types are narrower than other.  A type is narrower than a second type if it is missing a bar in this table where the second type has a bar.  From this, we see that:

One surprising result is that two types can each be narrower than the other!  For example, chars are narrower than bytes but bytes are also narrower than chars.

Types of Conversions

Here we cover the four different ways in which primitive type conversions may occur.

1)  Assignment Conversions

An assignment conversion occurs when we assign a value of one type into a variable of another type.  For example:
      int i = 5;
      double d = i;   // Assignment conversion!
This example converts the integer 5 into the double 5.0 and then assigns that value (5.0) to the double d.

Assignment conversions must be widening conversions (such as from int to double).  Assignment conversions cannot be narrowing conversions (such as from doubles to ints).  For example:
      double d = 5.0;
      int i = d;  // Fails:  narrowing assignment conversion

2)  Method Invocation Conversions

A method invocation conversion occurs when we assign an argument of one type to a parameter of another type in a method call.  For example:
      int i = 5;
      double d = Math.sqrt(i);   // Method invocation conversion!
This example assigns the integer value 5 to the only parameter of the Math.sqrt method, but that method expects its argument to be a double, so Java first converts the integer (5) into a double (5.0), and then calls the method.

Method invocation conversions must be widening conversions (such as from int to double).  Method invocation conversions cannot be narrowing conversions (such as from doubles to ints).  For example:
      page.fillRect(0,0,10,20.5); // Fails:  narrowing method invocation conversion
The fillRect method expects 4 integers for the left, top, width, and height.  Here, we provided a double for the height, and doubles are wider than integers, so this fails.

3)  Promotion Conversions

A promotion conversion occurs when we perform arithmetic on values with different types.  For example:
      double d = 1.5 + 2;   // Promotion conversion!
To add 1.5 (a double) and 2 (an integer), Java first makes these values have the same type by promoting 2 from an integer to a double, then adding 1.5 and 2.0, the result of which is a double.  Notice that there is no assignment conversion here -- the right-hand-side is already a double before it is assigned to the double

Because Java always promotes the narrower type to the wider type, promotion conversions are always widening conversions (such as from int to double).

4)  Casting Conversions

The previous three cases are all automatic:  when necessary, Java automatically performs a widening conversion.  What about narrowing conversions?  Java will never perform these automatically.  But sometimes we must perform a narrowing conversion.  In this case, we must explicitly direct the compiler to do the conversion by casting to the narrower type.  For example:
      double d = 5.0;
      int i = (int) d;        // Legal narrowing conversion with casting

      System.out.println(i);  // prints 5
This example compiles and runs without errors.  How does it work?  On the second line, we precede the value d with "(int)" -- that is, the type int enclosed in parentheses.  This awkward syntax is how we cast the double into an int.  The result of  (int)d   is an int where the double d has been truncated.  That is, its value after the decimal point is simply ignored.  For example:
      System.out.println((int)4);    // prints 4 (not 4.0)
      System.out.println((int)4.9);  // prints 4 (not 5)
      System.out.println((int)4.5);  // prints 4 (not 5)
      System.out.println((int)-4.9); // prints -4
Here we see that casting to an integer is not the same as rounding, nor taking the floor nor ceiling.  In all cases, casting to an integer will simply truncate a value after the decimal point.

What happens if we cast a value that is out of range?  For example:
      System.out.println((byte)200);  // overflow!  prints -56
Here, we try to cast the value 200 from an int into a byte, but the largest byte is +127.  This is a case of overflow, and so the result is bogus.  In this case, the bogus result happens to be -56 (which, of course, is not equal to 200).  In general, if you cast a value that is outside the range of the target type, the result will be incorrect.

As an aside, we now can understand why we use "(int)" in a call to Math.round.  It turns out that Math.round is the only Math method that takes a double parameter and yet returns a long result.  Since converting a long into an int is a narrowing conversion, we cannot do this automatically, so we must cast the result to an int.  This is precisely what we do when we precede the call with "(int)", as such:
      int i = (int)Math.round(2.50); // rounds 2.5 to the long 3, then casts to the int 3
      System.out.println(i);          // prints 3

Illegal Conversions

There are some conversions that Java will not perform, even with casting.  In particular, Java will not convert between boolean values and numeric values.  For example:
      boolean b = true;
      int i = (int) b;  // Fails:  illegal conversion from boolean to int

Here, we try to convert the boolean value "true" into an integer value.  We may think this is reasonable (and may expect, say, a value of 1 as a result), but Java will not let us do this.

Other Conversions

Converting character digits to ints

While not strictly conversions in the technical sense, there are other ways in which we may wish to change primitive types.  For example, say we have a char value that we know is a decimal digit ('0', '1', ..., '9'), and we wish to convert it into that decimal value.  This is not the same as directly converting the char into an int, because in that case (as we have already seen) we will obtain the Unicode value for that character.  That is:
      char c = '0';
      int i = c;
      System.out.println(i);  // prints 48

This prints 48, not 0, because 48 is the Unicode value for the character '0'.  To convert to the decimal value 0, we must subtract the Unicode value of '0' (that is, 48).  So we have:
      char c = '0';
      int i = c - '0';
      System.out.println(i);  // prints 0

This seems to work.  Let's try it with the character '3', which has Unicode value 51:
      char c = '3';
      int i = c - '0';
      System.out.println(i);  // prints 3

This prints out 3, and not 51.  So we conclude that we can find the decimal value of a character digit by subtracting '0' and converting to an int.

Converting uppercase to lowercase

As another example, here we convert an uppercase letter to its lowercase equivalent.  This is not a conversion, technically, because we start and end with a char.  Now, because 'A' is 65 and 'a' is 97, we may observe that we just need to add their difference, or 32, to any uppercase letter to get its lowercase equivalent.  However, we wish to do this without using the actual Unicode values, so instead we express the difference as ('a' - 'A').  This is still 32, but does not require us to know that value.  So we have:
      char c1 = 'A';
      char c2 = c1 + ('a' - 'A');  // fails: illegal narrowing conversion from int to char
      System.out.println(c2);
This code will not compile!  What went wrong?  On the second line, when we add c1 and ('a' - 'A'), even though all the values are chars, the result of the operation is an int.  The compiler will not automatically convert this int back into a char when assigning the value to c2.  Fortunately, we know what to do about this:  we simply cast the result back into a char, like this:
      char c1 = 'A';
      char c2 = (char)(c1 + ('a' - 'A'));  // converts uppercase into lowercase
      System.out.println(c2);  // prints 'a'
This seems to work.  Let's try it with uppercase 'Q':
      char c1 = 'Q';
      char c2 = (char)(c1 + ('a' - 'A'));  // converts uppercase into lowercase
      System.out.println(c2);  // prints 'q'
This prints out q, as desired.  So we conclude that we can convert from uppercase to lowercase by adding ('a' - 'A') and casting the result to a char.

Converting booleans to ints

As a final example, we noted that it is illegal to convert from booleans to ints, even with casting.  Yet there are times when we wish to do precisely this.  In this case, we can make use of the tertiary operator (?:).  This is a strange syntax, and your use of it should be limited.  But here is an example that uses the tertiary operator to convert boolean values into integers, using 0 for false and 1 for true:
      boolean b = true;
      int i = (b ? 1 : 0);    // uses tertiary operator to convert boolean to int
      System.out.println(i);  // prints 1
      b = false;
      i = (b ? 1 : 0);        // once again, now converting false to 0
      System.out.println(i);  // prints 0
In general, the tertiary operator takes a boolean value followed by two other values.  If the boolean is true, the operator evaluates to its second value, otherwise it evaluates to its third value.  From this, you should understand how the given example works.  Again, you should use this operator sparingly, if at all.

Some practice:


carpe diem  -  carpe diem  -  carpe diem  -  carpe diem  -  carpe diem  -  carpe diem