Fluent Commerce Logo
Docs

BigDecimal Precision & Rounding Principles

Essential knowledge

Intended Audience:

Technical User

Authors:

Alexey Kaminskiy, Yulia Andreyanova

Changed on:

18 Feb 2026

Overview

This document defines the core principles for using `BigDecimal` in monetary and high-precision calculations. After reading, developers can apply the appropriate constructors, division rules, and rounding strategies to ensure consistent numeric representation across services, APIs, and storage. These principles reduce defects caused by floating-point arithmetic, undefined division, rounding errors, and incorrect value comparisons.

Key points

  • Monetary calculations rely on deterministic precision across all services.
  • Precision is preserved using `NUMERIC` (DB), `BigDecimal` (Java), and (`unscaledValue`, `scale`) (API).
  • Division requires explicit scale and rounding to avoid runtime exceptions.
  • Most defects originate from constructing from `double`, confusing scale vs precision, or misusing `equals()`.
These principles apply to all monetary and high-precision calculations, including cryptocurrency values (e.g., Ethereum with 18 decimal places). Their purpose is to ensure deterministic financial behavior, consistent numeric representation across system layers, lossless data transfer between DB, API, and business logic, and explicit, predictable rounding decisions.`BigDecimal` is immutable and represents decimal values as an unscaled integer combined with a scale. This model makes it suitable for financial and high-precision domains, but it also requires intentional management of scale and rounding to maintain consistency across services.In practice, most production issues originate from a small number of recurring patterns: constructing `BigDecimal` from `double` (binary artifacts), dividing without specifying rounding (runtime exceptions), confusing scale (decimal places) with precision (significant digits), and using `equals()` where `compareTo()` is required.The following sections describe the approved patterns and examples for working with `BigDecimal` to prevent inconsistent numeric representations across modules and integrations.

Precise Monetary Calculations Using `BigDecimal`

Floating-point types (`double``float`) cannot precisely represent most decimal fractions due to binary encoding. They introduce hidden rounding errors and are not suitable for monetary values.For financial computations, the system consistently uses:
  • `NUMERIC` in the database
  • `BigDecimal` in Java
  • `(unscaledValue, scale)` representation in GraphQL
This model preserves exact arithmetic and prevents silent precision loss.
Example: Cryptocurrency Calculation (18 Decimals)
Order Creation
1{
2  "ref": "order-ref-ethereum",
3  ...
4  "preciseTotalPrice": {
5    "unscaledValue": 1234567,
6    "scale": 18
7  }
8}
1BigDecimal preciseTotalPrice =
2    new BigDecimal(new BigInteger("1234567"), 18);
Result:`preciseAmount = 0.000000000001234567`
Workflow Example (Capture & Shipping)
1//0.000000000001234567
2BigDecimal preciseTotalPrice = 
3    order.getPreciseTotalPrice().getPreciseAmount()
4
5//0.000000000000000010
6BigDecimal deliveryPrice = 
7    order.getFulfilmentChoice().getPreciseFulfilmentPrice().getPreciseAmount()
8  
9BigDecimal captureAmount = preciseTotalPrice;
10
11//percentageToCapture = captureAmount / preciseTotalPrice
12//percentageToCapture = 0.000000000001234567 / 0.000000000001234567 = 1.000000000000000000
13BigDecimal percentageToCapture =
14    captureAmount.divide(preciseTotalPrice, 18, RoundingMode.HALF_UP);
15
16//shippingFee = deliveryPrice * percentageToCapture
17//shippingFee = 0.000000000000000010 * 1.000000000000000000 = 0.000000000000000010
18BigDecimal shippingFee =
19    deliveryPrice.multiply(percentageToCapture);
20
21//paymentTotal = captureAmount + shippingFee
22//paymentTotal = 0.000000000001234567 + 0.000000000000000010 = 0.000000000001234577
23BigDecimal paymentTotal =
24    captureAmount.add(shippingFee)
25                 .setScale(18, RoundingMode.HALF_UP);
1preciseTotalPrice   = 0.000000000001234567
2percentageToCapture = 1.000000000000000000
3shippingFee         = 0.000000000000000010
4paymentTotal        = 0.000000000001234577
 
Division Without Rounding
Unlike floating-point types, `BigDecimal` does not silently truncate repeating decimals.
1// Throws ArithmeticException (non-terminating decimal expansion)
2new BigDecimal("1").divide(new BigDecimal("3"));
3
4// Correct: explicit scale + rounding mode
5new BigDecimal("1").divide(new BigDecimal("3"), 18, RoundingMode.HALF_UP);
This is intentional behavior: `BigDecimal` never performs implicit rounding, ensuring all financial rounding decisions remain explicit and consistent across services.`
`
Constructor and Comparison Rules
1// ncorrect: constructing from double introduces precision artifacts
2BigDecimal incorrect = new BigDecimal(0.1);
3
4// ncorrect: equals() compares value AND scale
5new BigDecimal("1.0").equals(new BigDecimal("1.00")); // false
6
7// Correct: use String constructor for exact decimal value
8BigDecimal correct = new BigDecimal("0.1");
9
10// Correct: compareTo() compares numeric value only
11new BigDecimal("1.0").compareTo(new BigDecimal("1.00")) == 0; // true
12

Rounding Strategies Overview

Different rounding modes support different business objectives. The chosen strategy affects:
  • customer-visible totals
  • platform fees
  • tax calculations
  • long-term statistical bias
Rounding decisions should align with the underlying business rule.
Rounding ModeRuleExample (1.235 → scale 2)Typical Use CaseAdvantagesRisk / Trade-off
HALF_UPRound to nearest; .5 rounds up1.24Retail pricing, payment totals, user-visible amountsIntuitive, widely accepted, legally commonSmall upward bias over many operations
HALF_EVENRound to nearest; .5 rounds to nearest even digit1.24 (1.245 → 1.24)Banking systems, interest calculations, large-scale aggregationMinimizes statistical rounding biasLess intuitive for business users
DOWNAlways truncate extra digits (toward zero)1.23Fee calculation before remainder distribution, crypto minimal unitsNever inflates value, deterministic, safe for splitsSystematic negative bias (always reduces value)
UPAlways round away from zero1.24Regulatory minimums, guaranteed feesEnsures minimum thresholdsSystematic positive bias
CEILINGRound toward positive infinity1.24Tax calculations favoring authorityPredictable for positive-only domainsBiased upward
FLOORRound toward negative infinity1.23Conservative accounting rulesPredictable downward biasBiased downward
Alexey Kaminskiy

Alexey Kaminskiy

Contributors:
Yulia Andreyanova