skip to content

How do widening rules apply inside arithmetic expressions (numeric promotion), and what surprises does that cause?

level: seniorimportance: should knowfreq 48%

answer

  1. Smaller-than-int promotes to int (unary)
  2. Binary: double>float>long>int wins, floor int
  3. byte+byte is int -> needs cast back
  4. += hides a narrowing cast
  5. 1/2==0; widen an operand before dividing/multiplying

basics

~20 s

In math expressions Java automatically widens small types to at least int before computing. So byte + byte gives an int, and mixing an int with a double makes the whole thing a double. This is why byte b = b1 + b2; needs a cast.

solid answer

~50 s

Java applies numeric promotion before arithmetic. Unary numeric promotion widens any operand smaller than int (byte, short, char) up to int. Binary numeric promotion then, for two operands, widens both to the larger of the two types, with a floor of int: if either is double the result is double, else if either is float it is float, else if either is long it is long, otherwise both become int. Consequences: byte + byte is an int, so byte sum = b1 + b2; fails to compile without a cast. Compound assignment (sum += b) hides an implicit narrowing cast, so it compiles but can silently overflow. char + char is int arithmetic, so 'a' + 'b' is 195, not a string. And integer division happens before any promotion to double, so 1/2 is 0 even when assigned to a double.

code

java · 13 lines
java
byte b1 = 10, b2 = 20;
// byte sum = b1 + b2;      // ERROR: b1 + b2 is int
byte sum = (byte)(b1 + b2);  // OK

byte b = 100;
b += 50;                     // compiles; = (byte)(b + 50) -> overflow to -106

double half = 1 / 2;         // 0.0
double ok   = 1.0 / 2;       // 0.5

int x = 100000, y = 100000;
long bad  = x * y;           // 1410065408 (int overflow)
long good = (long) x * y;    // 10000000000

go deeper

for a junior

Knows that byte+byte needs a cast and that 1/2 is 0; can apply fixes by example.

for a middle

States the unary and binary promotion rules and explains the += hidden cast.

for a senior

Predicts result types of mixed expressions, diagnoses pre-widening overflow, and prescribes (long) operand widening.

for a principal

Treats promotion pitfalls as a code-review/static-analysis concern and codifies guidelines (e.g. promote-before-multiply, ban silent compound narrowing) across teams.

## The core idea: arithmetic forces widening Widening doesn't only happen on assignment -- it also happens **inside expressions**, automatically, via two rules the JLS calls **numeric promotion**. Understanding these explains a whole family of beginner surprises. ## Rule 1: Unary numeric promotion Any single operand whose type is **smaller than int** (`byte`, `short`, `char`) is widened to **int** before use. This applies to operands of `+ - * /`, the unary `-`, array indices, shift counts, etc. There is no byte arithmetic; the smallest arithmetic type is `int`. ## Rule 2: Binary numeric promotion When a binary operator (like `+`) has two numeric operands, the compiler promotes **both** to a common type using this ladder (highest wins, with a floor of int): 1. If either operand is `double` -> both become `double`. 2. Else if either is `float` -> both become `float`. 3. Else if either is `long` -> both become `long`. 4. Otherwise -> both become `int` (this is where byte/short/char land). The **result type of the expression** is that common type. ## Surprise 1: byte + byte is an int ```java byte b1 = 10, b2 = 20; byte sum = b1 + b2; // COMPILE ERROR ``` Both bytes are promoted to int, so `b1 + b2` is an `int`. Assigning an int to a byte is narrowing and needs a cast: `byte sum = (byte)(b1 + b2);`. ## Surprise 2: compound assignment hides a cast ```java byte b = 10; b += 5; // compiles! equivalent to b = (byte)(b + 5) b *= 1000; // compiles, but silently overflows/wraps ``` The JLS defines `E1 op= E2` as `E1 = (T)(E1 op E2)` where T is E1's type -- an **implicit narrowing cast is inserted for you**. Convenient, but it can silently lose data. ## Surprise 3: char arithmetic is int arithmetic ```java char a = 'a'; // code point 97 int x = 'a' + 'b'; // 97 + 98 = 195 (an int), not "ab" char next = (char)(a+1); // 'b' -- must cast back to char ``` String concatenation only happens when one operand is a `String`; with two chars you get integer addition. ## Surprise 4: integer division happens first ```java double half = 1 / 2; // 0.0, not 0.5 double ok = 1.0 / 2; // 0.5 (one operand is double -> double division) ``` Promotion is decided by the operand types **at the operator**, before the assignment. `1` and `2` are both int, so `1/2` is integer division (=0); the widening to double happens only afterward, on the already-truncated 0. ## Surprise 5: overflow stays in the promoted type ```java int a = 100000, b = 100000; long product = a * b; // 1410065408, NOT 10000000000 ``` Both operands are int, so the multiply is int arithmetic and overflows (wraps) before being widened to long. Fix: make an operand long first: `long product = (long) a * b;`. ## Mental checklist - Anything smaller than int -> promoted to int in arithmetic. - Mixed types -> result is the widest operand (min int). - Decide promotion at the operator, not the assignment target. - Compound assignment silently re-narrows. - To avoid overflow, widen an operand *before* the operation.

  • Why does long total = bigInt1 * bigInt2; overflow even though total is a long?
    Both operands are int, so the multiplication is performed in int arithmetic and overflows before the result is widened to long. Cast one operand to long first ((long) bigInt1 * bigInt2) so the multiply happens in long.
  • Why does b += 5 compile when b = b + 5 does not, for a byte b?
    Compound assignment is defined as b = (byte)(b + 5) -- the JLS inserts an implicit narrowing cast back to the variable's type. The plain b + 5 produces an int with no implicit cast, so it fails.

saying these in an interview costs you the question

  • Thinking byte + byte yields a byte
  • Expecting 1/2 assigned to double to be 0.5
  • Expecting long sum = intA * intB to avoid int overflow
  • Thinking 'a' + 'b' concatenates

context