{"record":{"id":"a647036f295bcd6f","repo":"nautechsystems/nautilus_trader","slug":"decimal-overflow-adding-lhs-and-rhs","errorCode":null,"errorMessage":"Decimal overflow adding {lhs} and {rhs}","messagePattern":"Decimal overflow adding (.+?) and (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/model/src/data/bet.rs","lineNumber":766,"sourceCode":"}\n\nfn check_odds_gt_one(price: Decimal) -> anyhow::Result<()> {\n    if price <= Decimal::ONE {\n        anyhow::bail!(\"Price must be greater than 1.0 for lay liability calculation, was {price}\");\n    }\n    Ok(())\n}\n\nfn check_nonzero_denominator(value: Decimal, name: &str) -> anyhow::Result<()> {\n    if value.is_zero() {\n        anyhow::bail!(\"invalid {name}: must be non-zero\")\n    }\n    Ok(())\n}\n\nfn checked_add(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {\n    lhs.checked_add(rhs)\n        .ok_or_else(|| anyhow::anyhow!(\"Decimal overflow adding {lhs} and {rhs}\"))\n}\n\nfn checked_sub(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {\n    lhs.checked_sub(rhs)\n        .ok_or_else(|| anyhow::anyhow!(\"Decimal overflow subtracting {rhs} from {lhs}\"))\n}\n\nfn checked_mul(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {\n    lhs.checked_mul(rhs)\n        .ok_or_else(|| anyhow::anyhow!(\"Decimal overflow multiplying {lhs} by {rhs}\"))\n}\n\nfn checked_div(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {\n    check_nonzero_denominator(rhs, \"divisor\")?;\n    lhs.checked_div(rhs)\n        .ok_or_else(|| anyhow::anyhow!(\"Decimal overflow dividing {lhs} by {rhs}\"))\n}\n","sourceCodeStart":748,"sourceCodeEnd":784,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/data/bet.rs#L748-L784","documentation":"`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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Audit the operands in the failing call: log both values from the message and find which input is unexpectedly large.","Cap or normalize exposure/PnL magnitudes before accumulation (e.g. convert to a smaller scale or use f64 for analytics).","Fix the upstream value source (config, feed, or trade data) that produced the oversized operand.","If legitimate huge sums are needed, switch those accumulations to a wider numeric type or arbitrary-precision decimal."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"fn within_decimal_range(v: Decimal) -> bool {\n    v.abs() < Decimal::from_i128_with_exp(7_900_000, 23) // ~7.9e28, Decimal max\n}","typeGuard":null,"tryCatchPattern":"let pnl = calc_bets_pnl_checked(&bets)\n    .map_err(|e| { log::error!(\"PnL overflow: {e}\"); e })?;","preventionTips":["Clamp or validate exposure/PnL magnitudes at ingestion boundaries.","Keep currency units consistent (avoid mixing scaled units).","Log accumulators periodically to spot runaway growth before overflow."],"tags":["rust","decimal","overflow","arithmetic"],"backgroundTag":"value-out-of-range","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}