Numbers and Math
|
This section documents the current Java release line, with Java 25 LTS as the reference point (no specific patch version is pinned), as published at the Java developer portal, the Java Tutorials, and the Java SE API specification — which are the references these pages are written and verified against. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Java has two families of numbers: the primitives (int, long, double, …) covered on
Primitive Types and Variables, and the object
types that wrap them. This page covers the wrappers and their autoboxing traps, the Math utility
class, arbitrary-precision BigInteger and BigDecimal, and the parsing, formatting and
floating-point rules every numeric program runs into. The narrative overview is
The Numbers Classes in the Java
Tutorials.
Wrapper Classes and Autoboxing
Every primitive has a matching immutable class:
Integer,
Long, Short,
Byte,
Double, Float,
Character, and
Boolean. The six
numeric ones extend
java.lang.Number.
Wrappers exist so numbers can be used as generic type arguments and collection elements, can be null,
and can host the static parse/format/constant helpers.
int prim = 42;
Integer boxed = prim; // autoboxing: Integer.valueOf(42)
int back = boxed; // auto-unboxing: boxed.intValue()
Integer.MAX_VALUE; // 2147483647
Integer.parseInt("1010", 2); // 10 -- radix 2
Integer.toBinaryString(10); // "1010"
Integer.compare(3, 7); // -1 -- safe; unlike (3 - 7) it cannot overflow
Long.parseLong("9999999999"); // 9999999999
Character.isDigit('7'); // true
Boolean.parseBoolean("TRUE"); // true -- case-insensitive; anything else is false
List<Integer> ids = new ArrayList<>(); // generics need a reference type
ids.add(1); // autoboxes int -> Integer
Autoboxing pitfalls
NPE when unboxing null. A wrapper expression used where a primitive is expected calls
xxxValue(); if the reference is null that throws
NullPointerException.
Map<String, Integer> counts = new HashMap<>();
int n = counts.get("missing"); // get() returns null -> NPE on unboxing
int safe = counts.getOrDefault("missing", 0); // fix: never unbox a null
== compares identities, not values. On two wrapper references == asks "same object?", not "same
number?". Always use equals or unbox explicitly.
Integer a = 1000, b = 1000;
a == b; // false -- two distinct objects
a.equals(b); // true
a.intValue() == b.intValue(); // true
The Integer cache makes == "sometimes work".
JLS 5.1.7 requires
Integer.valueOf (which autoboxing calls) to return cached instances for -128..127, so == on
small boxed values happens to be true — which is worse than a consistent failure.
Integer x = 127, y = 127;
x == y; // true -- same cached object
Integer p = 128, q = 128;
p == q; // false -- outside the cache, fresh objects
Byte, Short and Long cache -128..127 too; Character caches 0..127; Boolean caches both
values.
Boxing cost in loops. A wrapper-typed accumulator allocates a new object every iteration.
Long sum = 0L; // WRONG: Long, not long
for (long i = 0; i < 10_000_000; i++) {
sum += i; // unbox, add, box -> ~10M throwaway Long objects
}
// fix: declare the accumulator 'long'
See Autoboxing and Unboxing for the full conversion rules.
Math and StrictMath
java.lang.Math
holds static numeric helpers plus Math.PI and Math.E. Its transcendental functions may differ
across platforms by up to one unit in the last place;
StrictMath
has the identical API but is bit-for-bit reproducible everywhere. Use Math unless you need
cross-platform identical results.
Math.max(3, 7); // 7
Math.abs(-4); // 4
Math.pow(2, 10); // 1024.0
Math.sqrt(2); // 1.4142135623730951
Math.hypot(3, 4); // 5.0
Math.clamp(15, 0, 10); // 10 -- constrain to [min, max]
Math.toRadians(180); // 3.141592653589793
Math.floorDiv(-7, 2); // -4 -- rounds toward negative infinity ((-7)/2 is -3)
Math.floorMod(-1, 3); // 2 -- result has the sign of the divisor ((-1)%3 is -1)
int and long arithmetic wraps silently on overflow: Integer.MAX_VALUE + 1 is
Integer.MIN_VALUE, no error. The *Exact methods throw
ArithmeticException
instead — use them wherever a wrapped result would be a correctness bug.
Math.addExact(Integer.MAX_VALUE, 1); // ArithmeticException: integer overflow
Math.multiplyExact(100_000, 100_000); // ArithmeticException
Math.toIntExact(3_000_000_000L); // ArithmeticException
Math.incrementExact(counter);
Math.negateExact(Long.MIN_VALUE); // ArithmeticException: long overflow
Arbitrary Precision: BigInteger and BigDecimal
BigInteger
is an immutable integer bounded only by memory; operations are methods, not operators.
import java.math.BigInteger;
BigInteger two = BigInteger.valueOf(2);
BigInteger big = two.pow(1000); // 2^1000, 302 digits
BigInteger fact = BigInteger.ONE;
for (int i = 2; i <= 100; i++) {
fact = fact.multiply(BigInteger.valueOf(i)); // 100!
}
big.mod(BigInteger.valueOf(7)); // 5
new BigInteger("123456789012345678901234567890").isProbablePrime(20); // false
BigDecimal
is an immutable decimal with an explicit scale (digits after the point) — exact decimal arithmetic.
Why BigDecimal, not double, for money. double is binary floating point, and most decimal
fractions have no exact binary form, so values drift:
0.1 + 0.2; // 0.30000000000000004
0.1 + 0.2 == 0.3; // false
Rounding once at the end does not undo error accumulated across thousands of amounts. Use BigDecimal — or integer minor units (cents as long) — for currency.
The new BigDecimal(double) trap. Constructing from a double copies its binary error in; construct
from a String (or use valueOf, which routes through Double.toString).
new BigDecimal(0.1); // 0.1000000000000000055511151231257827021181583404541015625
new BigDecimal("0.1"); // exactly 0.1
BigDecimal.valueOf(0.1); // 0.1
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
var price = new BigDecimal("19.99");
var net = price.multiply(new BigDecimal("3")); // 59.97 (scale 2)
var tax = net.multiply(new BigDecimal("0.21"))
.setScale(2, RoundingMode.HALF_UP); // 12.59
var gross = net.add(tax); // 72.56
net.compareTo(new BigDecimal("59.970")); // 0 -- compareTo ignores scale
net.equals(new BigDecimal("59.970")); // false -- equals compares scale too
// division must state scale + rounding, or a non-terminating result throws
new BigDecimal("10").divide(new BigDecimal("3"), 4, RoundingMode.HALF_UP); // 3.3333
new BigDecimal("10").divide(new BigDecimal("3")); // ArithmeticException
// MathContext = significant digits + rounding, applied to a whole computation
var mc = new MathContext(10, RoundingMode.HALF_EVEN);
new BigDecimal("2").divide(new BigDecimal("7"), mc); // 0.2857142857
Rules of thumb: compare with compareTo, never equals; every divide needs an explicit
RoundingMode
(HALF_UP for invoices, HALF_EVEN — banker’s rounding — for statistics); keep a single scale for
each currency and setScale after every multiplication.
Parsing, Formatting, and IEEE-754
The static parseXxx methods turn text into a primitive and throw
NumberFormatException
on malformed input.
int i = Integer.parseInt("42");
int hex = Integer.parseInt("ff", 16); // 255
long l = Long.parseLong("9999999999");
double d = Double.parseDouble("3.14e2"); // 314.0
Integer.parseInt("12x"); // NumberFormatException
java.text.NumberFormat
is locale-sensitive (grouping, currency symbol, percent) and not thread-safe — build one per use or
guard it.
import java.text.NumberFormat;
import java.util.Locale;
NumberFormat eur = NumberFormat.getCurrencyInstance(Locale.of("es", "ES"));
eur.format(1234.5); // "1234,50 EUR" style, locale-formatted
NumberFormat pct = NumberFormat.getPercentInstance(Locale.US);
pct.setMinimumFractionDigits(1);
pct.format(0.075); // "7.5%"
String grouped = String.format(Locale.US, "%,.2f", 1234.5); // "1,234.50"
IEEE-754 caveats. Floating point is approximate and not associative:
-
Never test
double/floatwith==. Compare against a tolerance:Math.abs(a - b) < 1e-9. -
1.0 / 0.0isDouble.POSITIVE_INFINITYand0.0 / 0.0isDouble.NaN— no exception (unlike integer/ 0).NaNis not equal to anything, including itself, sox == Double.NaNis alwaysfalse; test withDouble.isNaN(x). -
Double.compare(a, b)andDouble.equalsimpose a total order where-0.0 < 0.0andNaNsorts above every number, which is whyTreeSet<Double>andsortstay consistent even though<does not orderNaN.
0.0 == -0.0; // true
Double.compare(0.0, -0.0); // 1
Double.isNaN(0.0 / 0.0); // true
Double.valueOf(Double.NaN).equals(Double.NaN); // true
Random values.
RandomGenerator
is the modern interface (with pluggable algorithms), the legacy
java.util.Random
implements it, and
ThreadLocalRandom
is the one to use from concurrent code (never share a Random across threads).
import java.util.random.RandomGenerator;
import java.util.concurrent.ThreadLocalRandom;
RandomGenerator rng = RandomGenerator.getDefault();
int dice = rng.nextInt(1, 7); // 1..6
double u = rng.nextDouble(); // [0.0, 1.0)
rng.ints(5, 0, 100).forEach(System.out::println);
var named = RandomGenerator.of("L64X128MixRandom"); // choose an algorithm by name
int worker = ThreadLocalRandom.current().nextInt(100); // in server / parallel code
For tokens, keys and nonces use java.security.SecureRandom, not Random or the default
RandomGenerator.
See Also
-
Primitive Types and Variables — the
int,longanddoubleprimitives these types wrap, plus widening and narrowing. -
Operators and Expressions — integer division,
%, and numeric promotion inside expressions. -
Methods and Parameters — the
MathandObjectshelpers from the method author’s perspective. -
Strings and Text —
String.format, text blocks, and number-to-text round trips.