Why BigDecimal Can Lose Precision: A Real-World Checkout Failure Explained
This article analyzes a checkout‑system outage caused by BigDecimal precision loss, reproduces the bug with Java code, explains the underlying double‑to‑long conversion issue, and recommends using String‑based construction for accurate monetary calculations.
Background
We often use BigDecimal when calculating or displaying monetary amounts, and it is highly recommended for financial values.
However, improper use of its constructors can cause unnecessary trouble or even monetary loss, leading to incidents.
Incident
The checkout service experienced an error while calculating product prices, preventing orders from being paid.
Problem Description
Checkout failed to compute the amount, causing payment failure.
Incident Level
P0
Incident Timeline
13:44 – Alert received, payment success rate dropped to 60%.
13:50 – Quickly rolled back the deployed code, service restored.
14:20 – Reviewed code; pre‑release testing identified the issue.
14:58 – Fixed the problematic code and redeployed, service fully recovered.
Root Cause
Precision loss in BigDecimal when handling monetary calculations.
Analysis
First, we reproduce the problem with a short Java program:
public static void main(String[] args) {
BigDecimal bigDecimal = new BigDecimal(88);
System.out.println(bigDecimal);
bigDecimal = new BigDecimal("8.8");
System.out.println(bigDecimal);
bigDecimal = new BigDecimal(8.8);
System.out.println(bigDecimal);
}Running the program yields the following output:
Testing shows that using double or float loses precision, while String and int do not. To understand why, we inspect the source of the constructor:
public static long doubleToLongBits(double value) {
long result = doubleToRawLongBits(value);
// Check for NaN based on values of bit fields, maximum
// exponent and nonzero significand.
if (((result & DoubleConsts.EXP_BIT_MASK) == DoubleConsts.EXP_BIT_MASK) &&
(result & DoubleConsts.SIGNIF_BIT_MASK) != 0L)
result = 0x7ff8000000000000L;
return result;
}The problem lies in doubleToRawLongBits, which converts a double to a long using native (C++) code. The loss occurs because converting a decimal fraction to binary cannot represent the exact value.
BigDecimalhandles this by scaling the decimal number to an integer and keeping the precision information.
Key Points
1. float and double are designed for scientific and engineering calculations, providing fast approximations over a wide range.
2. They do not guarantee exact results and should not be used where precision is required.
3. Large floating‑point numbers are automatically expressed in scientific notation, which is an approximation.
4. Converting decimal fractions to binary can produce infinite repeats or exceed the mantissa length.
Conclusion
Therefore, when precise calculations are needed, especially for money, prefer constructing BigDecimal from String (or integer) values rather than from floating‑point literals.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
