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

  1. Check operands for NaN/infinity before adding: skip or clamp non-finite values with `is_finite()`.
  2. Replace the `+` operator with `NotNan::new(a.0 + b).ok()` and handle the None case explicitly.
  3. 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

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


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)