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
- Sanitize/validate floats before wrapping: reject NaN with NotNan::try_from or check value.is_nan()
- Use the checked API (NotNan::new returns Result) instead of unchecked/raw constructors
- 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
- Always construct NotNan via NotNav::new/try_from (fallible), never new_raw/unchecked
- Sanitize external float data (NaN/Inf) at ingestion boundaries
- Avoid raw float arithmetic that can yield NaN (0.0/0.0, inf-inf) before wrapping; prefer checked NotNan operators
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
- Addition resulted in NaN
- Sum resulted in NaN
- Subtraction resulted in NaN
- Multiplication resulted in NaN
- Product resulted in NaN
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)