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

Java BigDecimal equality: value and scale are separate contracts

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

BigDecimal.equals compares both numeric value and scale, while compareTo compares numeric value without requiring matching scale.

Choose one identity policy

A charge of 47.0 and a charge of 47.00 compare as the same amount, but equals says they are different objects by value representation. HashSet follows equals and stores both; TreeSet uses natural ordering and keeps one. If the same business key passes through both structures, two counts can disagree even though neither implementation is broken.

For a fixed two-decimal currency boundary, normalize exact input to scale two before using the amount as a key. setScale(2, UNNECESSARY) rejects an input that would need rounding. For tax or exchange-rate calculations, choose and document a separate rounding rule; quietly rounding at an identity boundary can merge values the business treats as distinct.

Keep representation visible

stripTrailingZeros changes representation and can produce a negative scale for values with trailing powers of ten. It is useful when numeric equivalence is the domain rule, but it is not a blanket money formatter. Decimal arithmetic covers construction and division; sorted-set collisions explains why comparator equality controls membership.

The program starts from strings. Constructing BigDecimal from a binary floating-point double would import that double's approximation before the scale policy has a chance to act.

Working program

Java
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

public class ChargeIdentityPolicy {
    public static void main(String[] args) {
        BigDecimal firstCharge = new BigDecimal("47.0");
        BigDecimal secondCharge = new BigDecimal("47.00");
        Set<BigDecimal> exactRepresentations = new HashSet<>();
        Set<BigDecimal> numericOrder = new TreeSet<>();
        exactRepresentations.add(firstCharge);
        exactRepresentations.add(secondCharge);
        numericOrder.add(firstCharge);
        numericOrder.add(secondCharge);
        Set<BigDecimal> fixedCurrency = new HashSet<>();
        fixedCurrency.add(firstCharge.setScale(2, RoundingMode.UNNECESSARY));
        fixedCurrency.add(secondCharge.setScale(2, RoundingMode.UNNECESSARY));
        System.out.println(exactRepresentations.size() + "/" + numericOrder.size());
        System.out.println(fixedCurrency.size());
    }
}

Output

Output
2/1
1

Cost and ownership

Hash-based membership is expected O(1) per operation; ordered-tree membership is O(log n). Normalization allocates a new decimal representation when needed. The fixed-scale rule belongs at the input boundary so later collections do not guess what equality means.

Common Mistakes

  • Do not assume compareTo returning zero implies equals returns true.
  • Do not feed binary double literals into exact decimal identity rules.
  • Do not use stripTrailingZeros as an unexplained currency display policy.

Read next

Java BigDecimal: decimal amounts and explicit rounding, Java TreeSet comparator collisions: compare zero means one slot, Java HashSet: deduplication and equality, Java comparators: tie-breakers, overflow and sorted-key identity.

More numeric representation boundaries

Continue with Java RoundingMode.UNNECESSARY: reject hidden fractional cents.

java
bigdecimal-scale-equality
Storage details