slanted W3C logo

Day 04

Today we look at operations with the floating-point types and what happens when types mix in arithmetic expressions.

double

We have already seen the double type used to represent real numbers. The range of values that can be represented by the type is approximately:

Minimum Value Maximum Value
-1.7 × 10308 1.7 × 10308

A double occupies 8 bytes of memory and has about 15 significant digits. All of the operators we have seen so far can be used with double types.

Closure

Operations with double can overflow. If overflow occurs, the result is set to a value representing positive infinity (if the value is positive) or negative infinity (if the value is negative).

The value 0.0 / 0.0 is called NaN or not a number. Division by 0.0 with other double values produces either positive or negative infinity.

public class DoubleClosureExamples
{
   public static void main(String[] args)
   {
      // positive overflow
      double x = 1E200;
      System.out.println(x * x);

      // negative overflow
      System.out.println(-x * x);

      // NaN
      double zero = 0.0;
      System.out.println(zero / zero);

      // positive overflow (division by 0.0)
      System.out.println(x / zero);

      // negative infinity (division by 0.0)
      System.out.println(-x / zero);
   }
}

Making Change

Suppose you pay $5.00 for three candy bars costing $1.10 each. How much change should you get back? Write a Java program that solves the problem in two different ways:

  1. for the first way, you should compute the total cost of the candy bars as (3 * 1.10)
  2. for the second way, you should just use 3.30 as the total cost of the candy bars

For both ways, you should use the subtraction operator to calculate the final result, and you should use doubles for the monetary values.

Precision

You should have gotten two different answers for the problem on the previous slide: 1.6999999999999997 and 1.7000000000000002. The problem occurs because not all decimal values can be represented exactly using binary floating point numbers (the internal representation of the primitive real number types in Java).

Do not use double to repesent monetary amounts; instead, convert everything to cents and use int or long to perform the calculatioins.

float

The float type is also used to represent real numbers but it has a smaller range and less precision than double. The range of values that can be represented by the type is approximately:

Minimum Value Maximum Value
-3.4 × 1038 3.4 × 1038

A float occupies 4 bytes of memory and has about 7 significant digits. All of the operators we have seen so far can be used with float types. A float literal is a number followed by an F or an f; for example:

Most of the time you would prefer double over float unless lack of memory is an issue.

Mixing Integer and Floating-Point Types

If you mix types in an arithmetic expression the results can be confusing. You need to apply the precedence rules and associativity rules to determine the order in which the operands are evaluated, and then apply the following set of rules:

  1. if one of the operands has type double then the other operand is promoted to type double
  2. otherwise, if one of the operands has type float then the other operand is promoted to type float
  3. otherwise, if one of the operands has type long then the other operand is promoted to type long
  4. otherwise, both operands are promoted to type int

Examples

Assume that for each of the following examples shown in the table below we have the declarations:

double xDouble = 10.0;
float xFloat = 5.0F;
long xLong = 4L;
int xInt = 2;
short xShort = 1;
Expression Evaluation
xFloat + xDouble ⇒ ((double) xFloat) + xDouble
⇒ 15.0
xFloat * xLong ⇒ xFloat * ((float) xLong)
⇒ 20F
xInt - xLong ⇒ ((long) xInt) - xLong
⇒ -2L
xShort + xShort ⇒ ((int) xShort) + ((int) xShort)
⇒ 2
+xShort ⇒ +((int) xShort)
⇒ +1
-xShort ⇒ -((int) xShort)
⇒ -1

Assignment and Mixed Types

In an assignment statement involving the primitive numeric types, the Java compiler will promote the right-hand side value (perform a widening conversion) but it will not demote the value (perform a narrowing conversion).

If you have a variable of type:

  1. double then you can assign to it any value of primitive numeric type
  2. float then you can assign to it any value of primitive numeric type except double
  3. long then you can assign to it any value of primitive integer type
  4. int then you can assign to it any value of primitive integer type except long
  5. short then you can assign to it only values of type short
  6. byte then you can assign to it only values of type byte
  7. char then you can assign to it only values of type char

Casting to Widen a Value

Occassionally, you will want to explicitly perform a widening conversion. For example, suppose you wanted to compute the average of two integers:

public class BadAverage
{
   public static void main(String[] args)
   {
      int num1 = 10;
      int num2 = 11;
      double avg = (1 / 2) * (num1 + num2);
      System.out.println(avg);
   }
}

The example computes an incorrect average because the 1 and 2 both have type int; hence int division is performed. We want the division to be performed using floating-point math, so a solution is to cast the 1 or the 2 to a floating-point type.

public class Average1
{
   public static void main(String[] args)
   {
      int num1 = 10;
      int num2 = 11;
      double avg = ((double) 1 / 2) * (num1 + num2);
      System.out.println(avg);
   }
}

In Average1 the literal 1 is cast to type double.

public class Average2
{
   public static void main(String[] args)
   {
      int num1 = 10;
      int num2 = 11;
      double avg = (1 / (double) 2) * (num1 + num2);
      System.out.println(avg);
   }
}

In Average2 the literal 2 is cast to type double.

public class BadAverageAgain
{
   public static void main(String[] args)
   {
      int num1 = 10;
      int num2 = 11;
      double avg = (double) (1 / 2) * (num1 + num2);
      System.out.println(avg);
   }
}

In BadAverageAgain the expression (1 / 2) is cast to type double, but the value of (1 / 2) is 0.

Instead of casting, an easier way to solve this problem is to use (1.0 / 2.0) or simply 0.5 instead of (1 / 2).

Casting to Narrow a Value

Occassionally, you will want to explicitly perform a narrowing conversion. For example, suppose you wanted to compute the number of hairs on a human head (see Day 02):

public class HairsOnHead
{
   public static void main(String[] args)
   {
      int diameter;
      diameter = 17;
      double fractionCovered = 0.5;
      double areaCovered = fractionCovered * 3.1415 * diameter * diameter;
      int linearDensity = 15;
      int areaDensity = linearDensity * linearDensity;
      double numberOfHairs = areaCovered * areaDensity;
      System.out.println(numberOfHairs);
   }
}

The number of hairs should be an integer value. A cast can be used to round the value down towards zero (truncate the decimal part):

public class IntHairsOnHead
{
   public static void main(String[] args)
   {
      int diameter;
      diameter = 17;
      double fractionCovered = 0.5;
      double areaCovered = fractionCovered * 3.1415 * diameter * diameter;
      int linearDensity = 15;
      int areaDensity = linearDensity * linearDensity;
      int numberOfHairs = (int) (areaCovered * areaDensity);
      System.out.println(numberOfHairs);
   }
}

Self Check

  1. Can you compile and run all of the example programs shown in today's slides? Do you understand what they are trying to show?
  2. Do you understand the concepts listed in the Summary of Key Concepts on page 35 of textbook?
  3. Can you answer the Review Questions for Chapter 1?
  4. Can you do Exercises for Chapter 1?

To Do For Next Lecture

  1. Read all of Section 2.1 Chapter 2 in the textbook.