Java Double vs BigDecimal: Precision and Performance
java double vs bigdecimal: Compare Java's double and BigDecimal for exact decimal arithmetic, precision, performance, and when each type is the right choice.
The java double vs bigdecimal decision usually surfaces when a calculation produces a result that looks wrong. In Java, 0.1 + 0.2 evaluated as double gives 0.30000000000000004, while the same operation on BigDecimal values gives exactly 0.3. Neither type is universally better; the correct choice depends on whether the calculation needs exact decimal arithmetic or can tolerate binary floating-point approximation.
How double Represents Decimal Values
double is a 64-bit IEEE 754 binary floating-point primitive. It stores a sign, an 11-bit exponent, and a 52-bit mantissa, all in base 2. Because the mantissa is binary, most decimal fractions cannot be represented exactly. The value 0.1 is stored as the closest binary fraction, which is slightly larger than the decimal 0.1. This approximation is invisible in most output because System.out.println rounds, but it becomes visible when arithmetic compounds the error.
double a = 0.1; double b = 0.2; System.out.println(a + b); // 0.30000000000000004
The error is small, but it is not zero. In a loop that accumulates many such values, the difference can grow large enough to break equality checks or produce incorrect totals.
What BigDecimal Changes About Arithmetic
BigDecimal represents a signed decimal number as an unscaled integer value combined with a scale. new BigDecimal("100.50") stores the unscaled value 10050 with scale 2, meaning the value is 100.50. Arithmetic is performed on the unscaled integers, so decimal fractions stay exact as long as the operation does not require rounding.
BigDecimal x = new BigDecimal("0.1"); BigDecimal y = new BigDecimal("0.2"); System.out.println(x.add(y)); // 0.3
Addition, subtraction, and multiplication are exact when the result fits within the available precision. Division is the operation that requires care, because a non-terminating decimal result cannot be represented without a rounding mode.
Constructing BigDecimal Without Losing Precision
The constructor you choose determines whether the value is exact. new BigDecimal(0.1) converts the double's binary approximation into a decimal expansion, producing a long string of digits. new BigDecimal("0.1") parses the decimal string directly and is exact.
BigDecimal fromDouble = new BigDecimal(0.1); System.out.println(fromDouble); // 0.1000000000000000055511151231257827021181583404541015625 BigDecimal fromString = new BigDecimal("0.1"); System.out.println(fromString); // 0.1
For values that come from user input, configuration files, or database columns, always use the string constructor or BigDecimal.valueOf(double). The valueOf method uses the canonical string representation of the double, which avoids the long expansion while still accepting a primitive.
equals, compareTo, and the Scale Trap
BigDecimal.equals considers scale, so new BigDecimal("1.0") is not equal to new BigDecimal("1.00") even though the numeric values are identical. compareTo ignores scale and compares numeric value only.
BigDecimal one = new BigDecimal("1.0"); BigDecimal oneHundred = new BigDecimal("1.00"); System.out.println(one.equals(oneHundred)); // false System.out.println(one.compareTo(oneHundred)); // 0
When using BigDecimal as a key in a HashMap or storing it in a HashSet, the scale-sensitive equals and hashCode behavior can cause unexpected misses. Use compareTo for ordering and numeric comparison, and normalize scale explicitly when scale-insensitive equality is required.
Performance and Memory Tradeoffs
double is a primitive: 8 bytes of storage, no heap allocation, and arithmetic that maps directly to CPU floating-point instructions. BigDecimal is an object that wraps a BigInteger unscaled value, so every operation allocates objects and performs integer arithmetic on arbitrary-precision values. In a tight loop that performs millions of operations, BigDecimal can be orders of magnitude slower and consume significantly more memory.
This does not mean BigDecimal is unsuitable for production. It means the cost is justified only when exact decimal arithmetic is a requirement. For scientific computation, graphics, simulation, or any performance-sensitive numeric code, double is the appropriate default. For monetary calculations, tax, interest, or any value that must round to a specific decimal scale, the cost of BigDecimal is the price of correctness.
| Criterion | double | BigDecimal |
|---|---|---|
| Representation | IEEE 754 binary64 | Unscaled integer + scale |
| Decimal exactness | Approximate for most fractions | Exact for decimal fractions |
| Storage | 8-byte primitive | Object with BigInteger value |
| Arithmetic cost | Low | Higher, allocates per operation |
| Division | IEEE rounding | Requires scale and RoundingMode |
| Best fit | Scientific, performance-sensitive | Financial, exact decimal math |
Choosing Between double and BigDecimal by Scenario
The decision criteria are concrete:
- Use
doublewhen the values are measurements, ratios, or results of scientific formulas where a small relative error is acceptable and performance matters. - Use
BigDecimalwhen the values represent money, quantities that must round to a fixed decimal scale, or anything that will be compared for exact equality after arithmetic. - Use
doublewhen the calculation is part of a hot path such as signal processing or physics simulation. - Use
BigDecimalwhen the result feeds into an audit trail, invoice, or legal document where the exact decimal value must be reproducible.
A common middle ground is to use double for intermediate scientific computation and convert to BigDecimal only at the boundary where the value is persisted or displayed. This keeps the hot path fast while preserving exactness at the points where it matters.
Division Requires an Explicit Rounding Mode
Dividing two BigDecimal values with a non-terminating result throws ArithmeticException unless you provide a scale and a RoundingMode.
BigDecimal one = new BigDecimal("1"); BigDecimal three = new BigDecimal("3"); // one.divide(three) throws ArithmeticException BigDecimal result = one.divide(three, 4, RoundingMode.HALF_UP); System.out.println(result); // 0.3333
The choice of rounding mode is a business decision, not a technical one. HALF_UP matches common rounding expectations in financial contexts, while HALF_EVEN avoids bias in statistical calculations. Define the rounding mode once in a shared utility or constant so that every division in the codebase behaves consistently. Mixing rounding modes across modules is a common source of subtle discrepancies in totals and reports.