Appearance
IEEE 754: How Float and Double Are Stored in Memory
Type this into any Java program and run it:
java
System.out.println(0.1 + 0.2);You expect to see 0.3. What you actually see is 0.30000000000000004.
Now try this:
java
float f = 0.7f;
System.out.println(f);You expect 0.7. What you actually see is 0.6999999880790710.
If you have never seen this before, your first instinct is probably "something is broken." Nothing is broken. This is correct behavior. Java is doing exactly what it is supposed to do. But to understand why, you need to understand how floating point numbers are physically stored inside computer memory, and that is exactly what this article teaches you from the ground up.
This is also one of the most frequently asked Java interview questions, even for experienced developers. A lot of experienced developers get confused by this. By the end of this article you will be able to explain it completely, draw the bit layout on a whiteboard, and walk an interviewer through the full process step by step.
Why Computers Cannot Store Most Decimals Exactly
Before getting to IEEE 754, you need to understand the root problem.
Your computer stores everything as binary, which means ones and zeros. For whole numbers this works perfectly. The number 4 in binary is 100. The number 5 is 101. Every integer you could ever want has a clean, finite binary representation. There is no ambiguity, no approximation.
Fractions are a completely different story.
Think about what happens when you try to write 1/3 as a decimal. You get 0.333... and the threes go on forever. There is no point at which you can stop and say the representation is complete. The decimal system simply cannot express one third in a finite number of digits. So if you need to store it, you are forced to round at some point. You might write 0.333 or 0.3333333, but no matter how many digits you keep, you are storing an approximation, not the real value.
Binary has the exact same problem, but the set of fractions that cannot be represented is different. In decimal, 0.1 is fine. In binary, 0.1 is a repeating fraction. It goes on forever. So when Java stores 0.1 in a float or a double, it stores the closest value that fits in the available bits. That value is not exactly 0.1. It is something like 0.1000000000000000055511151231257827... for a double.
When you add two of these approximations together, the errors in each one combine. The result you get back is not exactly 0.3. It is the sum of two approximations of 0.1, and that sum happens to be 0.30000000000000004.
This is not a Java bug. This is a mathematical property of binary representation that exists in every programming language that uses floating point arithmetic. Python has the same issue. JavaScript has the same issue. C has the same issue. Understanding this is what separates developers who debug floating point problems in five minutes from developers who spend an hour assuming their code is wrong.
The IEEE 754 Standard
IEEE 754 is an international standard that defines exactly how floating point numbers must be stored in binary. Java's float and double both follow this standard. Understanding the standard means you can predict exactly how any floating point value will behave.
The key insight of IEEE 754 is that every non zero floating point number can be written in the form:
(sign) × 1.mantissa × 2^exponentThis is scientific notation, but in binary instead of decimal. In decimal you might write 6.022 × 10^23. In binary you write 1.something × 2^something. IEEE 754 dedicates specific bits in memory to each of these three components.
Float: 32 Bits
A Java float uses 32 bits arranged like this:
[ 1 bit sign ][ 8 bits exponent ][ 23 bits mantissa ]| Section | Bits | Purpose |
|---|---|---|
| Sign | 1 | 0 means positive, 1 means negative |
| Exponent | 8 | The power of 2, stored with a bias of 127 |
| Mantissa | 23 | The significant digits after the leading 1 |
Double: 64 Bits
A Java double uses 64 bits arranged like this:
[ 1 bit sign ][ 11 bits exponent ][ 52 bits mantissa ]| Section | Bits | Purpose |
|---|---|---|
| Sign | 1 | 0 means positive, 1 means negative |
| Exponent | 11 | The power of 2, stored with a bias of 1023 |
| Mantissa | 52 | The significant digits after the leading 1 |
A double has more than twice as many mantissa bits as a float. That means it can track more decimal places before the approximation error shows up. But both types have the same fundamental limitation: infinite binary fractions get cut off at some point, and information is lost.
The Four Steps to Store a Floating Point Number
Every floating point value goes through the same four steps before it gets stored in memory. Learning these four steps is the key to understanding everything else.
Step one: convert the number to binary. Step two: normalize into the form 1.something × 2^exponent. Step three: add the bias to the exponent. Step four: fill the bits into the sign, exponent, and mantissa slots.
Let us walk through both an easy case and a hard case to see what happens.
The Easy Case: Storing 4.125f
The number 4.125 is a good starting example because it converts to binary perfectly without any infinite repetition.
Step One: Convert to Binary
You handle the integer part and the fractional part separately.
The integer part is 4. Converting 4 to binary: you repeatedly divide by 2 and track remainders. 4 ÷ 2 = 2 remainder 0, 2 ÷ 2 = 1 remainder 0, 1 ÷ 2 = 0 remainder 1. Reading the remainders from bottom to top gives 100. So 4 in binary is 100.
The fractional part is 0.125. To convert a decimal fraction to binary, you repeatedly multiply by 2 and track whether the result is greater than or equal to 1. If it is, the next bit is 1 and you subtract the 1 and continue. If it is not, the next bit is 0 and you continue.
0.125 × 2 = 0.25 → digit is 0
0.25 × 2 = 0.50 → digit is 0
0.50 × 2 = 1.00 → digit is 1 (stop, because result is exact)Reading the digits from top to bottom gives 0.001. So 0.125 in binary is 0.001.
Putting both parts together: 4.125 in binary is 100.001.
Step Two: Normalize
You need the number in the form 1.something × 2^exponent. Right now you have 100.001. You move the binary point to the left until there is exactly one digit before it. Moving it two places left gives you:
1.00001 × 2^2The mantissa is 00001 (the digits after the 1.) and the exponent is 2.
Step Three: Add the Bias
Here is something that surprises people: the exponent stored in memory is not the actual exponent. It is the actual exponent plus a bias value.
For float, the bias is 127. For double, the bias is 1023.
Why does the bias exist? Because the exponent can be negative. If you are storing a very small number like 0.001 in binary, after normalization you end up with something like 1.0 × 2^(minus 3). The exponent is negative 3. But the 8 bits reserved for the exponent in a float do not use a sign bit. They store a plain non negative integer. Using a bias solves this: instead of storing minus 3, you store minus 3 + 127 = 124. Any stored exponent value below 127 corresponds to a negative actual exponent, and any value above 127 corresponds to a positive one.
For 4.125, the actual exponent is 2, so the stored exponent is 2 + 127 = 129. In 8 bit binary, 129 is 10000001.
Step Four: Fill the Bits
Now you pack everything into the 32 bit layout:
Sign: 0 (positive)
Exponent: 10000001 (129 in binary)
Mantissa: 00001000000000000000000
(first 23 bits of the mantissa, padded with zeros)Notice something important: the leading 1 in 1.00001 is NOT stored in the mantissa. IEEE 754 assumes a float always has a leading 1 before the binary point, so that bit is implied and not wasted. Only the digits after the 1. go into the mantissa field.
Verifying: Reading the Value Back
To verify this is correct, you can reconstruct the value using the formula:
value = (−1)^sign × (1 + mantissa) × 2^(exponent − 127)Plugging in: (−1)^0 × (1 + 0.03125) × 2^(129−127) = 1 × 1.03125 × 4 = 4.125.
The 0.03125 comes from the mantissa: the only bit that is set is at position 5 after the decimal point, and 2^(minus 5) is 0.03125. You get 4.125 back exactly. This number had a clean binary representation, so no information was lost.
The Hard Case: Storing 0.7f
Now let us try 0.7f. This is the example that produces the surprising output in the interview question.
Step One: Convert to Binary
Start multiplying by 2 and tracking the digits:
0.7 × 2 = 1.4 → digit is 1 (continue with 0.4)
0.4 × 2 = 0.8 → digit is 0 (continue with 0.8)
0.8 × 2 = 1.6 → digit is 1 (continue with 0.6)
0.6 × 2 = 1.2 → digit is 1 (continue with 0.2)
0.2 × 2 = 0.4 → digit is 0 (continue with 0.4)
0.4 × 2 = 0.8 → digit is 0 (continue with 0.8)Look at what happened: after six steps you are back to 0.8, which is the same value you had at step two. This means the digits from step two onward repeat forever. The binary representation of 0.7 is:
0.10110011001100110011001100110011...The pattern 0011 repeats infinitely. There is no finite binary representation for 0.7. This is the same problem as trying to write 1/3 as a decimal.
Step Two: Normalize
You move the binary point one place to the right to get a 1 before it:
1.0110011001100110011001100110011... × 2^(−1)The actual exponent is −1.
Step Three: Add the Bias
The stored exponent is −1 + 127 = 126. In 8 bit binary, 126 is 01111110.
Step Four: Fill the Bits and Cut the Mantissa
Here is where the problem happens. The mantissa after the 1. is:
0110011001100110011001100110011001100110011... (infinite)The float format gives you exactly 23 bits for the mantissa. You take the first 23 bits and then you stop. The rest of the infinite sequence is simply discarded. That information is gone permanently.
The 23 bits you store are:
01100110011001100110011The full bit layout stored in memory for 0.7f is:
Sign: 0
Exponent: 01111110 (126 in binary)
Mantissa: 01100110011001100110011Verifying: Reading the Value Back
When you reconstruct the value from these bits using the same formula, you do not get 0.7. The value you recover is approximately 0.699999988079071. That is exactly the number Java prints when you run System.out.println(0.7f).
The stored value is the nearest representable 32 bit float to 0.7. It is slightly less than 0.7 because the infinite pattern was cut off and the remaining bits rounded down. This is not a mistake. It is the closest value possible in 32 bits.
Double Has the Same Problem, Just More Bits
For a double, the process is identical. The only differences are:
The double format uses 1 sign bit, 11 exponent bits, and 52 mantissa bits. The bias for double is 1023 (which is 2^10 − 1).
When you store 0.7 as a double, the infinite 0011 repeating pattern gets cut off after 52 bits instead of 23 bits. So the approximation is much closer to 0.7, but it is still an approximation. That is why System.out.println((double)0.7) prints 0.7 while the actual stored value, visible through new BigDecimal(0.7), is:
0.6999999999999999555910790149937383830547332763671875Java's println rounds the display for readability. The actual stored bits still represent a value that is not exactly 0.7.
What This Means in Your Code
Now that you understand the storage mechanism, you can predict exactly how floating point arithmetic will behave:
java
// The classic surprise
System.out.println(0.1 + 0.2); // 0.30000000000000004
System.out.println(0.1 + 0.2 == 0.3); // false
// float rounds differently than double
System.out.println(0.1f + 0.2f); // 0.3 (float precision is different)
// Printing hides the problem
float f = 0.7f;
System.out.println(f); // 0.7 (display is rounded)
// BigDecimal reveals the truth
System.out.println(new java.math.BigDecimal(0.7));
// 0.6999999999999999555910790149937383830547332763671875Never Compare Floats with ==
This is one of the most common pitfalls in Java. Two floating point values that should be mathematically equal can differ by tiny amounts due to rounding, so == returns false even when the values are effectively the same. The correct approach is to check whether they are close enough:
java
double a = 0.1 + 0.2;
double b = 0.3;
double epsilon = 1e-9; // your tolerance threshold
if (Math.abs(a - b) < epsilon) {
System.out.println("Equal within acceptable tolerance");
}Choose your epsilon based on the precision you actually need. For most calculations, something between 1e-9 and 1e-12 works. The key is that you are never comparing for exact equality.
Errors Accumulate With Repeated Operations
A single float or double operation might have a tiny error. But in a loop with many operations, those tiny errors add up:
java
double price = 4.7;
double total = 0.0;
for (int i = 0; i < 10; i++) {
total += price;
}
System.out.println(total); // 47.00000000000001 — not 47.0Ten additions of 4.7 should give exactly 47.0. Instead you get 47.00000000000001. This is accumulation of floating point error across multiple operations.
The Golden Rule: Never Use Float or Double for Money
This is the rule that matters most in professional Java development. Financial calculations require exact decimal arithmetic. Floating point types cannot provide exact decimal arithmetic. Therefore, never use float or double for money, prices, interest rates, tax calculations, or any domain where decimal precision is a requirement.
The fact that System.out.println(4.7) prints 4.7 is misleading. Java is rounding the display. The actual stored value is slightly off. Accumulate those tiny errors over thousands of transactions and you have a real problem.
Java provides BigDecimal for exactly this purpose.
Using BigDecimal
java
import java.math.BigDecimal;
// WRONG: using double for money
double priceDouble = 4.7;
System.out.println(priceDouble); // looks fine: 4.7
// CORRECT: using BigDecimal for money
BigDecimal price = new BigDecimal("4.7");
System.out.println(price); // 4.7, and it is actually exactly 4.7
BigDecimal quantity = new BigDecimal("10");
BigDecimal total = price.multiply(quantity);
System.out.println(total); // 47.0, exactly correctWhen you use BigDecimal, the value 4.7 is stored as a decimal number internally, not converted to binary. There is no binary rounding problem. The number you put in is the number you get out.
The Critical Detail: Always Use the String Constructor
Here is a pitfall that catches even experienced developers. There are two ways to create a BigDecimal from a value like 0.1:
java
// WRONG: using the double constructor
BigDecimal wrong = new BigDecimal(0.1);
System.out.println(wrong);
// 0.1000000000000000055511151231257827021181583404541015625
// CORRECT: using the String constructor
BigDecimal correct = new BigDecimal("0.1");
System.out.println(correct);
// 0.1When you write new BigDecimal(0.1), you are passing a Java double to BigDecimal. That double already contains the binary rounding error. You are asking BigDecimal to represent the same imprecise value the double was already storing. The imprecision arrives with the argument before BigDecimal can do anything about it.
When you write new BigDecimal("0.1"), you are passing a string. BigDecimal parses the characters 0, ., and 1 directly and constructs the exact decimal value 0.1 without any double being involved at any point.
Always use the String constructor. This is not optional. The double constructor exists but should essentially never be used for user facing values.
Float vs Double: When to Use Which
java
float f = 3.14f; // requires the 'f' suffix; 4 bytes; ~7 significant decimal digits
double d = 3.14; // default floating point type; 8 bytes; ~15 to 16 significant decimal digitsIn Java, when you write a literal like 3.14 with no suffix, it is automatically treated as a double. To make it a float, you need the f suffix: 3.14f.
Use double as your default for all floating point calculations. It has more precision and there is essentially no reason to use float unless you have a specific memory constraint. In graphics programming or scientific simulations where you are storing millions of floating point values in arrays, float cuts the memory usage in half compared to double. In those contexts, the precision tradeoff is acceptable. For normal application code, use double.
Use BigDecimal for financial calculations, currency, prices, and anywhere exact decimal values matter.
Never use float for financial calculations.
Interview Preparation: The Questions You Will Be Asked
This topic appears in Java interviews at all experience levels. Here is how to handle each question:
Question: Why does System.out.println(0.7f) print 0.6999999... instead of 0.7?
Walk through the four steps. Step one: convert 0.7 to binary. Show that multiplying by 2 repeatedly produces an infinite repeating pattern 0.10110011001100110011.... Step two: normalize to 1.0110011... × 2^(−1). Step three: add the bias, so the stored exponent is −1 + 127 = 126. Step four: the mantissa only has 23 bits, so the infinite pattern is cut off after 23 bits. Information is permanently lost. When you reconstruct the value from those 23 bits, you get approximately 0.6999999, not 0.7. The stored value is the nearest representable 32 bit float to 0.7.
Question: What is the bias in IEEE 754 float, and why does it exist?
The bias for float is 127. The bias for double is 1023. It exists because the exponent field stores only non negative integers, but actual exponents can be negative. By subtracting the bias from whatever is stored in the exponent field, you recover the real exponent, which can be negative, zero, or positive. Instead of using a sign bit or two's complement for the exponent, IEEE 754 uses this bias approach.
Question: Why does 0.1 + 0.2 == 0.3 return false?
Because 0.1 and 0.2 each have infinite binary representations that are rounded when stored in 64 bit doubles. Adding two rounded approximations does not produce the same rounded approximation that you get when you store 0.3 directly. The two values differ by a tiny amount, so == returns false. The correct approach is to compare using Math.abs(a - b) < epsilon with a small tolerance.
Question: What is BigDecimal and when should you use it?
BigDecimal is a Java class that stores decimal numbers as decimal, not as binary floating point. It avoids binary rounding errors entirely. Use it whenever you need exact decimal arithmetic: financial calculations, currency, prices, tax, banking, anything where a rounding error in the 15th decimal place would be unacceptable. Always pass values to BigDecimal as strings, not as doubles.
Question: What is the difference between float and double in terms of memory and precision?
float uses 32 bits and provides approximately 7 significant decimal digits of precision. double uses 64 bits and provides approximately 15 to 16 significant decimal digits. Both follow IEEE 754. Both have the same fundamental binary representation problem. double is the default floating point type in Java. Use double unless you have a compelling reason to save memory at scale, in which case float might be appropriate.
The Complete Picture
Here is a summary of everything you have learned in this article:
Computers store everything in binary. Integers have exact binary representations. Fractional numbers often do not. Some fractions, like 0.125 which is 1/8, convert perfectly to binary because they are exact powers of 1/2. But fractions like 0.7 and 0.1 require infinitely many binary digits, just as 1/3 requires infinitely many decimal digits. When stored in a finite number of bits, the infinite representation is cut off, leaving an approximation.
IEEE 754 is the standard that defines how this approximation is structured. Every floating point number is stored as three parts: a sign bit, an exponent (stored with a bias), and a mantissa (the significant digits after the leading 1). A float uses 32 bits. A double uses 64 bits and is more precise.
The practical consequences are: never compare floats with ==, never use float or double for money, and when you need exact decimal arithmetic use BigDecimal with its String constructor.
java
// Demonstrating everything together
public class FloatDemo {
public static void main(String[] args) {
// The classic surprise
System.out.println(0.1 + 0.2); // 0.30000000000000004
System.out.println(0.1 + 0.2 == 0.3); // false
// The float surprise
float f = 0.7f;
System.out.println(f); // 0.7 (display rounded)
// BigDecimal reveals the stored value
System.out.println(new java.math.BigDecimal(f)); // 0.699999988079071044921875
// BigDecimal with String: exact
java.math.BigDecimal exact = new java.math.BigDecimal("0.7");
System.out.println(exact); // 0.7
// Money calculation: wrong way
double total = 0.0;
for (int i = 0; i < 10; i++) total += 4.7;
System.out.println(total); // 47.00000000000001
// Money calculation: right way
java.math.BigDecimal bdTotal = java.math.BigDecimal.ZERO;
java.math.BigDecimal bdPrice = new java.math.BigDecimal("4.7");
for (int i = 0; i < 10; i++) bdTotal = bdTotal.add(bdPrice);
System.out.println(bdTotal); // 47.0
}
}Understanding the four steps, the bias, the mantissa cutoff, and the BigDecimal solution gives you complete mastery of one of Java's most important and most misunderstood areas. Practice drawing the bit layout from memory, walk through the 0.7 example a few times until it is automatic, and you will be ready to answer any interview question on this topic confidently.