Number Types
Java offers several ways to represent numbers, each with a different range, precision, memory cost, and speed. There are six primitive numeric types, a wrapper class for each, the abstract class Number that ties the wrappers together, and two arbitrary-precision classes, BigInteger and BigDecimal, for values that do not fit or must be exact. This lesson maps out the whole family so you can choose the right type for each kind of data. The following lessons examine Integer, Float, Double, BigInteger, and BigDecimal individually.
The Numeric Type Family
Primitive types Wrapper classes (objects)
byte (8-bit) ─► Byte ┐
short (16-bit) ─► Short │
int (32-bit) ─► Integer │
long (64-bit) ─► Long ├─ all extend java.lang.Number
float (32-bit) ─► Float │
double (64-bit) ─► Double │
BigInteger│ (arbitrary-size integers)
BigDecimal┘ (exact decimals)
(char is also a 16-bit unsigned integer type internally, but it represents characters and is not a subclass of Number.)
Integer Types
Integer types store whole numbers exactly, as long as the value fits in their range.
| Type | Bits | Minimum | Maximum | Literal |
|---|---|---|---|---|
byte | 8 | −128 | 127 | (byte) 10 |
short | 16 | −32,768 | 32,767 | (short) 10 |
int | 32 | −2,147,483,648 | 2,147,483,647 | 10 |
long | 64 | about −9.22 × 10¹⁸ | about 9.22 × 10¹⁸ | 10L |
All are signed, stored in two's complement. Each wrapper class exposes the limits as constants (Integer.MIN_VALUE, Long.MAX_VALUE), and SIZE and BYTES give the size in bits and bytes.
Arithmetic that exceeds the range wraps around silently:
System.out.println(Integer.MAX_VALUE + 1); // -2147483648
System.out.println(Long.MAX_VALUE + 1); // -9223372036854775808
Math.addExact, multiplyExact, and toIntExact throw ArithmeticException instead of wrapping, and BigInteger removes the limit entirely.
Unsigned operations
Java has no unsigned integer types, but the wrapper classes provide methods that treat the bits as unsigned, useful for binary file formats, network protocols, and hash values:
Integer.toUnsignedString(-1); // "4294967295"
Byte.toUnsignedInt((byte) 200); // 200 (a byte cast from 200 holds -56)
Integer.divideUnsigned(-2, 2); // 2147483647
Floating-Point Types
Floating-point types store numbers with a fractional part using the IEEE 754 binary format.
| Type | Bits | Approx. significant digits | Largest value | Smallest positive value | Literal |
|---|---|---|---|---|---|
float | 32 | 6–7 | 3.4 × 10³⁸ | 1.4 × 10⁻⁴⁵ | 3.14f |
double | 64 | 15–16 | 1.8 × 10³⁰⁸ | 4.9 × 10⁻³²⁴ | 3.14 |
Two points surprise many developers:
Float.MIN_VALUEandDouble.MIN_VALUEare the smallest positive values, not the most negative ones. The most negative double is-Double.MAX_VALUE.- Most decimal fractions are approximations.
0.1has no exact binary representation, so0.1 + 0.2is0.30000000000000004as adouble. The error is tiny, but it accumulates and breaks equality checks.
Floating-point types also have special values: Infinity, -Infinity, and NaN (not a number), produced by operations such as 1.0 / 0 and 0.0 / 0.
double is the default and the right choice for scientific, statistical, geometric, and graphics calculations. float halves the memory and is used for very large arrays, graphics APIs, and machine-learning data where 7 digits of precision suffice.
Arbitrary-Precision Types
| Class | Represents | Use when |
|---|---|---|
BigInteger | Integers of any size | Values beyond long: cryptography, factorials, very large IDs or counters |
BigDecimal | Decimal numbers with exact digits and controlled rounding | Money, tax, interest, and any calculation that must match decimal arithmetic exactly |
new BigDecimal("0.1").add(new BigDecimal("0.2")); // 0.3 exactly
new BigInteger("2").pow(100); // 1267650600228229401496703205376
They are immutable objects, considerably slower than primitives, and use method calls instead of operators (add, multiply, compareTo). Both have their own lessons.
Primitives Versus Wrappers
Primitive (int) | Wrapper (Integer) | |
|---|---|---|
| What it is | A raw value | An object containing a value |
Can be null | No | Yes |
| Usable in collections and generics | No | Yes (List<Integer>) |
| Memory | 4 bytes | About 16 bytes per object, plus the reference |
Comparing with == | Compares values | Compares object identity |
| Utility methods | No | parseInt, toBinaryString, compare, constants |
Java converts between them automatically (autoboxing and unboxing), which is convenient but has pitfalls: unboxing null throws NullPointerException, and == between two wrapper objects compares references. The Integer Class lesson covers these in detail.
Use primitives for local variables, fields, and arithmetic. Use wrappers where an object is required (collections, generics, nullable database columns).
The Number Class
All wrappers, plus BigInteger and BigDecimal, extend the abstract class java.lang.Number, which defines conversion methods:
| Method | Returns |
|---|---|
intValue() | The value as an int |
longValue() | The value as a long |
doubleValue() | The value as a double |
floatValue(), shortValue(), byteValue() | The value in those types |
These conversions can lose information silently: truncating fractions, wrapping values that do not fit, or rounding to fewer significant digits. Code that accepts any Number is convenient (a chart that plots any numeric series, for example), but it should be aware of what each conversion does.
Choosing a Type
| Data | Recommended type |
|---|---|
| Counts, indexes, ages, quantities | int |
| Timestamps in milliseconds, file sizes, large IDs | long |
| Measurements, scientific values, coordinates | double |
| Money, prices, tax, interest | BigDecimal (or long holding the smallest unit, such as paise) |
| Integers that can exceed 9.2 × 10¹⁸ | BigInteger |
| Raw binary data | byte / byte[] |
| Large arrays where memory matters | float[], short[], byte[] |
| A numeric value that may be absent | Wrapper type (Integer) or OptionalInt |
byte and short do not make arithmetic faster: the JVM promotes them to int for every operation. Use them only for compact storage or for formats that require them.
Conversions Between Types
Widening conversions to a larger type happen automatically and never lose magnitude:
byte → short → int → long → float → double
Narrowing conversions require an explicit cast and can lose data:
long big = 9_000_000_000L;
int truncated = (int) big; // 410065408: high bits discarded
double price = 3.99;
int whole = (int) price; // 3: fraction truncated, not rounded
Converting long to float or double is widening, but can lose precision for values above about 2⁵³ (for double) or 2²⁴ (for float), because the floating-point type cannot store every digit.
Practical Example: Inspecting the Number Types
import java.math.*;
import java.util.*;
public class NT {
static String describe(Number n) {
return String.format("%-11s int=%d long=%d double=%s", n.getClass().getSimpleName(), n.intValue(), n.longValue(), n.doubleValue());
}
public static void main(String[] args) {
System.out.printf("%-7s %5s %22s %22s%n", "type", "bits", "min", "max");
System.out.printf("%-7s %5d %22d %22d%n", "byte", Byte.SIZE, Byte.MIN_VALUE, Byte.MAX_VALUE);
System.out.printf("%-7s %5d %22d %22d%n", "short", Short.SIZE, Short.MIN_VALUE, Short.MAX_VALUE);
System.out.printf("%-7s %5d %22d %22d%n", "int", Integer.SIZE, Integer.MIN_VALUE, Integer.MAX_VALUE);
System.out.printf("%-7s %5d %22d %22d%n", "long", Long.SIZE, Long.MIN_VALUE, Long.MAX_VALUE);
System.out.printf("%-7s %5d %22s %22s%n", "float", Float.SIZE, Float.MIN_VALUE, Float.MAX_VALUE);
System.out.printf("%-7s %5d %22s %22s%n", "double", Double.SIZE, Double.MIN_VALUE, Double.MAX_VALUE);
List<Number> values = List.of(42, 9_000_000_000L, 3.99f, 2.75, (byte) -5,
new BigInteger("123456789012345678901234567890"), new BigDecimal("1499.995"));
for (Number n : values) System.out.println(describe(n));
}
}
Output:
type bits min max
byte 8 -128 127
short 16 -32768 32767
int 32 -2147483648 2147483647
long 64 -9223372036854775808 9223372036854775807
float 32 1.4E-45 3.4028235E38
double 64 4.9E-324 1.7976931348623157E308
Integer int=42 long=42 double=42.0
Long int=410065408 long=9000000000 double=9.0E9
Float int=3 long=3 double=3.990000009536743
Double int=2 long=2 double=2.75
Byte int=-5 long=-5 double=-5.0
BigInteger int=1312754386 long=-4362896299872285998 double=1.2345678901234568E29
BigDecimal int=1499 long=1499 double=1499.995
The table, built from each wrapper's SIZE, MIN_VALUE, and MAX_VALUE constants, confirms that float and double report their smallest positive values as the minimum. The second part passes values of every numeric class through the same describe method, which accepts any Number, and shows what the Number conversions do:
Long9,000,000,000 converted tointwraps to 410,065,408 because it does not fit in 32 bits.Float3.99, widened todouble, reveals that it was only ever stored as approximately 3.99.BigIntegerconverted tointandlongkeeps only the low-order bits, producing unrelated numbers, and itsdoubleloses all but about 16 significant digits.
Each of these conversions compiles without a warning, which is exactly why choosing the right type at the start matters.