Understanding BigDecimal Precision Issues and Proper Usage in Java
This article explains why using floating‑point types for monetary calculations can cause precision loss, demonstrates the problem with BigDecimal constructors, analyzes the underlying double‑to‑long conversion, and provides a utility class with safe BigDecimal operations for backend Java applications.
Background – When handling monetary values, developers often use BigDecimal because it avoids the precision problems of float and double . Improper use of its constructors can still lead to loss of precision and financial loss.
Problem Description – A cash‑register incident reported that orders could not be paid due to a calculation error, classified as a P0 outage.
Incident Process
13:44 – Alert received, payment failure, availability dropped to 60%.
13:50 – Quick rollback of the offending code restored service.
14:20 – Code review on pre‑release environment identified the issue.
14:58 – Fixed code deployed and service fully recovered.
Root Cause – Using new BigDecimal(8.8) (or any double/float) invokes the constructor that converts the binary floating‑point value to a long via doubleToRawLongBits , which loses decimal precision. Constructing from a String (e.g., "8.8" ) preserves the exact decimal representation.
Analysis – Floating‑point types are designed for scientific and engineering calculations where a small rounding error is acceptable; they cannot guarantee exact results for financial data. Converting a decimal fraction to binary can produce an infinite repeating fraction, causing rounding errors.
Conclusion – For precise monetary calculations, always create BigDecimal from String (or int ) and use its provided arithmetic methods instead of the traditional operators.
Recommended Usage Example
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 manipulated with methods such as add , subtract , multiply , and divide ; they cannot be used with the standard arithmetic operators.
Utility Class Sharing
import java.math.BigDecimal;
/**
* @Author shuaige
* @Date 2022/4/17
* @Version 1.0
*/
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));
// keep two decimal places, round half up
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);
}
/**
* Compare two numbers using BigDecimal
*/
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);
}
}The article concludes with a reminder to use the provided utility methods for safe financial calculations and invites readers to share the knowledge.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
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.