JAVA / VARIABLES, PRIMITIVES AND TYPES
The eight primitives and their value ranges
Name all eight Java primitives with their bit widths, state each one's exact range, and explain why the signed ranges are asymmetric.
What you will learn
- Recall each primitive's bit width and derive its range from that width
- Explain why byte runs -128 to 127 rather than -127 to 127
- Read limits from Byte.MIN_VALUE, Integer.MAX_VALUE and Character.MAX_VALUE
- Avoid the Double.MIN_VALUE trap: it is the smallest positive, not the lowest
Understanding The eight primitives and their value ranges
Java has exactly eight primitive types and no mechanism for adding more: byte, short, int, long, float, double, char and boolean. Their widths belong to the language rather than to the machine, so a byte is 8 bits and an int is 32 bits whether the code runs on a phone or a server, unlike C where int can change size between compilers. That fixed width is the whole story behind the ranges: a type with n bits has exactly 2^n distinct bit patterns, and the range is just a decision about which numbers those patterns stand for.
The four signed integer types interpret their bits as two's complement, which hands half the patterns to negative numbers. With n bits that yields -2^(n-1) through 2^(n-1)-1, and the two ends differ because zero has to occupy one of the non-negative patterns, so the positive end stops one step short. That is why byte is -128 to 127, short is -32768 to 32767, int is -2147483648 to 2147483647, and long is -9223372036854775808 to 9223372036854775807. char breaks the pattern: it is 16 bits like short, but unsigned, so it covers 0 to 65535 and is the only unsigned type in the language.
float and double are not a plain count of consecutive values; IEEE 754 spends part of the bits on an exponent, so the extremes are enormous, near 3.4e38 and 1.8e308, and the gap between neighbouring representable values grows with magnitude. Their limit constants are also named misleadingly: Float.MIN_VALUE and Double.MIN_VALUE are the smallest positive non-zero values, 1.4E-45 and 4.9E-324, while the lowest finite value of each is -MAX_VALUE. boolean has no numeric range at all and the specification deliberately leaves its storage size open, which is why Boolean carries no MIN_VALUE, MAX_VALUE or SIZE constant.
public class PrimitiveRanges {
public static void main(String[] args) {
System.out.println("byte " + Byte.SIZE + " bits " + Byte.MIN_VALUE + " .. " + Byte.MAX_VALUE);
System.out.println("short " + Short.SIZE + " bits " + Short.MIN_VALUE + " .. " + Short.MAX_VALUE);
System.out.println("int " + Integer.SIZE + " bits " + Integer.MIN_VALUE + " .. " + Integer.MAX_VALUE);
System.out.println("long " + Long.SIZE + " bits " + Long.MIN_VALUE + " .. " + Long.MAX_VALUE);
System.out.println("char " + Character.SIZE + " bits " + (int) Character.MIN_VALUE + " .. " + (int) Character.MAX_VALUE);
System.out.println("float " + Float.SIZE + " bits +/- " + Float.MAX_VALUE);
System.out.println("double " + Double.SIZE + " bits +/- " + Double.MAX_VALUE);
System.out.println("boolean unspecified only false and true");
}
}Each primitive is a fixed-width block of bits, and its range is the mechanical consequence of that width plus the interpretation applied to it: two's complement, unsigned, or IEEE 754.
Worked examples
Where the asymmetry comes from
Counts the 8-bit patterns a byte has and shows that the -128 to 127 split uses every one of them.
public class ByteBudget {
public static void main(String[] args) {
int patterns = 1 << Byte.SIZE;
int negatives = -Byte.MIN_VALUE;
int positives = Byte.MAX_VALUE;
System.out.println("bit patterns: " + patterns);
System.out.println("negatives: " + negatives);
System.out.println("zero: 1");
System.out.println("positives: " + positives);
System.out.println("total used: " + (negatives + 1 + positives));
}
}Example explained
Line 11 << Byte.SIZE is 2 to the power 8, the number of distinct bit patterns an 8-bit byte can hold.
Line 2-Byte.MIN_VALUE prints 128, so the negative side really does have 128 usable values.
Line 3Zero consumes one non-negative pattern, which is exactly why the positive side stops at 127.
Line 4The totals meet at 256, so the odd-looking range wastes nothing: it is arithmetic, not a design whim.
MIN_VALUE means two different things
Shows that the floating point MIN_VALUE constants are tiny positive numbers while the integer ones are true lower bounds.
public class FloatingLimits {
public static void main(String[] args) {
System.out.println(Double.MIN_VALUE);
System.out.println(Double.MIN_VALUE > 0);
System.out.println(-Double.MAX_VALUE);
System.out.println(Float.MIN_VALUE);
System.out.println(Integer.MIN_VALUE > 0);
}
}Example explained
Line 1Double.MIN_VALUE is the smallest positive non-zero double, so comparing it with 0 yields true.
Line 2The lowest finite double has to be written as -Double.MAX_VALUE; no constant is provided for it.
Line 3Float.MIN_VALUE is the same idea at float width, 1.4E-45 rather than a large negative number.
Line 4Integer.MIN_VALUE is a genuine lower bound, so the shared name carries opposite meanings across wrapper classes.
Range constants as a guard
Uses Short.MIN_VALUE and Short.MAX_VALUE to test whether long values would fit in a short.
public class FitsInShort {
public static void main(String[] args) {
long[] values = {0L, 32767L, 32768L, -32768L, -32769L};
for (long v : values) {
boolean fits = v >= Short.MIN_VALUE && v <= Short.MAX_VALUE;
System.out.println(v + " fits in short: " + fits);
}
}
}Example explained
Line 1The two short constants participate in a long comparison, so the check is safe for any long input.
Line 232768 fails by a single step because the upper edge is 32767.
Line 3-32768 passes, and a hand-typed bound of -32767 would have rejected a perfectly storable value.
Line 4Naming the bounds through constants keeps both edges right without remembering the digits.
Important notes
The specification fixes the width of seven primitives but leaves boolean storage to the JVM, so Boolean offers no SIZE, MIN_VALUE or MAX_VALUE.
long x = -9223372036854775808L; compiles, while the same digits without the leading minus do not: that final value is only reachable through unary minus, and 2147483648 behaves the same way for int.
Common mistakes
Assuming the lower bound is -MAX_VALUE and writing a guard such as v >= -32767 for a short, which silently rejects the legal value -32768.
Seeding a maximum search with Double.MIN_VALUE: it is +4.9E-324, so a set of negative measurements reports 4.9E-324 as its largest element.
Expecting char to be a signed 16-bit type and writing char c = -1;, which does not compile because char's range starts at 0.
Try it yourself
Change, predict, then run
In a browser editor print MIN_VALUE and MAX_VALUE for byte, short and int, then print (long) MAX_VALUE - MIN_VALUE + 1 for each and confirm you get 256, 65536 and 4294967296. Add a comment explaining why that expression cannot work for long.
Open the Java workspaceCheck your understanding
A method receives an int and must accept only values that could be stored in a short. Which condition is correct?
- v >= Short.MIN_VALUE && v <= Short.MAX_VALUE
- v >= -Short.MAX_VALUE && v <= Short.MAX_VALUE
- Math.abs(v) <= Short.MAX_VALUE
- v > Short.MIN_VALUE && v < Short.MAX_VALUE
Show answer
short spans -32768 to 32767, so only a check against both real constants accepts every storable value. The -Short.MAX_VALUE version looks tidy and symmetric, which is why it is the usual guess, but it and the Math.abs form both reject the legal value -32768; the last option additionally throws away both edges.