databendlabs/databend · error

partial_cmp failed for non-NaN value

Error message

partial_cmp failed for non-NaN value

What it means

NotNan<T>'s Ord::cmp calls partial_cmp().expect("partial_cmp failed for non-NaN value") — this panics if the wrapped float is NaN, since NaN breaks total ordering. The type is designed to guarantee non-NaN values; the panic is an internal invariant check that fires when a NaN slipped past construction guards (e.g. via unsafe or an unchecked conversion).

Solutions

  1. Sanitize/validate floats before wrapping: reject NaN with NotNan::try_from or check value.is_nan()
  2. Use the checked API (NotNan::new returns Result) instead of unchecked/raw constructors
  3. Fix upstream arithmetic that produces NaN (guard division by zero, infinity subtraction)

Example fix

// before
let v = NotNan::new_raw(raw);
// after
let v = NotNan::new(raw).map_err(|_| ErrorCode::BadDataBytes("NaN in ordered float"))?;
Defensive patterns

Strategy: validation

Validate before calling

fn not_nan(x: f64) -> Option<NotNan<f64>> {
    if x.is_nan() { None } else { NotNan::new(x).ok() }
}

Type guard

fn is_finite_ordered(x: f64) -> bool { !x.is_nan() }

Try / catch

let v = NotNan::new(raw).map_err(|_|
    ErrorCode::BadDataBytes(format!("NaN not allowed in ordered float: {}", raw)))?;

Prevention

When it happens

Trigger: Constructing NotNan::from/unchecked with a NaN value, or an arithmetic op producing NaN without the checked-API error path, then any comparison/sort/BTreeMap operation triggering cmp().

Common situations: 0.0/0.0 or inf-inf computed elsewhere and injected via NotNan::new_raw/unchecked; float data from external sources containing NaN not sanitized before ordering (sort, BTreeMap keys, heap ops).

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of databendlabs/databend@288d84d76e (2026-09-11). Data as JSON: /api/errors/b36a4967b03c306e. Report an issue: GitHub.

Appendix: source

Thrown at src/common/base/src/base/ordered_float.rs:1192

    fn borrow(&self) -> &f32 {
        &self.0
    }
}

impl Borrow<f64> for NotNan<f64> {
    #[inline]
    fn borrow(&self) -> &f64 {
        &self.0
    }
}

#[allow(clippy::derive_ord_xor_partial_ord)]
impl<T: FloatCore> Ord for NotNan<T> {
    fn cmp(&self, other: &NotNan<T>) -> Ordering {
        // Can't use unreachable_unchecked because unsafe code can't depend on FloatCore impl.
        // https://github.com/reem/rust-ordered-float/issues/150
        self.partial_cmp(other)
            .expect("partial_cmp failed for non-NaN value")
    }
}

impl<T: FloatCore> Hash for NotNan<T> {
    #[inline]
    fn hash<H: Hasher>(&self, state: &mut H) {
        let bits = raw_double_bits(&canonicalize_signed_zero(self.0));
        bits.hash(state)
    }
}

impl<T: fmt::Debug> fmt::Debug for NotNan<T> {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}

View on GitHub (pinned to 288d84d76e)