skip to content

What surprising results can binary numeric promotion cause in mixed int/long/float/double arithmetic, and how do you avoid them?

level: middleimportance: must knowfreq 52%

answer

  1. Expression type comes from operands, not the target
  2. int/int = truncating integer division
  3. Cast an operand, not the result
  4. Use 24L * ... to avoid int overflow
  5. double loses precision for huge longs

basics

~20 s

Because both operands are promoted to a common type, an int/int division stays integer (truncates), and an int times int can overflow before being assigned to a long. Cast one operand to long or double first to fix it.

solid answer

~50 s

Binary numeric promotion picks the common type from the operands themselves, not from where the result will be stored. Two classic bugs follow. First, integer division: '5 / 2' is int/int, so both stay int and the result truncates to 2; assigning to a double afterward ('double d = 5 / 2;') still gives 2.0 because the truncation already happened. Cast first: '(double)5 / 2' gives 2.5. Second, overflow before widening: 'long ms = 24 * 60 * 60 * 1000 * 1000;' computes entirely in int, overflows around 2.1 billion, and only then widens to long — producing a wrong negative value. Promote early: '24L * 60 * 60 * 1000 * 1000' makes the whole chain long. The rule of thumb: the type of an arithmetic expression depends only on its operands; make the widest operand a literal of the type you need (use L or a (double) cast) before the operation, not after.

code

java · 7 lines
java
double bad  = 5 / 2;          // 2.0  (int division first)
double good = (double)5 / 2;  // 2.5  (cast an operand)

long wrong = 24 * 60 * 60 * 1000 * 1000;   // overflows in int -> negative
long right = 24L * 60 * 60 * 1000 * 1000;  // 86_400_000_000L

long safe = Math.multiplyExact(1_000_000_000L, 5); // throws on overflow

go deeper

for a junior

Knows int/int division truncates and that you cast to double to get a fractional result.

for a middle

Explains that expression type depends on operands not the assignment target, and fixes both truncation and overflow bugs by casting/typing an operand early.

for a senior

Discusses overflow-before-widening, double mantissa precision loss for large longs, and tools like Math.multiplyExact; reasons about evaluation order in a multiplication chain.

for a principal

Can establish team conventions and static-analysis rules to catch these, weigh BigDecimal/BigInteger vs primitive math, and reason about numeric correctness across an API surface.

## The core principle Binary numeric promotion determines the type of an arithmetic expression from the **operands**, completely independent of the variable the result is later assigned to. The assignment's type does **not** reach back into the computation. Misunderstanding this causes two of the most common Java bugs. ## Pitfall 1: integer division truncates When both operands are integral (`byte/short/char/int/long`), `/` is integer division — it discards the fractional part. ```java int a = 5, b = 2; int q1 = a / b; // 2 double q2 = a / b; // 2.0 (truncation happened FIRST, then widened) double q3 = (double)a / b; // 2.5 (a is double -> b promoted to double -> real division) double q4 = a / (double)b; // 2.5 (same, either operand works) double q5 = a / 2.0; // 2.5 (2.0 is a double literal) ``` `q2` is the trap: people expect 2.5 because the target is `double`, but the division `a / b` is int/int = 2, and only the **already-truncated** 2 is widened to 2.0. To get a real division, at least one **operand** must be floating point *before* the division. ## Pitfall 2: overflow before widening ```java long micros = 24 * 60 * 60 * 1000 * 1000; // BUG ``` Every factor is an `int`, so the whole product is computed in 32-bit `int` arithmetic, which wraps around (overflows) at about +/-2.1 billion. The intermediate value overflows to a wrong (often negative) number, and *then* that wrong value is widened to `long`. The fix is to make the arithmetic happen in `long` from the start: ```java long micros = 24L * 60 * 60 * 1000 * 1000; // 86_400_000_000L, correct ``` One `L` on the first factor is enough: `24L * 60` promotes to `long`, and every subsequent multiplication stays `long`. ## Pitfall 3: int + double precision and ordering When an `int` meets a `double`, the int is widened to double. Large longs widened to double can lose precision (double has 53 bits of mantissa), so `(double)(Long.MAX_VALUE)` is not exact. Be careful converting big integral values through floating point. ## Pitfall 4: comparison promotion `==`, `<`, etc. also promote. `0.1 + 0.2 == 0.3` is `false` due to floating-point representation, and comparing an int with a float promotes the int to float (which can lose precision for very large ints). ## How to avoid all of these 1. **Decide the target type and force it into an operand early** — use a typed literal (`L`, `0.0`, `1.0`) or an explicit cast (`(long)`, `(double)`) on one operand *before* the operator. 2. Remember: casting the **result** (`(double)(a / b)`) is too late; cast an **operand**. 3. For overflow-sensitive integer math, use `Math.multiplyExact`/`addExact` to fail fast, or compute in `long`. ## Deriving the answer For any expression: look only at the operand types, apply the double>float>long>int ladder to get the expression type, perform the operation in that type (with truncation/overflow as that type dictates), and only then apply any assignment widening.

  • Why is 'double d = 1 / 3;' equal to 0.0?
    1 and 3 are both int, so 1 / 3 is integer division = 0; that 0 is then widened to 0.0. Use 1.0 / 3 or (double)1 / 3 to get 0.333...
  • How do you make 'long total = 1000 * 1000 * 1000 * 5;' correct?
    Force long arithmetic before overflow: 1000L * 1000 * 1000 * 5. The first long factor promotes the whole chain to long, avoiding the 32-bit int overflow that the all-int version hits.

saying these in an interview costs you the question

  • Expecting 'double d = 5 / 2;' to be 2.5
  • Casting the result instead of an operand: '(double)(a/b)'
  • Assuming assigning to long prevents int overflow in the expression
  • Believing widening to double is always lossless for large longs

context