databendlabs/databend · error

Rem resulted in NaN

Error message

Rem resulted in NaN

What it means

The Rem impl for NotNan<T> computes self.0 % other and validates with NotNan::new, panicking with this message if the remainder is NaN. IEEE remainder is NaN for x % 0, inf % x, and NaN operands. This is the wrapper's fail-fast check keeping NaN out of ordered floats.

Solutions

  1. Validate before the op: `divisor.is_finite() && divisor != 0.0 && dividend.is_finite()`.
  2. Fall back to `NotNan::new(a.0 % b).ok()` and handle None (e.g. skip the value or use a default bucket).
  3. Ensure bucket-size config cannot be 0 and clamp upstream values so they never reach inf.

Example fix

// before
let bucket = notnan_value % bucket_size; // panics if bucket_size == 0
// after
let bucket = if bucket_size.is_finite() && bucket_size != 0.0 { notnan_value % bucket_size } else { notnan_value };
Defensive patterns

Strategy: validation

Validate before calling

fn can_rem(a: &NotNan<f64>, b: f64) -> bool {
    b != 0.0 && b.is_finite() && a.0.is_finite()
}

Type guard

fn is_valid_modulus(x: f64) -> bool { x != 0.0 && x.is_finite() }

Try / catch

let bucket = std::panic::catch_unwind(|| notnan_value % bucket_size).ok();

Prevention

When it happens

Trigger: Using `%` (std::ops::Rem<T> for NotNan<T>) with a divisor of 0.0, a dividend of ±inf, or a NaN `other` operand.

Common situations: Bucketing/binning values with a modulo computed from config that resolved to 0, or taking remainders of values that overflowed to infinity earlier in the pipeline.

Related errors


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

Appendix: source

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

/// Panics if the provided value is NaN or the computation results in NaN
impl<T: FloatCore> Div<T> for NotNan<T> {
    type Output = Self;

    #[inline]
    fn div(self, other: T) -> Self {
        NotNan::new(self.0 / other).expect("Division resulted in NaN")
    }
}

/// Calculates `%` with a float directly.
///
/// Panics if the provided value is NaN or the computation results in NaN
impl<T: FloatCore> Rem<T> for NotNan<T> {
    type Output = Self;

    #[inline]
    fn rem(self, other: T) -> Self {
        NotNan::new(self.0 % other).expect("Rem resulted in NaN")
    }
}

macro_rules! impl_not_nan_binop {
    ($imp:ident, $method:ident, $assign_imp:ident, $assign_method:ident) => {
        impl<T: FloatCore> $imp for NotNan<T> {
            type Output = Self;

            #[inline]
            fn $method(self, other: Self) -> Self {
                self.$method(other.0)
            }
        }

        impl<T: FloatCore> $imp<&T> for NotNan<T> {
            type Output = NotNan<T>;

            #[inline]

View on GitHub (pinned to 288d84d76e)