Java Numbers
Java represents numbers in two families: integer types for whole numbers and floating-point types for numbers with a fractional part. Choosing between them affects range, precision, and whether arithmetic is exact.
Integer Types
| Type | Bits | Minimum | Maximum |
|---|---|---|---|
byte | 8 | −128 | 127 |
short | 16 | −32,768 | 32,767 |
int | 32 | −2,147,483,648 | 2,147,483,647 |
long | 64 | −9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 |
int population = 1_400_000_000;
long distanceToSunMeters = 149_597_870_700L;
byte flags = 0b0000_0101;
short year = 2026;
int is the default choice. Use long when a value can exceed about ±2.1 billion: timestamps in milliseconds, file sizes in bytes, large database IDs. The L suffix is required for a long literal larger than int range, because unsuffixed integer literals are int.
Literal forms
int decimal = 255;
int hex = 0xFF; // 255, prefix 0x
int binary = 0b11111111; // 255, prefix 0b
int octal = 0377; // 255, leading 0 means octal
int readable = 1_000_000;
The octal form is a trap: int code = 010; is 8, not 10. Never write a leading zero on a decimal number. Underscores are purely visual and may appear only between digits.
Overflow
Integer arithmetic wraps around when it exceeds the type's range. No error is raised.
int max = Integer.MAX_VALUE;
System.out.println(max + 1); // -2147483648
int millis = 30 * 24 * 60 * 60 * 1000; // milliseconds in 30 days
System.out.println(millis); // -1702967296 (overflowed)
long correct = 30L * 24 * 60 * 60 * 1000; // 2592000000
In the second example every operand is an int, so the multiplication happens in 32-bit arithmetic and overflows before the result is stored. Making the first operand a long (30L) promotes the whole calculation to 64 bits. Assigning an overflowed int result to a long variable does not help; the damage is done in the expression.
To detect overflow instead of wrapping silently, use the exact methods:
int total = Math.addExact(a, b); // throws ArithmeticException on overflow
long product = Math.multiplyExact(x, y);
Floating-Point Types
| Type | Bits | Approx. range | Significant decimal digits |
|---|---|---|---|
float | 32 | ±3.4 × 10³⁸ | 6–7 |
double | 64 | ±1.8 × 10³⁰⁸ | 15–16 |
double pi = 3.141592653589793;
float ratio = 0.75f;
double avogadro = 6.022e23; // scientific notation: 6.022 × 10²³
double tiny = 1.6e-19;
Decimal literals are double by default; float requires the f suffix. double is the standard choice; float is used mainly for memory-heavy arrays and graphics APIs.
Precision limits
Floating-point numbers are stored in binary, and most decimal fractions have no exact binary representation, just as 1/3 has no exact decimal one.
System.out.println(0.1 + 0.2); // 0.30000000000000004
System.out.println(1.1 * 3); // 3.3000000000000003
System.out.println(0.1 + 0.2 == 0.3); // false
Compare floating-point values with a tolerance:
double a = 0.1 + 0.2;
double b = 0.3;
boolean close = Math.abs(a - b) < 1e-9; // true
For money and anything that must be exact in decimal, use BigDecimal (covered in its own lesson) constructed from a string:
import java.math.BigDecimal;
BigDecimal price = new BigDecimal("0.10");
BigDecimal total = price.add(new BigDecimal("0.20"));
System.out.println(total); // 0.30
Special values
System.out.println(1.0 / 0); // Infinity
System.out.println(-1.0 / 0); // -Infinity
System.out.println(0.0 / 0); // NaN
System.out.println(Math.sqrt(-1)); // NaN
Floating-point division by zero does not throw. NaN ("not a number") propagates through every calculation it touches and is not equal to anything, including itself, so test with Double.isNaN(x). Double.isFinite(x) checks that a value is neither infinite nor NaN.
Integer Versus Floating-Point Arithmetic
The type of the operands decides the kind of arithmetic:
System.out.println(10 / 4); // 2 (int / int)
System.out.println(10 / 4.0); // 2.5 (int / double)
System.out.println(10.0 / 4); // 2.5
System.out.println(10 % 4); // 2
System.out.println(10.5 % 4); // 2.5
To get a decimal result from two integer variables, cast one before dividing:
int correct = 47, total = 60;
double percent = (double) correct / total * 100; // 78.333...
(double) (correct / total) would be wrong: the integer division happens first and yields 0.
Number Wrapper Classes
Each numeric primitive has a wrapper class (Integer, Long, Double, ...) that provides constants and conversion methods:
System.out.println(Integer.MAX_VALUE); // 2147483647
System.out.println(Long.MIN_VALUE); // -9223372036854775808
System.out.println(Double.MAX_VALUE); // 1.7976931348623157E308
int n = Integer.parseInt("123");
double d = Double.parseDouble("4.56");
String bin = Integer.toBinaryString(10); // "1010"
String hex = Integer.toHexString(255); // "ff"
int fromHex = Integer.parseInt("ff", 16); // 255
int cmp = Integer.compare(3, 7); // negative
Wrappers are required wherever an object is needed, such as in collections (List<Integer>), and the compiler converts between primitives and wrappers automatically. The wrapper classes each have their own lesson.
Practical Example: Loan Interest
public class LoanCalculator {
public static void main(String[] args) {
long principal = 500_000; // whole currency units
double annualRate = 8.5; // percent
int years = 5;
double monthlyRate = annualRate / 12 / 100;
int months = years * 12;
double emi = principal * monthlyRate * Math.pow(1 + monthlyRate, months)
/ (Math.pow(1 + monthlyRate, months) - 1);
double totalPaid = emi * months;
double interest = totalPaid - principal;
System.out.printf("Monthly EMI: %,.2f%n", emi);
System.out.printf("Total paid: %,.2f%n", totalPaid);
System.out.printf("Total interest: %,.2f%n", interest);
}
}
Output:
Monthly EMI: 10,258.27
Total paid: 615,495.94
Total interest: 115,495.94
principal is a long because loan amounts can exceed int range in some currencies. The rate is a double because it is fractional. annualRate / 12 / 100 is computed in double since annualRate is a double; had it been an int, both divisions would truncate to 0. Math.pow returns a double. %,.2f prints two decimals with thousands separators. Note that the total is computed from the unrounded EMI; a bank charging exactly 10,258.27 each month would collect 615,496.20, which is why real banking systems round each payment with BigDecimal and track the difference explicitly.