nautechsystems/nautilus_trader · error

Whole raw quantity must fit in Decimal

Error message

Whole raw quantity must fit in Decimal

What it means

Quantity::raw_as_decimal splits a raw fixed-point quantity into whole and fractional parts for Decimal conversion; the whole part (raw / FIXED_SCALAR_RAW) is narrowed to i128 with try_from and expect. If the whole component cannot fit in i128 (only possible when QuantityRaw is u128 in high-precision builds), this panics.

Source

Thrown at crates/model/src/types/quantity.rs:443

        // because our quantity constraints ensure the maximum raw value times the scaling
        // factor cannot exceed i128::MAX (high-precision) or i64::MAX (standard-precision).
        #[allow(
            clippy::unnecessary_cast,
            clippy::cast_lossless,
            reason = "cast is real when QuantityRaw is u64, no-op when u128"
        )]
        scaled_raw_to_decimal(rescaled_raw as i128, self.precision)
    }

    /// Returns a raw fixed-point quantity as a `Decimal`.
    #[must_use]
    #[allow(
        clippy::unnecessary_fallible_conversions,
        reason = "try_from is infallible when QuantityRaw is u64, fallible when u128"
    )]
    pub(crate) fn raw_as_decimal(raw: QuantityRaw) -> Decimal {
        let whole =
            i128::try_from(raw / FIXED_SCALAR_RAW).expect("Whole raw quantity must fit in Decimal");
        let fractional = i128::try_from(raw % FIXED_SCALAR_RAW)
            .expect("Fractional raw quantity must fit in Decimal");

        Decimal::from(whole) + Decimal::from_i128_with_scale(fractional, u32::from(FIXED_PRECISION))
    }

    /// Returns a formatted string representation of this instance.
    #[must_use]
    pub fn to_formatted_string(&self) -> String {
        format!("{self}").separate_with_underscores()
    }

    /// Creates a new [`Quantity`] from a `Decimal` value with specified precision.
    ///
    /// Uses pure integer arithmetic on the Decimal's mantissa and scale for fast conversion.
    /// The value is rounded to the specified precision using banker's rounding (round half to even).
    ///
    /// # Errors

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Keep accumulated quantities below i128 range: validate raw <= i128::MAX * FIXED_SCALAR_RAW before further accumulation.
  2. Cap quantities at domain level (max order/position sizes) so raw values stay far below the boundary.
  3. Use a wider accumulation type (big-int) for aggregation and only construct Quantity after validation.
  4. Report this as a bug if hit with ordinary values; in default u64 builds it is unreachable.

Example fix

// before
let huge = Quantity::from_raw(u128::MAX / 2, precision);
println!("{}", huge); // panics inside raw_as_decimal
// after
let qty = Quantity::from_raw(1_500_000_u64, precision); // comfortably representable
println!("{}", qty);
Defensive patterns

Strategy: validation

Validate before calling

// Rust (high-precision u128 builds)
fn whole_fits_i128(raw: u128, fixed_scalar_raw: u128) -> bool {
    raw / fixed_scalar_raw <= i128::MAX as u128
}

Prevention

When it happens

Trigger: Calling internal raw_as_decimal (used by Quantity::as_decimal and Display) with a raw u128 quantity whose whole part exceeds i128::MAX; triggered indirectly by any Display/format/as_decimal call on such a Quantity.

Common situations: High-precision builds with astronomically large quantities produced by unbounded multiplication; aggregating quantities until the raw value passes i128 range; data-import bugs writing sentinel max values.

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/e49e1b11797f3d4e. Report an issue: GitHub.