skip to content

Math & Numbers

Numeric utilities and exact arithmetic: the Math class, BigDecimal and BigInteger, the random generators, and locale-aware number formatting. The money question — why not double — lives here.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

29

What is the difference between BigDecimal.equals() and BigDecimal.compareTo() when comparing two values, and which should you use to test numeric equality?

level: juniorimportance: must knowfreq 70%

answer

  1. equals() = value AND scale
  2. compareTo() = value only, ignores scale
  3. 2.0 vs 2.00: equals false, compareTo 0
  4. Use compareTo() == 0 for numeric equality
  5. Internal = unscaled BigInteger + scale

basics

~20 s

equals() also checks scale, so 2.0 is not equal to 2.00. compareTo() ignores scale and compares the actual number, so 2.0 compareTo 2.00 returns 0. To check if two numbers are numerically equal, use compareTo() == 0.

solid answer

~40 s

BigDecimal stores both an unscaled integer and a scale (number of digits after the decimal point). equals() requires both the numeric value AND the scale to match, so new BigDecimal("2.0").equals(new BigDecimal("2.00")) is false even though they represent the same number. compareTo() compares only the mathematical value and ignores scale, so the same pair returns 0 (equal). The rule: use compareTo(other) == 0 whenever you mean 'is this the same number'. Reserve equals() for when you specifically care that the representation, including trailing zeros / precision, is identical. This mismatch is the classic gotcha because it means BigDecimal's equals and compareTo are inconsistent, which also breaks usage in hash-based collections.

code

java · 10 lines
java
BigDecimal a = new BigDecimal("2.0");
BigDecimal b = new BigDecimal("2.00");

System.out.println(a.equals(b));        // false  (scale 1 != scale 2)
System.out.println(a.compareTo(b));     // 0      (same numeric value)
System.out.println(a.compareTo(b) == 0); // true  -> numeric equality

// Make equals agree by normalizing scale:
System.out.println(
    a.stripTrailingZeros().equals(b.stripTrailingZeros())); // true

go deeper

for a junior

Knows equals() returns false for 2.0 vs 2.00 and that compareTo() == 0 is the right way to test numeric equality.

for a middle

Can explain the internal unscaled-value + scale representation and why equals() factors in scale.

for a senior

Articulates that BigDecimal deliberately breaks the equals/compareTo consistency recommendation and knows the downstream HashSet/HashMap and normalization implications.

for a principal

Can reason about when precision-sensitive equality (equals) is actually desired vs numeric equality, and set team conventions (e.g. always normalize scale or always use compareTo) plus enforcement via static analysis.

