nautechsystems/nautilus_trader · error

Decimal overflow adding {lhs} and {rhs}

Error message

Decimal overflow adding {lhs} and {rhs}

What it means

`checked_add` is the module's safe wrapper over `rust_decimal::Decimal::checked_add`. When adding two Decimal values overflows the fixed-precision Decimal range, the inner operation returns None and this error is raised with both operands in the message.

Source

Thrown at crates/model/src/data/bet.rs:766

}

fn check_odds_gt_one(price: Decimal) -> anyhow::Result<()> {
    if price <= Decimal::ONE {
        anyhow::bail!("Price must be greater than 1.0 for lay liability calculation, was {price}");
    }
    Ok(())
}

fn check_nonzero_denominator(value: Decimal, name: &str) -> anyhow::Result<()> {
    if value.is_zero() {
        anyhow::bail!("invalid {name}: must be non-zero")
    }
    Ok(())
}

fn checked_add(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {
    lhs.checked_add(rhs)
        .ok_or_else(|| anyhow::anyhow!("Decimal overflow adding {lhs} and {rhs}"))
}

fn checked_sub(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {
    lhs.checked_sub(rhs)
        .ok_or_else(|| anyhow::anyhow!("Decimal overflow subtracting {rhs} from {lhs}"))
}

fn checked_mul(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {
    lhs.checked_mul(rhs)
        .ok_or_else(|| anyhow::anyhow!("Decimal overflow multiplying {lhs} by {rhs}"))
}

fn checked_div(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {
    check_nonzero_denominator(rhs, "divisor")?;
    lhs.checked_div(rhs)
        .ok_or_else(|| anyhow::anyhow!("Decimal overflow dividing {lhs} by {rhs}"))
}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Audit the operands in the failing call: log both values from the message and find which input is unexpectedly large.
  2. Cap or normalize exposure/PnL magnitudes before accumulation (e.g. convert to a smaller scale or use f64 for analytics).
  3. Fix the upstream value source (config, feed, or trade data) that produced the oversized operand.
  4. If legitimate huge sums are needed, switch those accumulations to a wider numeric type or arbitrary-precision decimal.
Defensive patterns

Strategy: validation

Validate before calling

fn within_decimal_range(v: Decimal) -> bool {
    v.abs() < Decimal::from_i128_with_exp(7_900_000, 23) // ~7.9e28, Decimal max
}

Try / catch

let pnl = calc_bets_pnl_checked(&bets)
    .map_err(|e| { log::error!("PnL overflow: {e}"); e })?;

Prevention

When it happens

Trigger: Any of the callers (`increased_state`, `decreased_state`, `realized_after_close`, `total_pnl_checked`, `calc_bets_pnl_checked`) summing exposures/PnL values whose magnitudes exceed Decimal's maximum (~7.9e28 with rust_decimal's default 96-bit representation).

Common situations: Accumulating PnL over very many bets without scaling; a runaway loop repeatedly adding a large exposure; unit/currency confusion producing values orders of magnitude too large; seeding a position with an already-huge value from a bad config.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/a647036f295bcd6f. Report an issue: GitHub.