Why the String Constructor Makes BigDecimal Precise While Double Fails
This article explains how Java's BigDecimal class provides exact arithmetic for numbers beyond double's 16‑digit limit, demonstrates the pitfalls of using the double constructor with concrete examples, and guides developers through constructors, common methods, formatting, exception handling, and a utility class for high‑precision calculations.
BigDecimal Overview
Java's java.math.BigDecimal class enables exact arithmetic for numbers with more than 16 significant digits. While double can handle up to 16 digits, many applications require higher precision for very large or very small values.
For non‑critical calculations, float and double are sufficient, but converting a String to Float or Double loses precision. Therefore, when exact results are required, developers must use BigDecimal.
Because BigDecimal creates objects, arithmetic operators ( +, -, *, /) cannot be used directly; instead, the class provides specific methods, and all parameters must also be BigDecimal instances.
BigDecimal Common Constructors
1. Frequently Used Constructors
BigDecimal(int) – creates an object from an integer value.
BigDecimal(double) – creates an object from a double value.
BigDecimal(long) – creates an object from a long value.
BigDecimal(String) – creates an object from a string representation of a number.
2. Usage Analysis
Example code:
BigDecimal a = new BigDecimal(0.1);
System.out.println("a values is:" + a);
System.out.println("=====================");
BigDecimal b = new BigDecimal("0.1");
System.out.println("b values is:" + b);Result:
a values is:0.1000000000000000055511151231257827021181583404541015625
=====================
b values is:0.1Reason analysis:
The BigDecimal(double) constructor inherits the inexact binary representation of 0.1. The double value cannot be represented exactly in binary, so the resulting BigDecimal contains a long tail of digits.
The BigDecimal(String) constructor is deterministic; it creates a BigDecimal that exactly matches the textual value 0.1. Hence it is generally recommended.
If a double must be the source, use BigDecimal.valueOf(double) which internally converts the double to its string representation before constructing the BigDecimal, avoiding the unexpected tail.
BigDecimal Common Methods
1. Core Arithmetic Methods
add(BigDecimal) – returns the sum of two BigDecimal values.
subtract(BigDecimal) – returns the difference.
multiply(BigDecimal) – returns the product.
divide(BigDecimal) – returns the quotient (may throw an exception if the division is non‑terminating).
toString() – converts the value to a string.
doubleValue() , floatValue() , longValue() , intValue() – convert the BigDecimal to primitive types.
2. Comparing Values
In Java, BigDecimal comparison is performed with compareTo: int a = bigDecimal1.compareTo(bigDecimal2); Result interpretation: a = -1 – first number is smaller. a = 0 – numbers are equal. a = 1 – first number is larger.
Example:
boolean res = new BigDecimal(a).compareTo(new BigDecimal(b)) >= 0;Formatting BigDecimal
The NumberFormat class can format BigDecimal values for currency, percentages, or generic numbers. Example:
NumberFormat currency = NumberFormat.getCurrencyInstance();
NumberFormat percent = NumberFormat.getPercentInstance();
percent.setMaximumFractionDigits(3);
BigDecimal loanAmount = new BigDecimal("15000.48");
BigDecimal interestRate = new BigDecimal("0.008");
BigDecimal interest = loanAmount.multiply(interestRate);
System.out.println("贷款金额:\t" + currency.format(loanAmount));
System.out.println("利率:\t" + percent.format(interestRate));
System.out.println("利息:\t" + currency.format(interest));Result:
贷款金额: ¥15,000.48 利率: 0.8% 利息: ¥120.00A helper method formats numbers to two decimal places, handling edge cases such as zero or values between 0 and 1:
public static String formatToNumber(BigDecimal obj) {
DecimalFormat df = new DecimalFormat("#.00");
if (obj.compareTo(BigDecimal.ZERO) == 0) {
return "0.00";
} else if (obj.compareTo(BigDecimal.ZERO) > 0 && obj.compareTo(BigDecimal.ONE) < 0) {
return "0" + df.format(obj).toString();
} else {
return df.format(obj).toString();
}
}Common Division Exception
When divide is called with a non‑terminating decimal, BigDecimal throws:
java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal resultSolution: Specify a scale and rounding mode, e.g., divide(divisor, 2), to obtain a finite result.
Summary
Use BigDecimal only when exact decimal arithmetic is required; it is slower than double and should be avoided for simple calculations.
Prefer the String constructor or BigDecimal.valueOf(double) to avoid hidden binary errors.
Remember that BigDecimal is immutable—each operation returns a new instance.
Utility Class Recommendation
A reusable utility class ArithmeticUtils provides static methods for addition, subtraction, multiplication, division, rounding, and remainder handling with configurable scale and proper exception checks.
package com.vivo.ars.util;
import java.math.BigDecimal;
/**
* High‑precision arithmetic utilities.
*/
public class ArithmeticUtils {
private static final int DEF_DIV_SCALE = 10; // default division precision
/** Add two double values with high precision */
public static double add(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}
/** Add two string values */
public static BigDecimal add(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2);
}
/** Subtract two double values */
public static double sub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}
/** Subtract two string values with optional scale */
public static String sub(String v1, String v2, int scale) {
if (scale < 0) throw new IllegalArgumentException("The scale must be a positive integer or zero");
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/** Multiply two double values */
public static double mul(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}
/** Multiply two string values with optional scale */
public static String mul(String v1, String v2, int scale) {
if (scale < 0) throw new IllegalArgumentException("The scale must be a positive integer or zero");
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/** Divide two double values using default scale */
public static double div(double v1, double v2) {
return div(v1, v2, DEF_DIV_SCALE);
}
/** Divide two double values with custom scale */
public static double div(double v1, double v2, int scale) {
if (scale < 0) throw new IllegalArgumentException("The scale must be a positive integer or zero");
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/** Divide two string values with custom scale */
public static String div(String v1, String v2, int scale) {
if (scale < 0) throw new IllegalArgumentException("The scale must be a positive integer or zero");
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString();
}
/** Round a double to a given scale */
public static double round(double v, int scale) {
if (scale < 0) throw new IllegalArgumentException("The scale must be a positive integer or zero");
BigDecimal b = new BigDecimal(Double.toString(v));
return b.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/** Round a string value */
public static String round(String v, int scale) {
if (scale < 0) throw new IllegalArgumentException("The scale must be a positive integer or zero");
BigDecimal b = new BigDecimal(v);
return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/** Remainder of two string values */
public static String remainder(String v1, String v2, int scale) {
if (scale < 0) throw new IllegalArgumentException("The scale must be a positive integer or zero");
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/** Remainder of two BigDecimal objects */
public static BigDecimal remainder(BigDecimal v1, BigDecimal v2, int scale) {
if (scale < 0) throw new IllegalArgumentException("The scale must be a positive integer or zero");
return v1.remainder(v2).setScale(scale, BigDecimal.ROUND_HALF_UP);
}
/** Compare two string numbers – returns true if the first is greater */
public static boolean compare(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.compareTo(b2) > 0;
}
}This utility encapsulates the best practices demonstrated throughout the article, ensuring consistent handling of scale, rounding, and exception scenarios.
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 Web Project
Focused on Java backend technologies, trending internet tech, and the latest industry developments. The platform serves over 200,000 Java developers, inviting you to learn and exchange ideas together. Check the menu for Java learning resources.
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.
