Skip to content

VOL 01 / CH 02 / LESSON 03

2.3 Numeric Conversion, Promotion, and Precision ​

Prerequisites: Range and Boundaries of Basic Types

Estimated time: 30 minutes

You are ready to continue when: You can distinguish widening, narrowing, binary numeric promotion, and string parsing.

Widening Primitive Conversion ​

Widening conversions do not require an explicit cast:

java
byte small = 100;
int count = small;
long total = count;
double measurement = total;

Common valid directions:

text
byte  → short → int → long → float → double
                ↑
char ───────────┘

This diagram is a mnemonic, not a promise that every step is lossless:

  • byte, short, char to int can be represented exactly;
  • int to long can be represented exactly;
  • float to double can exactly preserve the original float value;
  • int to float, long to float, and long to double may lose low-order precision;
  • Widening conversions do not overflow due to the target range being too small, but floating-point results may only retain an approximate value.

Actual verification:

java
long original = 9_007_199_254_740_993L;
double widened = original;
long roundTrip = (long) widened;

System.out.println(original);
System.out.println(widened);
System.out.println(roundTrip);
System.out.println(original == roundTrip);

In this example, roundTrip becomes 9_007_199_254_740_992, and the final comparison is false.

Narrowing Primitive Conversion ​

When the target type cannot represent all source values, an explicit cast is typically required:

java
double weight = 85.7;
int kilograms = (int) weight;
System.out.println(kilograms); // 85

Floating-point to integer conversion truncates toward zero, not rounds:

java
System.out.println((int) 3.9);  // 3
System.out.println((int) -3.9); // -3

When converting to int or long, three more rules apply:

  • Casting NaN produces 0;
  • When exceeding the maximum value of the target integer, the result is clamped to the maximum value;
  • When below the minimum value of the target integer, the result is clamped to the minimum value.

Conversion to byte, short, or char adds a step: first convert to int using those rules, then narrow the integer. Thus (byte) 1000.0 is -24, rather than being clamped to Byte.MAX_VALUE; (byte) Double.POSITIVE_INFINITY is -1.

Integer narrowing only retains the lower bits of the target width:

java
int value = 1000;
byte narrowed = (byte) value;
System.out.println(narrowed); // -24

The cast indicates "allow the conversion to proceed as specified," it does not imply that the result aligns with business intent. Range checks should be performed before the conversion:

java
if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
    throw new IllegalArgumentException("out of byte range: " + value);
}
byte safe = (byte) value;

Special Narrowing of Constant Expressions ​

The following compiles:

java
byte level = 100;
char marker = 65;

Because the right-hand side is a constant expression evaluated at compile time, its value falls within the range of the target type. If you were to use a regular int variable, you would need to cast it:

java
int source = 100;
// byte level = source; // Compilation error
byte level = (byte) source;

Constant values that exceed the range are still rejected:

java
// byte invalid = 200;

Unary and Binary Numeric Promotion ​

byte, short, and char are typically promoted to int before participating in most arithmetic operations:

java
byte left = 10;
byte right = 20;
// byte sum = left + right; // Compilation error: result is int
int sum = left + right;

Compound assignment includes implicit conversion:

java
byte count = 10;
count += 1;          // Approximately equivalent to count = (byte) (count + 1)
// count = count + 1; // On the right is int, Cannot be directly assigned to byte

"Approximate equivalence" is important: the left expression is evaluated only once, and the difference will become apparent when studying arrays and side effects later.

Binary numeric promotion generally follows:

  1. If one side is double, the other is converted to double;
  2. Otherwise, if one side is float, the other is converted to float;
  3. Otherwise, if one side is long, the other is converted to long;
  4. Otherwise, both sides are promoted to int.
java
int units = 5;
double factor = 2.0;
double result = units * factor;

Do not use "the result is always the largest type" as a substitute for the formal rules; string concatenation, shift operations, conditional expressions, and constant expressions have their own rules.

Numerical Conversion Does Not Equal Parsing Text ​

The cast operator only handles compatible primitive or reference type relationships and cannot directly cast a string to an integer:

java
String text = "42";
int value = Integer.parseInt(text);

Invalid text will throw NumberFormatException at runtime:

java
try {
    int parsed = Integer.parseInt("forty-two");
    System.out.println(parsed);
} catch (NumberFormatException error) {
    System.out.println("not an integer");
}

Converting numbers to strings can use Integer.toString(value) or String.valueOf(value).

Boxing and Unboxing Boundaries ​

java
Integer boxed = 100; // Automatic boxing
int plain = boxed;   // Automatic unboxing

Wrapper types can be null, and unboxing null will throw NullPointerException:

java
Integer missing = null;
// int value = missing; // Runtime failure

This is not the widening or narrowing of primitive type values, so errors should be judged separately. The chapter on collections will handle wrapper types further.

Python vs C++ Comparison ​

Python's int(3.9) is a runtime call used to construct integer values, truncating toward zero; int("42") also handles text parsing. Standard Python int supports arbitrary precision, but when converted to fixed-width library types, range issues can still occur.

C++ provides static_cast and other explicit conversion forms, as well as the C-style cast. Java's integer overflow behavior is defined by the language specification; in C++, signed integer overflow is typically undefined behavior, while unsigned integers behave as modulo operations. You cannot directly port Java experimental results into C++ reasoning.

Professional Quick Reference ​

TaskMethod
Small integers to int/longWidening conversion, typically without cast
long to doubleNo cast required, but check precision requirements
Floating-point to integerExplicit cast; truncate toward zero and handle NaN/overflow
int to byteCheck range first, then cast
byte + byteThe result is typically int
Text to integerInteger.parseInt, handle format errors
Integer to intBe aware of null unboxing risks

Before Moving On ​

For each conversion, check range, precision, and failure behavior. Before the lab, explain why byte + byte produces int, how floating-point casts truncate toward zero, and why casts, text parsing, and unboxing need separate checks.

→ Next: Variable, Type, and Conversion Experiment

Specification: JLS §5.1: Types of Conversion, §5.6: Numeric Contexts and Promotion.

Built with VitePress | Software Systems Atlas