1.5 - Casting and Ranges of Variables

superdancer16

Introduction

Not all numeric values fit neatly into categories. Sometimes you need to convert between int and double, and sometimes calculations produce values outside the allowed range for a data type. Topic 1.5 covers casting (type conversion), the limits of integer values, and the precision issues that arise with floating-point numbers.

Understanding casting, especially how it differs from automatic conversion, is crucial for the AP exam. Similarly, knowing the range limitations of int values and what happens during overflow helps you write more robust programs and debug unexpected behavior.

Casting Between Primitive Types

What is Casting?

Casting is the explicit conversion of a value from one primitive type to another using a casting operator. The casting operators (int) and (double) convert values between int and double types.

Syntax:

(targetType) value

Casting double to int

Casting a double value to an int value causes the digits to the right of the decimal point to be truncated (cut off, not rounded).

Examples:

double x = 7.9;
int a = (int) x;            // a gets 7 (not 8!)

double y = 3.14159;
int b = (int) y;            // b gets 3

double z = -2.8;
int c = (int) z;            // c gets -2 (not -3!)

double w = 9.999;
int d = (int) w;            // d gets 9 (not 10!)

Key point: Truncation is NOT rounding. The decimal portion is simply removed, regardless of its value.

Truncation visualization:

(int) 5.15
(int) 5.55
(int) 5.95
(int) 5.999995

Casting int to double

You can explicitly cast an int to a double:

int x = 5;
double y = (double) x;      // y gets 5.0

However, this explicit cast is often unnecessary because of automatic widening.

Automatic Casting (Widening)

Some code causes int values to be automatically cast (widened) to double values. This happens when:

  • An int is assigned to a double variable
  • An int is used in an expression with a double
// Automatic widening in assignment
int x = 5;
double y = x;               // y gets 5.0 (automatic conversion)

// Automatic widening in expressions
int a = 10;
double b = 3.0;
double result = a + b;      // a becomes 10.0, result is 13.0

// Mixed operations
int c = 7;
double d = c / 2.0;         // c becomes 7.0, result is 3.5

Why widening is automatic: Converting int to double never loses information (every int can be represented as a double), so Java does it automatically.

Why narrowing requires casting: Converting double to int loses the decimal portion, so Java requires explicit casting to ensure you intend this loss of precision.

Using Casting in Expressions

Casting has high precedence and applies only to the value immediately following it:

double x = 7.5;
double y = 2.3;

int a = (int) x + (int) y;
// (int) x → 7
// (int) y → 2
// 7 + 2 = 9

int b = (int) (x + y);
// x + y → 9.8
// (int) 9.8 → 9

int c = (int) x + y;
// ERROR! Cannot assign double to int
// (int) x → 7 (int)
// 7 + y → 7 + 2.3 = 9.3 (double)

Common pattern to force floating-point division:

int a = 7;
int b = 2;
double result = (double) a / b;     // 7.0 / 2 = 3.5

// Or
double result2 = a / (double) b;    // 7 / 2.0 = 3.5

// Or
double result3 = (double) (a / b);  // (double) 3 = 3.0 (not what we want!)

Rounding with Casting

Values of type double can be rounded to the nearest integer using specific formulas.

Rounding Non-Negative Numbers

For non-negative numbers, use: (int) (x + 0.5)

double a = 3.2;
int rounded1 = (int) (a + 0.5);     // (int) 3.73

double b = 3.7;
int rounded2 = (int) (b + 0.5);     // (int) 4.24

double c = 3.5;
int rounded3 = (int) (c + 0.5);     // (int) 4.04

How it works:

  • Numbers with decimal < 0.5: Adding 0.5 keeps integer part the same
  • Numbers with decimal > 0.5: Adding 0.5 bumps to next integer
  • Truncation then gives the rounded result

Rounding Negative Numbers

For negative numbers, use: (int) (x - 0.5)

double a = -3.2;
int rounded1 = (int) (a - 0.5);     // (int) -3.7 → -3

double b = -3.7;
int rounded2 = (int) (b - 0.5);     // (int) -4.2 → -4

double c = -3.5;
int rounded3 = (int) (c - 0.5);     // (int) -4.0 → -4

Why subtract for negatives? Because truncation moves toward zero. For negative numbers, moving toward zero means going up, so we need to adjust in the opposite direction.

Summary:

  • Positive: (int) (x + 0.5)
  • Negative: (int) (x - 0.5)

Integer Range and Overflow

Integer Constants

The constant Integer.MAX_VALUE holds the value of the largest possible int value:

The constant Integer.MIN_VALUE holds the value of the smallest possible int value:

System.out.println(Integer.MAX_VALUE);  // 2147483647
System.out.println(Integer.MIN_VALUE);  // -2147483648

