Float Class
float is Java's 32-bit floating-point type, and java.lang.Float is its wrapper class. A float stores numbers with a fractional part using the IEEE 754 single-precision format, giving about 7 significant decimal digits in half the memory of a double. Understanding exactly what that precision means, and when it is enough, is the key to using float correctly. Most Java code uses double instead; float has specific, valuable uses.
Declaring float Values
A decimal literal is a double by default. A float literal needs the f (or F) suffix:
float price = 9.99f;
float ratio = 0.75F;
float big = 1.5e6f; // scientific notation: 1,500,000
float fromInt = 42; // int widens to float automatically
float wrong = 9.99; // compile error: possible lossy conversion from double to float
float cast = (float) 9.99; // allowed with an explicit cast
The compiler requires the suffix or cast because converting a double to a float discards precision.
How float Stores Numbers
A float's 32 bits are divided into three parts:
1 bit 8 bits 23 bits
[sign] [ exponent ] [ fraction (mantissa) ]
The value is ±1.fraction × 2^exponent. With 23 stored fraction bits plus an implicit leading 1, a float has 24 bits of precision, which corresponds to roughly 7 decimal digits.
Integer.toHexString(Float.floatToIntBits(1.0f)); // "3f800000": the raw bits of 1.0f
Most decimals are approximations
Just as 1/3 cannot be written exactly in decimal, most decimal fractions cannot be written exactly in binary. The nearest float to 0.1 is slightly larger than 0.1:
float f = 0.1f;
System.out.println(f); // 0.1
System.out.println((double) f); // 0.10000000149011612
println shows the shortest decimal that identifies the value, so the approximation is hidden until the value is widened to double or accumulates through arithmetic.
Whole numbers lose precision above 16,777,216
A float can represent every integer up to 2²⁴ = 16,777,216. Above that, it can no longer represent every whole number:
System.out.println(16_777_216f + 1f); // 1.6777216E7: adding 1 has no effect
System.out.println((int) 16_777_217f); // 16777216: the literal itself rounds
The gap between adjacent representable values (the ulp, "unit in the last place") grows with the magnitude:
Math.ulp(1.0f); // 1.1920929E-7
Math.ulp(1_000_000f); // 0.0625: near a million, floats are spaced 1/16 apart
This is why float is unsuitable for large counters, IDs, timestamps, and money.
Constants
| Constant | Value | Meaning |
|---|---|---|
Float.MAX_VALUE | 3.4028235E38 | Largest finite value |
Float.MIN_VALUE | 1.4E-45 | Smallest positive value (not the most negative) |
Float.MIN_NORMAL | 1.1754944E-38 | Smallest positive value with full precision |
Float.POSITIVE_INFINITY / NEGATIVE_INFINITY | Infinity / -Infinity | Results of overflow or division by zero |
Float.NaN | NaN | "Not a number," for example 0f / 0f |
Float.SIZE / Float.BYTES | 32 / 4 | Size |
Special Values: Infinity, NaN, and -0.0
Floating-point arithmetic never throws an exception for division by zero or overflow. It produces special values instead:
float inf = 1f / 0f; // Infinity
float nan = 0f / 0f; // NaN
Float.isInfinite(inf); // true
Float.isFinite(3f); // true
Float.isNaN(nan); // true
NaN has unusual rules: it is not equal to anything, including itself:
System.out.println(nan == nan); // false
System.out.println(Float.isNaN(nan)); // true: the correct test
Floating point also has a negative zero. 0.0f == -0.0f is true, but 1 / -0.0f is -Infinity.
== versus Float.compare and equals
The == operator follows IEEE 754 rules. Float.compare and Float.equals instead define a total ordering suitable for sorting and collections:
| Expression | Result |
|---|---|
nan == nan | false |
Float.compare(nan, nan) | 0 |
Float.valueOf(nan).equals(nan) | true |
0.0f == -0.0f | true |
Float.compare(0.0f, -0.0f) | 1 (positive zero is greater) |
This is why Float.compare is used in comparators, and why NaN keys work in a HashMap<Float, ...> even though NaN != NaN.
Comparing float Values
Exact equality is unreliable after any arithmetic. Compare with a tolerance suited to the magnitude of the values:
float a = 0.1f + 0.2f;
boolean close = Math.abs(a - 0.3f) < 1e-6f; // true
Whether exact equality succeeds depends on how each value happens to round. With float, 0.1f + 0.2f == 0.3f is actually true, while with double, 0.1 + 0.2 == 0.3 is false. Neither result should be relied on.
Parsing and Converting
float a = Float.parseFloat("3.14"); // 3.14
float b = Float.parseFloat("1e3"); // 1000.0
Float c = Float.valueOf("2.5"); // boxed
String s = Float.toString(3.14f); // "3.14"
double d = 3.14f; // widening: 3.140000104904175
int i = (int) 3.99f; // 3: truncated toward zero
int r = Math.round(3.5f); // 4: Math.round(float) returns int
parseFloat throws NumberFormatException for invalid text. Widening a float to double is automatic and exact, but it exposes the float's approximation as extra digits.
float or double?
| Consideration | float | double |
|---|---|---|
| Precision | ~7 significant digits | ~15–16 significant digits |
| Range | ±3.4 × 10³⁸ | ±1.8 × 10³⁰⁸ |
| Memory | 4 bytes | 8 bytes |
| Default for decimal literals | No (needs f) | Yes |
| Arithmetic speed on modern CPUs | Similar | Similar |
Use double by default: it is Java's default floating-point type, every Math method uses it, and its extra precision prevents many accuracy problems.
Use float when memory or bandwidth dominates and 7 digits are enough:
- Large arrays of measurements, coordinates, or pixel values (a million floats take about 4 MB instead of 8 MB).
- Graphics and game engines, and GPU APIs that expect 32-bit floats.
- Machine-learning tensors and embeddings.
- File formats or network protocols defined with 32-bit floats.
Use neither for money: use BigDecimal or integer amounts in the smallest currency unit.
Practical Example: Accumulated Rounding Error
A sensor reports 0.1 units ten million times. The program sums the readings with float and with double.
public class Fl {
public static void main(String[] args) {
int readings = 10_000_000;
float floatSum = 0f;
double doubleSum = 0.0;
for (int i = 0; i < readings; i++) {
floatSum += 0.1f;
doubleSum += 0.1;
}
System.out.println("Expected: " + readings * 0.1);
System.out.println("float sum: " + floatSum);
System.out.println("double sum: " + doubleSum);
float[] big = new float[1_000_000];
System.out.println(big.length * Float.BYTES / 1024 + " KB vs " + big.length * Double.BYTES / 1024 + " KB");
}
}
Output:
Expected: 1000000.0
float sum: 1087937.0
double sum: 999999.9998389754
3906 KB vs 7812 KB
The float total is off by nearly 88,000, about 9%. Two effects combine. First, 0.1f is not exactly 0.1. More importantly, as the running total grows, the gap between representable floats grows too: near one million, floats are spaced 0.0625 apart, so adding 0.1 is rounded to a multiple of 0.0625 each time, and the error compounds over millions of additions. The double sum also accumulates error, but its 53 bits of precision keep it within a tiny fraction of the expected value. The last line shows float's advantage: a million values take half the memory. The lesson is to use float for storing large amounts of moderate-precision data, and double for accumulating and computing.