Why BigDecimal Can Lose Precision and How to Safely Handle Money in Java
This article examines a real‑world cash‑register failure caused by BigDecimal precision loss, explains why using double or float constructors leads to errors, demonstrates the problem with code samples, and provides a robust utility class for accurate monetary calculations in Java.
Background
When calculating or displaying monetary amounts we often use BigDecimal, which is highly recommended for financial values. However, misuse of its many constructors can cause unnecessary trouble or even monetary loss, leading to incidents.
Incident
The cash register reported an error that prevented order payment, causing a drop in payment success rate to 60%.
Accident Level
P0
Incident Process
13:44 – Alarm received, order payment failed.
13:50 – Quickly rolled back the deployed code and restored normal operation.
14:20 – Reviewed the code and identified the problem in pre‑release testing.
14:58 – Fixed the problematic code and redeployed, restoring service.
Root Cause
The loss of precision occurs when BigDecimal is constructed from double or float values. The underlying doubleToRawLongBits method converts the floating‑point number to a long using native (C++) code, and binary representation of decimal fractions inevitably loses precision.
Reason Analysis
Floating‑point types ( float and double) are designed for scientific and engineering calculations, providing fast approximations over a wide range but not exact results. When the fractional part is converted to binary, it may produce an infinite repeating sequence or exceed the mantissa length, leading to rounding errors. BigDecimal avoids this by scaling the decimal number to an integer and retaining the scale information.
Summary
For precise monetary calculations, always construct BigDecimal from a String (or other exact representations) rather than from floating‑point literals.
Correct Usage
BigDecimal bigDecimal2 = new BigDecimal("8.8");
BigDecimal bigDecimal3 = new BigDecimal("8.812");
System.out.println(bigDecimal2.compareTo(bigDecimal3));
System.out.println(bigDecimal2.add(bigDecimal3));Note that BigDecimal objects must be operated on using their own methods (add, subtract, multiply, divide); they cannot be used with the traditional arithmetic operators.
Tool Sharing
The following utility class provides convenient methods for precise arithmetic with double and
float> values by internally converting them to <code>BigDecimalusing String constructors.
import java.math.BigDecimal;
public class BigDecimalUtils {
public static BigDecimal doubleAdd(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2);
}
public static BigDecimal floatAdd(float v1, float v2) {
BigDecimal b1 = new BigDecimal(Float.toString(v1));
BigDecimal b2 = new BigDecimal(Float.toString(v2));
return b1.add(b2);
}
public static BigDecimal doubleSub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2);
}
public static BigDecimal floatSub(float v1, float v2) {
BigDecimal b1 = new BigDecimal(Float.toString(v1));
BigDecimal b2 = new BigDecimal(Float.toString(v2));
return b1.subtract(b2);
}
public static BigDecimal doubleMul(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2);
}
public static BigDecimal floatMul(float v1, float v2) {
BigDecimal b1 = new BigDecimal(Float.toString(v1));
BigDecimal b2 = new BigDecimal(Float.toString(v2));
return b1.multiply(b2);
}
public static BigDecimal doubleDiv(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);
}
public static BigDecimal floatDiv(float v1, float v2) {
BigDecimal b1 = new BigDecimal(Float.toString(v1));
BigDecimal b2 = new BigDecimal(Float.toString(v2));
return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);
}
public static int doubleCompareTo(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.compareTo(b2);
}
public static int floatCompareTo(float v1, float v2) {
BigDecimal b1 = new BigDecimal(Float.toString(v1));
BigDecimal b2 = new BigDecimal(Float.toString(v2));
return b1.compareTo(b2);
}
}Illustrations
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.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
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.
