nautechsystems/nautilus_trader · error

Decimal exponent {exponent} exceeds U256 range

Error message

Decimal exponent {exponent} exceeds U256 range

What it means

`pow10` computes 10^exponent as a U256 after validating the exponent against DECIMAL_EXPONENT_MAX. The library throws this error when `checked_pow` overflows U256 — a defensive backstop for exponents that pass the max check but still cannot fit 10^exponent into 256 bits.

Source

Thrown at crates/model/src/defi/tick_map/full_math.rs:153

            remainder = remainder.mul_mod(scale, denominator);
        }

        Ok(quotient)
    }

    pub(crate) fn check_decimal_exponent(exponent: u8) -> anyhow::Result<()> {
        anyhow::ensure!(
            exponent <= DECIMAL_EXPONENT_MAX,
            "Decimal exponent {exponent} exceeds supported maximum {DECIMAL_EXPONENT_MAX}"
        );
        Ok(())
    }

    pub(crate) fn pow10(exponent: u8) -> anyhow::Result<U256> {
        Self::check_decimal_exponent(exponent)?;
        U256::from(10)
            .checked_pow(U256::from(exponent))
            .ok_or_else(|| anyhow::anyhow!("Decimal exponent {exponent} exceeds U256 range"))
    }

    /// Calculates ceil(a×b÷denominator) with full precision
    /// Returns `Ok` with the rounded result or an error when rounding cannot be performed safely.
    ///
    /// # Errors
    ///
    /// Returns error if `denominator` is zero or the rounded result would overflow `U256`.
    pub fn mul_div_rounding_up(a: U256, b: U256, denominator: U256) -> anyhow::Result<U256> {
        let result = Self::mul_div(a, b, denominator)?;

        // Check if there's a remainder
        if a.mul_mod(b, denominator).is_zero() {
            Ok(result)
        } else if result == U256::MAX {
            anyhow::bail!("Result would overflow 256 bits")
        } else {
            Ok(result + U256::from(1))

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Validate the exponent before calling: `ensure!(exponent <= DECIMAL_EXPONENT_MAX)` and that DECIMAL_EXPONENT_MAX < 79.
  2. Fix the source of the bad exponent (token metadata decoding, config parsing).
  3. Use U512 for intermediate powers if larger exponents are genuinely required.

Example fix

// before
let pow = pow10(raw_exponent)?;
// after
ensure!(raw_exponent <= DECIMAL_EXPONENT_MAX, "bad exponent {raw_exponent}");
let pow = pow10(raw_exponent)?;
Defensive patterns

Strategy: validation

Validate before calling

fn safe_pow10(exponent: u8) -> Option<U256> {
    (exponent < 79).then(|| U256::from(10).checked_pow(U256::from(exponent))).flatten()
}

Type guard

fn exponent_fits_u256(e: u8) -> bool { e < 79 }

Try / catch

match pow10(exponent) {
    Ok(v) => v,
    Err(e) if e.to_string().contains("U256 range") => {
        log::error!("exponent {exponent} overflows U256 — check metadata source");
        return Err(e);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `pow10` with an exponent large enough that 10^exponent >= 2^256 (exponent >= ~78) — either above DECIMAL_EXPONENT_MAX if that constant allows it, or via a path that bypassed `check_decimal_exponent`.

Common situations: Corrupt decimals metadata (e.g. u8::MAX from a failed decode), or code paths passing raw exponents from external data without prior validation.

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