How does floating-point overflow and underflow behave in Java, and how does it differ from integer overflow?
answer
- Overflow → ±Infinity, not wrap
- Underflow → subnormals → ±0.0
- 0.0/0.0 = NaN; 1.0/0.0 = Infinity
- NaN != NaN; use Double.isNaN
- float ~3.4e38, double ~1.8e308; neither throws
basics
~10 sFloating-point doesn't wrap. Too-big results become Infinity (or -Infinity); too-small results lose precision and shrink toward 0.0. Neither throws an exception. Integer overflow instead wraps around silently.
solid answer
~40 sJava's float and double follow IEEE 754, so they behave very differently from integers. **Overflow** — a magnitude larger than the type's maximum — does not wrap; it produces the special value **Double.POSITIVE_INFINITY** or NEGATIVE_INFINITY. **Underflow** — a non-zero result too small to represent — loses precision (degrades through subnormals) and ultimately rounds to **0.0** (or -0.0). Both happen silently with no exception; division by zero likewise yields ±Infinity, and 0.0/0.0 or Infinity-Infinity yields **NaN** (not-a-number). NaN is contagious and unequal to everything, even itself, so you must test it with Double.isNaN. Contrast with integers, which wrap modulo 2^N. So floating-point fails 'gracefully' into sentinel values rather than wrapping, but those sentinels silently poison later math. You detect them with Double.isInfinite / isNaN, and note that float overflows far sooner (~3.4e38) than double (~1.8e308).
code
java · 8 linesSystem.out.println(1e308 * 10); // Infinity (overflow, no exception)
System.out.println(1.0 / 0.0); // Infinity (float div-by-zero)
System.out.println(0.0 / 0.0); // NaN
System.out.println(1e-320 / 1e10);// 0.0 (underflow)
double x = 0.0 / 0.0;
System.out.println(x == x); // false! use Double.isNaN(x)
System.out.println(Double.isNaN(x)); // truego deeper
Knows floats give Infinity when too big and approach 0 when too small, and that neither throws.
Distinguishes Infinity vs NaN, knows underflow goes through subnormals to ±0.0, and tests with Double.isNaN/isInfinite.
Contrasts IEEE 754 behavior with integer wrap and integer div-by-zero, understands NaN contagion/ordering pitfalls, and validates at boundaries.
Sets numeric-type policy (BigDecimal for money, NaN/Infinity guards at API edges), reasons about precision/subnormal performance, and reviews for silent sentinel propagation.
## Two completely different number systems Java has two families of numeric types and they handle out-of-range results in **opposite** ways: - **Integers** (`int`, `long`, …) store exact whole numbers in a fixed width and **wrap around** (modulo 2^N) on overflow — silent, deterministic, two's-complement. - **Floating-point** (`float`, `double`) follow the **IEEE 754** standard. They represent numbers as sign × mantissa × 2^exponent — an approximation with a huge but finite range. They do **not** wrap; instead they overflow to **infinity** and underflow toward **zero**. ## Floating-point overflow → infinity When a result's magnitude exceeds the largest finite value the type can hold (`Float.MAX_VALUE` ≈ 3.4×10^38, `Double.MAX_VALUE` ≈ 1.8×10^308), IEEE 754 rounds it to the special value **infinity**: - `Double.POSITIVE_INFINITY` or `Double.NEGATIVE_INFINITY` (same for Float). - No exception is thrown. `1e308 * 10` → `Infinity`. `-1e308 * 10` → `-Infinity`. - **Division by zero** in floating-point also gives infinity: `1.0 / 0.0` → `Infinity`, `-1.0 / 0.0` → `-Infinity` (whereas integer `1 / 0` throws ArithmeticException — a key contrast). ## Floating-point underflow → (gradually) zero **Underflow** is the opposite end: a *non-zero* result whose magnitude is too small to represent normally. IEEE 754 first uses **subnormal (denormal) numbers** — values below the smallest normal number (`Double.MIN_NORMAL` ≈ 2.2×10^-308) down to the tiniest subnormal (`Double.MIN_VALUE` ≈ 4.9×10^-324) — which trade precision for reach. Below even that, the result rounds to **0.0** (or **-0.0**, a distinct bit pattern that compares equal to 0.0). So underflow is a *gradual loss of precision* ending in zero, not a wrap. It is silent. ## NaN — Not a Number Some operations have no meaningful real result: `0.0 / 0.0`, `Infinity - Infinity`, `Math.sqrt(-1)`, `Infinity * 0`. These produce **NaN** (`Double.NaN`). NaN is special: - It is **unequal to everything, including itself**: `NaN == NaN` is `false`. So you cannot test for it with `==`; use **`Double.isNaN(x)`**. - It is **contagious**: almost any arithmetic involving NaN yields NaN, so one bad value silently poisons a whole computation. - It breaks ordering: comparisons (`<`, `>`) with NaN are always false, which can corrupt sorting; `Double.compare` and the boxed `Double.equals` treat NaN consistently (and treat -0.0 < 0.0), unlike the primitive operators. ## Detecting the special values - `Double.isInfinite(x)` / `Float.isInfinite(x)` — overflow / div-by-zero. - `Double.isNaN(x)` — invalid operation. - `Double.isFinite(x)` — true only for an ordinary finite number. ## Integer vs floating-point overflow — side by side | | Integer | Floating-point | |---|---|---| | Overflow result | wraps (mod 2^N) | ±Infinity | | Underflow | n/a (wrap covers both ends) | → subnormals → ±0.0 | | Divide by zero | throws ArithmeticException | ±Infinity (or NaN for 0/0) | | Throws? | never (plain ops) | never | | Sentinels | none | Infinity, NaN, -0.0 | ## Why it matters Both are **silent**, but the failure modes differ. Integer overflow gives a plausible-looking wrong number (often negative). Floating-point overflow/NaN gives a sentinel that *spreads* — an Infinity or NaN can flow through many calculations and only reveal itself much later (a NaN in a UI, a sort that scrambles data). Defensive code validates inputs and checks `isNaN`/`isInfinite` at boundaries. For exact decimal needs (money), avoid floating-point entirely and use `BigDecimal`. ## Key takeaway Floating-point overflows to **infinity**, underflows to **zero** (via subnormals), yields **NaN** for invalid ops, and never throws — fundamentally unlike integer wraparound.
- Why can't you check for NaN with ==?By IEEE 754, NaN is unequal to everything including itself, so NaN == NaN is false. Use Double.isNaN(x).
- What does integer 1/0 do versus floating-point 1.0/0.0?Integer 1/0 throws ArithmeticException; floating-point 1.0/0.0 evaluates to Double.POSITIVE_INFINITY with no exception.
saying these in an interview costs you the question
- Saying floating-point wraps around like integers
- Testing for NaN with x == Double.NaN (always false; use Double.isNaN)
- Thinking 1.0/0.0 throws ArithmeticException (only integer division by zero throws)
- Assuming underflow throws or that it jumps straight to 0 without the subnormal/precision-loss stage
- Using float/double for money instead of BigDecimal