nautechsystems/nautilus_trader · error
Underflow occurred when subtracting `Money`
Error message
Underflow occurred when subtracting `Money`
What it means
The Sub impl for Money subtracts the raw fixed-point values with checked_sub and panics with this message on underflow (result below MoneyRaw minimum). Both operands must share currency precision. Overflow/underflow of the raw representation is treated as an unrecoverable invariant violation.
Source
Thrown at crates/model/src/types/money.rs:621
}
impl Sub for Money {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
assert_eq!(
self.currency, rhs.currency,
"Currency mismatch: cannot subtract {} from {}",
rhs.currency.code, self.currency.code
);
assert!(
raw_scales_match(self.currency.precision, rhs.currency.precision),
"Cannot subtract `Money` values with mismatched decimal scales"
);
Self {
raw: self
.raw
.checked_sub(rhs.raw)
.expect("Underflow occurred when subtracting `Money`"),
currency: self.currency,
}
}
}
impl Add<Decimal> for Money {
type Output = Decimal;
fn add(self, rhs: Decimal) -> Self::Output {
self.as_decimal() + rhs
}
}
impl Sub<Decimal> for Money {
type Output = Decimal;
fn sub(self, rhs: Decimal) -> Self::Output {
self.as_decimal() - rhs
}
}View on GitHub (pinned to 18893faf8b)
Solutions
- Track the running value in a wider type (i128/Decimal) and clamp or validate before each subtraction.
- Check against MONEY range bounds before subtracting.
- Compile with high-precision so MoneyRaw is i128.
Example fix
// before let net = balance - withdrawal; // panics on underflow // after let net_dec = balance.as_decimal() - withdrawal.as_decimal(); let net = Money::new(net_dec, USD); // range validated in constructor
Defensive patterns
Strategy: try-catch
Validate before calling
if a.raw.checked_sub(b.raw).is_none() {
return Err(underflow_err());
}
let diff = a - b; Type guard
fn sub_will_underflow(a: Money, b: Money) -> bool {
a.raw.checked_sub(b.raw).is_none()
} Try / catch
// Money subtraction panics; pre-check before using the operator
if a.raw.checked_sub(b.raw).is_none() {
// handle underflow (floor at min, alert, escalate)
} else {
let diff = a - b;
} Prevention
- Compute net positions in a wider signed type and clamp before converting back to Money.
- Watch for repeated negative accumulations (fees, adjustments) drifting toward the minimum.
- Add assertions on expected balance ranges in reconciliation code.
When it happens
Trigger: money_a - money_b where the raw result is less than MONEY_RAW_MIN, e.g. subtracting a large positive balance from a near-minimum (deeply negative) amount.
Common situations: Computing net exposure or PnL where signed totals swing beyond the raw range;ledger reconciliation code subtracting accumulated balances; excessive negative accumulations from repeated fee deductions.
Related errors
- Overflow occurred when adding `Money`
- WETH balance overflow for included transaction {tx_hash} at
- Negative timestamp not allowed
- initial margin calculation overflow
- maintenance margin calculation overflow
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/8f7300d592aad4b0.
Report an issue: GitHub.