Why Integer Values Have Limits

Integer values in Java are represented by values of type int, which are stored using a finite amount (4 bytes) of memory. Therefore, an int value must be in the range from Integer.MIN_VALUE to Integer.MAX_VALUE inclusive.

4 bytes = 32 bits possible values

These values are distributed:

  • 1 bit for sign (positive/negative)
  • 31 bits for magnitude
  • Range: approximately to

Integer Overflow

If an expression would evaluate to an int value outside of the allowed range, an integer overflow occurs. The result is an int value in the allowed range but not necessarily the value expected.

Overflow examples:

int max = Integer.MAX_VALUE;        // 2147483647
int overflow1 = max + 1;
System.out.println(overflow1);      // -2147483648 (wraps to MIN_VALUE!)

int overflow2 = max + 2;
System.out.println(overflow2);      // -2147483647

int min = Integer.MIN_VALUE;        // -2147483648
int overflow3 = min - 1;
System.out.println(overflow3);      // 2147483647 (wraps to MAX_VALUE!)

Why overflow occurs: Think of integer values as a circular number line. When you go past the maximum, you wrap around to the minimum, and vice versa.

... → MAX_VALUE → MIN_VALUE → MIN_VALUE+1 → ...

Practical overflow example:

int a = 2000000000;     // Close to max

int b = 2000000000;

int sum = a + b;        // 4000000000 > MAX_VALUE

System.out.println(sum); // Negative number (overflow!)

Important: Overflow does NOT cause an exception or error. The program continues with the incorrect wrapped value, which can lead to subtle bugs.

Avoiding Overflow

To avoid overflow in calculations:

  1. Use long for very large integers (outside AP CSA scope)
  2. Check if values are close to limits before calculation
  3. Use double if exact integer values aren't required
// Using double to avoid overflow
int a = 2000000000;
int b = 2000000000;
double sum = (double) a + b;    // 4.0E9 (correct)

Floating-Point Precision

Round-Off Errors

Computers allot a specified amount of memory to store data based on the data type. If an expression would evaluate to a double that is more precise than can be stored in the allotted amount of memory, a round-off error occurs. The result will be rounded to the representable value.

Example of precision limits:

double x = 1.0 / 3.0;
System.out.println(x);              // 0.3333333333333333 (not exact!)

double y = 0.1 + 0.2;
System.out.println(y);              // 0.30000000000000004 (not 0.3!)

double z = 1.0 - 0.9;
System.out.println(z);              // 0.09999999999999998 (not 0.1!)

Why this happens: Decimal numbers are stored in binary (base 2). Many simple decimal fractions (like 0.1) cannot be represented exactly in binary, just as cannot be represented exactly in decimal.

Avoiding Round-Off Errors

To avoid rounding errors that naturally occur, use int values when possible.

Better approach:

// Instead of dollars with decimals
double price = 10.50;
double tax = 0.05;
double total = price + price * tax;     // Potential round-off

// Use cents as integers
int priceCents = 1050;                  // 10.50 in cents
int taxRate = 5;                        // 5%
int totalCents = priceCents + (priceCents * taxRate / 100);
double totalDollars = totalCents / 100.0;

When working with money: Many financial applications use integers to represent cents to avoid rounding errors entirely.

Common Mistakes

Mistake 1: Expecting casting to round

// WRONG expectation
double x = 7.9;
int y = (int) x;            // Student expects 8
System.out.println(y);      // Actually prints 7 (truncation!)

// CORRECT for rounding
int y = (int) (x + 0.5);    // Prints 8

Mistake 2: Casting in the wrong place

int a = 7;
int b = 2;

// WRONG
double result1 = (double) (a / b);  // (double) 3 = 3.0

// CORRECT
double result2 = (double) a / b;    // 7.0 / 2 = 3.5

Mistake 3: Not recognizing overflow

int x = Integer.MAX_VALUE;
int y = x + 1;
if (y > x) // FALSE! y wrapped to negative
{                
    System.out.println("y is greater");
}

Mistake 4: Assuming double arithmetic is exact

// WRONG assumption
double a = 0.1 + 0.1 + 0.1;
if (a == 0.3) // May be FALSE due to round-off!
{             
    System.out.println("Equal");
}

// BETTER approach (check within tolerance)
if (Math.abs(a - 0.3) < 0.0001) 
{
    System.out.println("Equal");
}

Mistake 5: Forgetting automatic widening

int a = 10;
double b = a;               // No cast needed! Automatic widening
// (int) is unnecessary here

int c = 5;
double d = c / 2;           // Still integer division! d = 2.0
double e = c / 2.0;         // Automatic widening, e = 2.5

Practice Problems