## What BigDecimal is `BigDecimal` is a Java class for **arbitrary-precision decimal numbers**. Unlike `double` (which is binary floating-point and can't represent 0.1 exactly), `BigDecimal` represents numbers exactly as a decimal. Internally it stores two things: - an **unscaled value**: a `BigInteger` (an integer of any size), and - a **scale**: an `int` saying how many digits sit to the right of the decimal point. The actual number it represents is `unscaledValue × 10^(-scale)`. Example: - `new BigDecimal("2.0")` → unscaled value `20`, scale `1` → 20 × 10⁻¹ = 2.0 - `new BigDecimal("2.00")` → unscaled value `200`, scale `2` → 200 × 10⁻² = 2.00 Both represent the **same mathematical number** (two), but they have **different internal representations** (different unscaled value and different scale). ## equals(): representation equality `BigDecimal.equals(Object)` returns `true` only when **both the unscaled value and the scale are equal**. So: ``` new BigDecimal("2.0").equals(new BigDecimal("2.00")) // false ``` This is intentional. The Javadoc explicitly says two BigDecimals are equal only if they are equal in value **and** scale. equals() treats `2.0` and `2.00` as different objects because they carry different precision information (the number of significant trailing zeros). ## compareTo(): numeric equality `BigDecimal.compareTo(BigDecimal)` ignores scale and compares only the **mathematical value**. It returns a negative int, zero, or a positive int (the standard `Comparable` contract). So: ``` new BigDecimal("2.0").compareTo(new BigDecimal("2.00")) // 0 (equal value) ``` A return of `0` means "these are the same number". ## The rule - **To ask "are these the same number?"** → use `a.compareTo(b) == 0`. - **To ask "are these the exact same value AND precision?"** → use `a.equals(b)`. In the vast majority of business code (money, totals, comparisons) you want numeric equality, so you should use `compareTo() == 0`, not `equals()`. ## Why this matters: the inconsistency The general `Object.equals`/`Comparable.compareTo` convention recommends that `a.compareTo(b) == 0` should be *consistent with* `a.equals(b)`. `BigDecimal` deliberately **violates** this recommendation (the Javadoc warns about it). That inconsistency is the root of two common bugs: 1. Direct equality checks giving "wrong" answers when scales differ. 2. Using BigDecimal as a key in `HashSet`/`HashMap` (see the related question) — because hashing is built on `equals()`, `2.0` and `2.00` land in different buckets. ## Normalizing scale If you need consistent behavior, you can normalize. `stripTrailingZeros()` removes trailing zeros, and `setScale(n, RoundingMode)` forces a fixed scale. After normalizing both operands to the same scale, `equals()` and `compareTo()` agree. (Note: `stripTrailingZeros()` on `0.00` historically produced `0E-8` rather than `0` in some JDKs — be careful with zero.) ## Summary table | | `2.0` vs `2.00` | |---|---| | `equals()` | false (scales differ) | | `compareTo() == 0` | true (same value) |

  • How can you make equals() and compareTo() agree on two BigDecimals?
    Normalize their scales first — e.g. call stripTrailingZeros() on both, or setScale(n, RoundingMode) to force the same scale. Once unscaled value and scale both match, equals() returns true and compareTo() returns 0.
  • Does compareTo() ever throw or behave oddly with NaN-like values?
    No. BigDecimal has no NaN or infinity — it's exact arbitrary-precision decimal. compareTo() always returns a clean -1/0/1 ordering for any two non-null BigDecimals.

Think of equals() as comparing two price tags character-by-character: '$2.0' and '$2.00' are different strings. compareTo() is like asking the cashier 'is it the same price?' — both ring up as two dollars.

