BigDecimal represents an arbitrary-precision decimal value using an unscaled integer and a scale, with immutable arithmetic results.
Java BigDecimal: decimal amounts and explicit rounding
Start from a decimal representation
For a defined decimal amount, construct from the input text or another exact decimal source. Passing a double to the constructor captures that binary floating-point value, which may differ from the decimal text the developer intended.
The program calculates tax from a textual subtotal and rate, rounds tax once to two places, then adds it to the subtotal. These are demonstration values. A real invoice needs a defined currency, tax policy, rounding stage, and maximum amount.
Immutable means add, multiply, and setScale return new values. Calling total.add(tax) without keeping its result does not update total. This differs from a mutable balance object with an increment method.
Equality includes scale
equals distinguishes 2.0 and 2.00 because it compares both numeric value and scale. compareTo regards them as numerically equal. Pick the comparison that matches the application rule.
This affects map keys and set membership. Normalising scale at a boundary can establish one representation, but an arbitrary normalisation may destroy meaningful formatting or precision rules.
Division can require a rounding decision when the exact decimal expansion does not terminate. Use an explicit scale and RoundingMode or a MathContext suitable for the operation; silently assuming every decimal division is exact will fail.
Working program
import java.math.BigDecimal;
import java.math.RoundingMode;
public class InvoiceTaxCalculation {
public static void main(String[] args) {
BigDecimal subtotal = new BigDecimal("19.95");
BigDecimal taxRate = new BigDecimal("0.18");
BigDecimal tax = subtotal.multiply(taxRate).setScale(2, RoundingMode.HALF_UP);
BigDecimal total = subtotal.add(tax);
System.out.println("tax=" + tax.toPlainString());
System.out.println("total=" + total.toPlainString());
System.out.println(new BigDecimal("2.0").equals(new BigDecimal("2.00")));
System.out.println(new BigDecimal("2.0").compareTo(new BigDecimal("2.00")) == 0);
}
}Output
tax=3.59
total=23.54
false
trueCost and design choices
Arbitrary precision is not constant-cost arithmetic. Larger digit counts require more computation and storage; multiplication and division costs depend on operand sizes and the algorithms in use.
Limit accepted precision and scale at an input boundary. An attacker-supplied amount with an enormous digit count can create computation and memory pressure even before a business rule rejects its value.
Converting a final decimal amount back to double can reintroduce binary rounding. Keep the decimal representation through persistence and interchange when the format supports it.
Common Mistakes
- Do not construct an exact monetary input from an imprecise double by accident.
- Do not ignore a returned arithmetic value.
- Do not substitute equals for numeric comparison without considering scale.
- Do not leave rounding policy implicit.
