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:
byte small = 100;
int count = small;
long total = count;
double measurement = total;Common valid directions:
byte → short → int → long → float → double
↑
char ───────────┘This diagram is a mnemonic, not a promise that every step is lossless:
byte,short,chartointcan be represented exactly;inttolongcan be represented exactly;floattodoublecan exactly preserve the originalfloatvalue;inttofloat,longtofloat, andlongtodoublemay 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:
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:
double weight = 85.7;
int kilograms = (int) weight;
System.out.println(kilograms); // 85Floating-point to integer conversion truncates toward zero, not rounds:
System.out.println((int) 3.9); // 3
System.out.println((int) -3.9); // -3When converting to int or long, three more rules apply:
- Casting
NaNproduces 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:
int value = 1000;
byte narrowed = (byte) value;
System.out.println(narrowed); // -24The 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:
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:
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:
int source = 100;
// byte level = source; // Compilation error
byte level = (byte) source;Constant values that exceed the range are still rejected:
// byte invalid = 200;Unary and Binary Numeric Promotion
byte, short, and char are typically promoted to int before participating in most arithmetic operations:
byte left = 10;
byte right = 20;
// byte sum = left + right; // Compilation error: result is int
int sum = left + right;Compound assignment includes implicit conversion:
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:
- If one side is
double, the other is converted todouble; - Otherwise, if one side is
float, the other is converted tofloat; - Otherwise, if one side is
long, the other is converted tolong; - Otherwise, both sides are promoted to
int.
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:
String text = "42";
int value = Integer.parseInt(text);Invalid text will throw NumberFormatException at runtime:
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
Integer boxed = 100; // Automatic boxing
int plain = boxed; // Automatic unboxingWrapper types can be null, and unboxing null will throw NullPointerException:
Integer missing = null;
// int value = missing; // Runtime failureThis 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
| Task | Method |
|---|---|
Small integers to int/long | Widening conversion, typically without cast |
long to double | No cast required, but check precision requirements |
| Floating-point to integer | Explicit cast; truncate toward zero and handle NaN/overflow |
int to byte | Check range first, then cast |
byte + byte | The result is typically int |
| Text to integer | Integer.parseInt, handle format errors |
Integer to int | Be 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.