saying these in an interview costs you the question

  • Claiming equals() and compareTo() always agree for BigDecimal (they intentionally don't).
  • Saying 2.0 equals 2.00 via equals() — it returns false.
  • Believing BigDecimal stores numbers as a single double internally.
  • Using == to compare BigDecimal objects (compares references, not value).

context

open as a page

Why should you use BigDecimal instead of double or float for monetary values?

level: juniorimportance: must knowfreq 78%

basics

~10 s

double and float store numbers in binary, so many decimals like 0.1 can't be represented exactly and small errors creep in. BigDecimal stores decimals exactly, so it's the right choice for money.

open as a page

What is BigInteger in Java, when would you use it instead of long, and what does it mean that it is immutable?

level: juniorimportance: must knowfreq 62%

basics

~10 s

BigInteger holds whole numbers of any size, so it never overflows like long does. It is immutable: every operation returns a new BigInteger instead of changing the original.

open as a page

What are the most common methods on java.lang.Math, and what do they compute (abs, pow, sqrt, max/min, log/exp, trig)?

level: juniorimportance: must knowfreq 55%

basics

~10 s

Math is a utility class of static math functions: abs (magnitude), pow (x to a power), sqrt (square root), max/min (larger/smaller of two), log/exp (natural log and e^x), and sin/cos/tan (trigonometry, in radians).

open as a page

How do you format a number for display in Java using NumberFormat, and why is the Locale important?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Use NumberFormat factory methods like NumberFormat.getInstance(locale), then call format(value). The Locale decides things like whether the decimal point is a dot or a comma and which grouping separator is used, so the output looks right for that region.

open as a page

What is the difference between new BigDecimal("0.1"), new BigDecimal(0.1), and BigDecimal.valueOf(0.1)?

level: middleimportance: must knowfreq 71%

basics

~10 s

new BigDecimal("0.1") is exactly 0.1. new BigDecimal(0.1) takes the imprecise double and copies its error, giving 0.1000000000000000055... BigDecimal.valueOf(0.1) is safe — it produces exactly 0.1.

open as a page

Explain Math.round, Math.floor, and Math.ceil — their return types and rounding behavior, including how round handles ties and negatives.

level: middleimportance: must knowfreq 60%

basics

~20 s

floor rounds down to the nearest whole number, ceil rounds up, and round goes to the nearest whole number (rounding .5 up). floor/ceil return double; round returns long (from a double) or int (from a float).

open as a page

What is DecimalFormat and how do its pattern symbols (0, #, comma, dot) control the output?

level: middleimportance: must knowfreq 58%

basics

~20 s

DecimalFormat is a concrete NumberFormat that formats numbers from a pattern string. In the pattern, 0 forces a digit (shows zero if absent), # shows a digit only if present, the comma marks grouping, and the dot marks the decimal point. So "#,##0.00" gives grouped numbers with exactly two decimals.

open as a page

What are java.util.Random, SecureRandom, and ThreadLocalRandom, and when would you choose each?

level: middleimportance: must knowfreq 70%

basics

~20 s

Random is a basic, fast number generator that can repeat if you give it a seed. SecureRandom makes unpredictable numbers for things like passwords and tokens. ThreadLocalRandom is a faster Random for code where many threads run at once.

open as a page

How does scale behave through BigDecimal arithmetic, and why can divide throw an ArithmeticException?

level: seniorimportance: must knowfreq 68%

basics

~20 s

Each operation has predictable scale rules: multiply gives a scale equal to the sum of the operands' scales; add/subtract use the larger scale. divide can produce a non-terminating decimal (like 1/3), so without a rounding mode it throws ArithmeticException — you must supply a scale + RoundingMode or a MathContext.

open as a page

Are NumberFormat and DecimalFormat thread-safe, and how should you use them in concurrent code?

level: seniorimportance: must knowfreq 52%

basics

~20 s

No. NumberFormat and DecimalFormat are not thread-safe. If multiple threads share one instance and call format or parse at the same time, you can get wrong results or exceptions. Create a new instance per use, or give each thread its own via ThreadLocal.

open as a page

Why is java.util.Random considered predictable and unsuitable for security purposes?

level: seniorimportance: must knowfreq 55%

basics

~20 s

It uses a simple math formula with a small, public starting state. If someone sees a few of its numbers, they can figure out the formula's state and predict every number it will produce next.

open as a page

How does BigDecimal represent a number internally, and why does that design make scale significant to equals()?

level: middleimportance: should knowfreq 45%

basics

~20 s

A BigDecimal is stored as a whole-number 'unscaled value' plus a 'scale' that says how many digits are after the decimal point. The value is unscaledValue × 10^(-scale). Because two equal numbers can have different scales (like 2.0 and 2.00), equals() treats them as different.

open as a page

What is the difference between BigInteger and BigDecimal, and how do you convert between them?

level: middleimportance: should knowfreq 44%

basics

~10 s

BigInteger holds arbitrarily large whole numbers; BigDecimal holds arbitrary-precision decimals (with a fraction part and a scale). You convert with new BigDecimal(bigInteger) and, going back, BigDecimal.toBigInteger() (which drops the fraction).

open as a page

Why is Math (and double in general) the wrong tool for money, and what causes results like 0.1 + 0.2 != 0.3?

level: middleimportance: should knowfreq 45%

basics

~20 s

double stores numbers in binary, and many decimals like 0.1 can't be represented exactly, so tiny errors creep in (0.1 + 0.2 comes out as 0.30000000000000004). For money use BigDecimal, which stores exact decimal values.

open as a page

How do you format currency correctly across locales, and how do you customize symbols and separators?

level: middleimportance: should knowfreq 40%

basics

~10 s

Use NumberFormat.getCurrencyInstance(locale) to format money; it adds the right currency symbol and decimal places for that region. To override which currency or which symbols are used, call setCurrency(Currency) or supply a custom DecimalFormatSymbols.

open as a page

How do you generate a random number in a range without bias, and how do modern Java APIs help?

level: middleimportance: should knowfreq 35%

basics

~10 s

Use the built-in bounded methods like nextInt(bound) or nextInt(origin, bound) instead of doing 'rng.nextInt() % range'. The modulo trick can make some numbers come up slightly more often.

open as a page

How do you use ThreadLocalRandom correctly, and what problem does it solve compared to a shared Random?

level: middleimportance: should knowfreq 45%

basics

~20 s

Call ThreadLocalRandom.current() each time you need it and use the result right away; never save it and pass it to other threads. It avoids the slowdown you get when many threads share one Random object.

open as a page

What goes wrong when you use BigDecimal as a key in a HashSet or HashMap, and how do you fix it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Hash-based collections find keys using equals() and hashCode(), which both include scale. So new BigDecimal("2.0") and new BigDecimal("2.00") are treated as different keys — a lookup with the 'wrong' scale misses. Fix it by normalizing scale (e.g. stripTrailingZeros) before storing or looking up.

open as a page

What are the BigDecimal RoundingModes, and why is HALF_EVEN (banker's rounding) often preferred for money?

level: seniorimportance: should knowfreq 62%

basics

~20 s

RoundingMode defines how to drop digits: HALF_UP rounds .5 away from zero (the schoolbook way), HALF_DOWN toward zero, and HALF_EVEN rounds .5 to the nearest even digit. HALF_EVEN is preferred for money because it removes the upward bias of always rounding .5 up.

open as a page

How does BigInteger.isProbablePrime(certainty) work, and what does the certainty parameter actually guarantee?

level: seniorimportance: should knowfreq 35%

basics

~10 s

isProbablePrime(certainty) is a fast test that says a number is probably prime or definitely not prime. Higher certainty lowers the chance of a wrong 'prime' answer; it never wrongly says a prime is composite.

open as a page

What does BigInteger.modPow do, and why is it central to public-key cryptography like RSA?

level: seniorimportance: should knowfreq 48%

basics

~10 s

modPow(exp, m) computes (this ^ exp) mod m efficiently, without ever building the gigantic full power. RSA encryption and decryption are exactly this modular exponentiation on huge numbers.

open as a page

Math.abs and ordinary integer arithmetic can silently overflow. How does this happen, and what does Math offer to detect it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Java ints have a fixed range, so a result too big to fit silently wraps around to a wrong (often negative) value. Math.abs(Integer.MIN_VALUE) stays negative for this reason. Math.addExact, multiplyExact, etc. throw an exception instead of wrapping.

open as a page

What is the difference between Math and StrictMath, and when would you prefer one over the other?

level: seniorimportance: should knowfreq 35%

basics

~20 s

StrictMath always gives the exact same bit-for-bit result on every platform. Math may use faster, platform-optimized routines that can differ slightly between machines. Use StrictMath when you need identical results everywhere; otherwise Math is the usual, faster choice.

open as a page

How does rounding work in NumberFormat/DecimalFormat, and what is the default rounding behavior?

level: seniorimportance: should knowfreq 44%

basics

~20 s

When a number has more decimals than the format shows, DecimalFormat rounds it. By default it uses HALF_EVEN (banker's rounding), so 2.5 rounds to 2 and 3.5 rounds to 4. You can change this with setRoundingMode, e.g. to HALF_UP.

open as a page

How should you use SecureRandom in practice, and what are the common pitfalls (blocking, seeding, getInstanceStrong)?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Create one SecureRandom and reuse it to generate bytes for tokens or keys. Don't give it a fixed seed (that makes it predictable). On some setups it can pause briefly the first time while it gathers randomness.

open as a page

What is MathContext, how does it differ from setScale, and what do DECIMAL64 and UNLIMITED mean?

level: principalimportance: should knowfreq 41%

basics

~20 s

MathContext bundles a precision (total significant digits) plus a RoundingMode, and you pass it to operations to round by significant figures. setScale instead fixes the number of digits after the decimal point. DECIMAL64 is a 16-digit preset; UNLIMITED means no rounding (exact).

open as a page

What bit operations does BigInteger provide, and what is the performance cost model you should keep in mind?

level: seniorimportance: nice to knowfreq 24%

basics

~20 s

BigInteger supports bit-level methods like and, or, xor, not, shiftLeft/Right, testBit, setBit, and bitLength/bitCount. Each operation allocates a new object and costs roughly proportional to the number of words, so big numbers and tight loops get expensive.

open as a page

BigDecimal's compareTo() is documented as 'inconsistent with equals'. What does that mean, what general contract does it bend, and how should an API designer reason about it?

level: principalimportance: nice to knowfreq 25%

basics

~20 s

Java recommends that a.compareTo(b) == 0 should mean the same as a.equals(b). BigDecimal breaks this on purpose: 2.0 and 2.00 compare as equal (0) but are not equals() (scale differs). It's allowed but must be documented, and it causes surprises in collections that mix the two notions.

open as a page