Quick Overview

Java uses the IEEE 754 standard for floating-point arithmetic. Understanding how decimals are stored in binary explains why precision errors happen and why double is the default choice.

Demystifying the Fraction-to-Binary Math

The Quote Explained

“Add the fraction to itself and retain the integer until the entire sequence becomes 0”

This describes the standard algorithm for converting a decimal fraction (like 0.625) into binary. Adding a number to itself () is mathematically identical to multiplying by 2 (). Proof

Step-by-Step Example: Converting to Binary

  1. Multiply the fraction by 2.
  2. Separate the result into its Integer part (which becomes our binary digit: 0 or 1) and the New Fraction.
  3. Repeat the process with the new fraction until it becomes 0 (or until you run out of precision bits).
StepOperationResultExtracted Integer (Binary Digit)Remaining Fraction
11
20
31 (Stop!)

Reading the extracted integers from top to bottom gives us ().

Why some numbers never terminate

Try this with 0.3:

  • (Int: 0, Frac: )
  • (Int: 1, Frac: )
  • (Int: 0, Frac: )
  • (Int: 0, Frac: )
  • (Int: 1, Frac: ) — Infinite loop begins!

This is why fractions like 0.1 or 0.3 cannot be represented precisely in binary, leading to classic floating-point inaccuracies like 0.1 + 0.2 = 0.30000000000000004.

Representation in Java (float vs. double)

Java implements the IEEE 754 binary floating-point standard. Every floating-point number is stored using three components:

  1. Sign bit (): for positive, for negative.
  2. Exponent (): Represents the magnitude (scaled/biased).
  3. Mantissa / Fraction (): Represents the actual precision digits.
graph TD
    subgraph IEEE 754 32-bit Float
        A1[Sign: 1 bit] ---> A2[Exponent: 8 bits] ---> A3[Mantissa: 23 bits]
    end
    subgraph IEEE 754 64-bit Double
        B1[Sign: 1 bit] ---> B2[Exponent: 11 bits] ---> B3[Mantissa: 52 bits]
    end

3. Limits and Ranges

Because bits are finite, floating-point types have strict boundaries. You can access these programmatically in Java using wrapper classes:

public class FloatLimits {
    public static void main(String[] args) {
        System.out.println("Float Min: " + Float.MIN_VALUE);
        System.out.println("Float Max: " + Float.MAX_VALUE);
        
        System.out.println("Double Min: " + Double.MIN_VALUE);
        System.out.println("Double Max: " + Double.MAX_VALUE);
    }
}
Data TypeBit WidthRange (Approximate)Significant Decimal Digits
float32 bits to ~6 to 7 digits
double64 bits to ~15 to 16 digits

Why is double the Default in Java?

If you write a raw decimal literal in Java (e.g., double x = 5.2;), the compiler automatically treats it as a double.

// 5.2 is treated as a double by default. 
// Compilation error if you try to assign directly to a float without a cast or 'f' suffix:
float f = 5.2f; // Correct suffix

Reasons why double is the default

  • Precision Requirements:
    • Modern applications require high accuracy. float’s ~7 digits of precision accumulate rounding errors very quickly in standard business or scientific calculations.
  • Historical & Hardware Architecture:
    • Modern CPU architectures (64-bit ALUs/Floating-Point Units) are heavily optimized for 64-bit double-precision calculations natively.
  • Avoiding Accidental Data Loss:
    • Defaulting to the higher-precision type prevents silent truncation of data, reserving float strictly for specialized scenarios where memory constraints are absolute (e.g., 3D graphics buffers, massive arrays of audio data, or machine learning model weights).

Float precision can cause unexpected behaviour

1.1 Float Precision Issue

Equality Check Trap

Because of this hidden binary rounding, never compare floats directly using ==. For example:

(0.1f + 0.2f) == 0.3f can actually evaluate to false depending on the JVM and compiler optimizations, because the sum doesn’t precisely bit-match 0.3f. Always use an epsilon (threshold) for comparisons:

boolean isEqual = Math.abs((a + b) - 0.3f) < 0.00001f;

Best Practice for Financial Data

Never use float or double for currency or exact financial calculations due to rounding artifacts. Always use java.math.BigDecimal instead.

BigDecimal price = new BigDecimal("0.1");
BigDecimal tax = new BigDecimal("0.2");
BigDecimal total = price.add(tax); // Exactly 0.3

Reference

Representation of Floating point nubmer