{"record":{"id":"b36a4967b03c306e","repo":"databendlabs/databend","slug":"partial-cmp-failed-for-non-nan-value","errorCode":null,"errorMessage":"partial_cmp failed for non-NaN value","messagePattern":"partial_cmp failed for non-NaN value","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/common/base/src/base/ordered_float.rs","lineNumber":1192,"sourceCode":"    fn borrow(&self) -> &f32 {\n        &self.0\n    }\n}\n\nimpl Borrow<f64> for NotNan<f64> {\n    #[inline]\n    fn borrow(&self) -> &f64 {\n        &self.0\n    }\n}\n\n#[allow(clippy::derive_ord_xor_partial_ord)]\nimpl<T: FloatCore> Ord for NotNan<T> {\n    fn cmp(&self, other: &NotNan<T>) -> Ordering {\n        // Can't use unreachable_unchecked because unsafe code can't depend on FloatCore impl.\n        // https://github.com/reem/rust-ordered-float/issues/150\n        self.partial_cmp(other)\n            .expect(\"partial_cmp failed for non-NaN value\")\n    }\n}\n\nimpl<T: FloatCore> Hash for NotNan<T> {\n    #[inline]\n    fn hash<H: Hasher>(&self, state: &mut H) {\n        let bits = raw_double_bits(&canonicalize_signed_zero(self.0));\n        bits.hash(state)\n    }\n}\n\nimpl<T: fmt::Debug> fmt::Debug for NotNan<T> {\n    #[inline]\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        self.0.fmt(f)\n    }\n}\n","sourceCodeStart":1174,"sourceCodeEnd":1210,"githubUrl":"https://github.com/databendlabs/databend/blob/288d84d76e20a2f8f7173bda9691eb6ece301aa9/src/common/base/src/base/ordered_float.rs#L1174-L1210","documentation":"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).","triggerScenarios":"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().","commonSituations":"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).","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)"],"exampleFix":"// before\nlet v = NotNan::new_raw(raw);\n// after\nlet v = NotNan::new(raw).map_err(|_| ErrorCode::BadDataBytes(\"NaN in ordered float\"))?;","handlingStrategy":"validation","validationCode":"fn not_nan(x: f64) -> Option<NotNan<f64>> {\n    if x.is_nan() { None } else { NotNan::new(x).ok() }\n}","typeGuard":"fn is_finite_ordered(x: f64) -> bool { !x.is_nan() }","tryCatchPattern":"let v = NotNan::new(raw).map_err(|_|\n    ErrorCode::BadDataBytes(format!(\"NaN not allowed in ordered float: {}\", raw)))?;","preventionTips":["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"],"tags":["panic","float","nan","rust","invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"288d84d76e20a2f8f7173bda9691eb6ece301aa9","analyzedAt":"2026-09-11T11:29:36.208Z","contentChangedAt":"2026-09-11T11:29:36.208Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}