databendlabs/databend · error
Addition resulted in NaN
Error message
Addition resulted in NaN
What it means
The NotNan<T> wrapper in ordered_float.rs forbids NaN values so that Ord can be implemented soundly. The Add impl adds the wrapped float to a raw T and calls NotNan::new, which panics with this message when the result is NaN (e.g. inf + -inf). It is a fail-fast invariant check, not a recoverable library error.
Solutions
- Check operands for NaN/infinity before adding: skip or clamp non-finite values with `is_finite()`.
- Replace the `+` operator with `NotNan::new(a.0 + b).ok()` and handle the None case explicitly.
- Sanitize raw floats at the boundary with `NotNan::new(x).map_err(..)` before they ever reach NotNan arithmetic.
Example fix
// before
let total = notnan_total + raw_delta; // panics if result is NaN
// after
let total = if delta.is_finite() { notnan_total + delta } else { notnan_total }; Defensive patterns
Strategy: validation
Validate before calling
fn can_add(a: &NotNan<f64>, b: f64) -> bool {
a.is_finite() && b.is_finite() && !(a.0.is_infinite() && b == f64::NEG_INFINITY || a.0 == f64::INFINITY && b.is_infinite() && (a.0 + b).is_nan())
} Type guard
fn is_finite_f64(x: f64) -> bool { x.is_finite() } Try / catch
// Rust panics are not catchable with try/catch; use catch_unwind only at task boundaries: let r = std::panic::catch_unwind(|| notnan_a + raw_b).ok();
Prevention
- Never add raw unvalidated floats to NotNan values; construct via NotNan::new first.
- Check is_finite() on every operand entering arithmetic pipelines.
- Guard division-by-zero upstream so operands never become inf.
- Prefer fallible NotNan::new(...).ok() over operator arithmetic in risky paths.
When it happens
Trigger: Calling `+` (std::ops::Add<T> for NotNan<T>) where self.0 + other yields NaN: inf + (-inf), inf - inf via negative operand, 0.0 + NaN operand, or adding a raw NaN value to a NotNan number.
Common situations: Accumulating float results from numeric pipelines where infinities were produced earlier (overflowing division by zero), or adding an unchecked/unvalidated user-supplied float that is NaN to a NotNan value in aggregation or scoring code.
Related errors
- partial_cmp failed for non-NaN value
- 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/254bb542844e65cb.
Report an issue: GitHub.
Appendix: source
Thrown at src/common/base/src/base/ordered_float.rs:1320
impl<T: FloatCore + PartialEq> Eq for NotNan<T> {}
impl<T: FloatCore> PartialEq<T> for NotNan<T> {
#[inline]
fn eq(&self, other: &T) -> bool {
self.0 == *other
}
}
/// Adds a float directly.
///
/// Panics if the provided value is NaN or the computation results in NaN
impl<T: FloatCore> Add<T> for NotNan<T> {
type Output = Self;
#[inline]
fn add(self, other: T) -> Self {
NotNan::new(self.0 + other).expect("Addition resulted in NaN")
}
}
/// Adds a float directly.
///
/// Panics if the provided value is NaN.
impl<T: FloatCore + Sum> Sum for NotNan<T> {
fn sum<I: Iterator<Item = NotNan<T>>>(iter: I) -> Self {
NotNan::new(iter.map(|v| v.0).sum()).expect("Sum resulted in NaN")
}
}
impl<'a, T: FloatCore + Sum + 'a> Sum<&'a NotNan<T>> for NotNan<T> {
#[inline]
fn sum<I: Iterator<Item = &'a NotNan<T>>>(iter: I) -> Self {
iter.cloned().sum()
}
}View on GitHub (pinned to 288d84d76e)