Skip to content

float(), double(), float32Array(), and float64Array() never produce non-canonical NaNsΒ #6532

@TomerAberbach

Description

@TomerAberbach

πŸ› Bug Report

ECMAScript declares that all NaN values are indistinguishable from each other, but that the underlying bit pattern, which may be non-canonical, may be observable by writing to an ArrayBuffer depending on the engine.

https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-ecmascript-language-types-number-type

I first noticed that float32Array() and float64Array() actually never seem to be able to produce non-canonical NaN bit patterns because they are derived from lists of floats/doubles and those are also always generated as canonical.

I think it would be better if at least float32Array() and float64Array() could produce any sequence of underlying bytes (e.g. maybe by being derived from an arbitrary ArrayBuffer). This would help with testing things that are supposed to roundtrip data perfectly.

I'm less opinionated about float() and double() producing non-canonical NaNs as well, because those differences might be really hard to observe in practice (only when writing to an ArrayBuffer before/without doing operations that happen to canonicalize the NaN), but it might still be worth it for catching those edge cases.

To Reproduce

Steps to reproduce:

const canonicalNaNBytes = new Uint8Array(new Float32Array([Number.NaN]).buffer)

fc.sample(
  fc
    .float32Array()
    // Filter for Float32Arrays with non-canonical NaNs.
    .filter(array => {
      const nanIndex = array.findIndex(value => Number.isNaN(value))
      if (nanIndex === -1) {
        return false
      }

      const nanBytes = new Uint8Array(
        array.slice(nanIndex, nanIndex + 1).buffer,
      )
      for (let i = 0; i < nanBytes.length; i++) {
        if (nanBytes[i] !== canonicalNaNBytes[i]) {
          // Found a non-canonical NaN!
          return true
        }
      }

      return false
    }),
  10,
)

Expected behavior

The sampling should terminate by finding something, but it never finds a matching value.

Your environment

Packages / Softwares Version(s)
fast-check 4.5.3
node v24.8.0
TypeScript* 5.9.3

*Only for TypeScript's users

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions