databendlabs/databend · error

expected a non-NaN float

Error message

expected a non-NaN float

What it means

The NotNan wrapper (ordered_float) rejects float values that are NaN during borsh deserialization. After reading a raw float from the reader, NotNan::new is applied; NaN is not orderable, so the library refuses to construct NotNan<T> from it and maps the failure to a borsh io error with kind InvalidData. This guarantees every deserialized NotNan value is a valid, comparable float.

Solutions

  1. Fix the producer to never serialize NaN into NotNan fields (check for is_nan() before writing).
  2. Validate/sanitize the source data that produced the NaN before serialization.
  3. If NaN is legitimately possible, change the schema to Option<NotNan<T>> or serialize plain T and wrap with NotNan::new on read, handling the error explicitly.

Example fix

// before
let v: NotNan<f64> = NotNan::deserialize_reader(reader)?;

// after
let raw = f64::deserialize_reader(reader)?;
let v = NotNan::new(raw).map_err(|_| borsh::io::Error::new(
    borsh::io::ErrorKind::InvalidData,
    "expected a non-NaN float",
))?;
Defensive patterns

Strategy: validation

Validate before calling

if raw.is_nan() {
    return Err(anyhow!("value is NaN; cannot be stored as NotNan"));
}

Type guard

fn is_valid_notnan(v: f64) -> bool { !v.is_nan() }

Prevention

When it happens

Trigger: Borsh-deserializing a NotNan<T> (e.g. NotNan<f64>) whose underlying bytes encode NaN — i.e. the serialized payload contains an all-exponent-and-mantissa-bits-set float (exponent all 1s, mantissa nonzero).

Common situations: Corrupted or hand-crafted serialized payloads; writing raw f64::NAN or computed NaN (0.0/0.0) into a slot later read back as NotNan; cross-version data where the producer used plain f64 while the consumer expects NotNan.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

    }

    impl<T> borsh::BorshSerialize for NotNan<T>
    where T: borsh::BorshSerialize
    {
        #[inline]
        fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
            <T as borsh::BorshSerialize>::serialize(&self.0, writer)
        }
    }

    impl<T> borsh::BorshDeserialize for NotNan<T>
    where T: FloatCore + borsh::BorshDeserialize
    {
        #[inline]
        fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
            let float = <T as borsh::BorshDeserialize>::deserialize_reader(reader)?;
            NotNan::new(float).map_err(|_| {
                borsh::io::Error::new(
                    borsh::io::ErrorKind::InvalidData,
                    "expected a non-NaN float",
                )
            })
        }
    }

    #[test]
    fn test_ordered_float() {
        let float = OrderedFloat(1.0f64);
        let buffer = borsh::to_vec(&float).expect("failed to serialize value");
        let deser_float: OrderedFloat<f64> =
            borsh::from_slice(&buffer).expect("failed to deserialize value");
        assert_eq!(deser_float, float);
    }

    #[test]
    fn test_not_nan() {

View on GitHub (pinned to 288d84d76e)