Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java BigDecimal: decimal amounts and explicit rounding

Last updated: 5 Oct 20263 min read
tutorial
IntermediateBy AITrove Editorial

BigDecimal represents an arbitrary-precision decimal value using an unscaled integer and a scale, with immutable arithmetic results.

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.

Scale is part of equality

BigDecimal values can compare numerically equal while equals returns false because their scales differ. A sorted collection whose comparator uses compareTo can therefore group values that a hash-based collection keeps distinct. Choose the equality contract before using decimal values as keys; stripping trailing zeros also changes scale and is not a universal storage policy.

A string constructor preserves the decimal input supplied by the caller. Constructing from a double preserves that double value, including its binary approximation; valueOf uses a different conversion route. Division with a non-terminating decimal expansion needs a declared precision or scale and rounding rule. Do not let a display formatting choice silently become the calculation rule.

Working program

Java
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

Output
tax=3.59
total=23.54
false
true

Cost 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.

Connect the contracts

Compare the boundary explained in Equality contracts with the assumptions made by this program.

Extend this boundary

Continue with Java DecimalFormat: reject trailing input and pin the locale.

Compare the Python boundary

Python Decimal money: parse decimal text and choose rounding explicitly.

Related contract checks

Continue with Java BigDecimal equality: value and scale are separate contracts.

More numeric representation boundaries

Continue with Java BigDecimal.divide: make nonterminating quotients explicit, Java RoundingMode.UNNECESSARY: reject hidden fractional cents, Java floating-point sums: input order can change the result.

java
bigdecimal
Storage details