Skip to content

Compare every simd-backed function against Apple's simd and fix the differences found - #77

Merged
ctreffs merged 2 commits into
masterfrom
test/simd-differential-all
Oct 17, 2026
Merged

ctreffs merged 2 commits into
masterfrom
test/simd-differential-all

Conversation

@ctreffs

@ctreffs ctreffs commented Oct 17, 2026

Copy link
Copy Markdown
Member

Follow-up to #76. FirebladeMath promises the same results on every platform. This PR checks that systematically and fixes the five places where it wasn't true.

On Apple platforms, Apple's simd library is importable even with the simd trait off. The differential tests run FirebladeMath's portable implementations, which are what Linux and Windows use, and compare each result against simd, which is what FirebladeMath calls when the trait is on.

Coverage: all 108 simd-backed functions

Area Functions
Matrices multiply (matrix·matrix, scalar·matrix, vector·matrix, matrix·vector for 2×2/3×3/4×4), determinant, inverse, transpose
Quaternions multiply (both scalar sides and quaternion), add, subtract, conjugate, inverse, length, normalize, angle, axis, act
Vectors dot, length, normalize (2D/3D/4D), cross (2D/3D)
Scalars clamp, fract, mix, rsqrt, sign, smoothstep, step
Conversions matrix3x3/4x4(from:), quaternion(matrix:), quaternion(angle:axis:), quaternion(from:to:)

All of these are covered in Float and Double.

Inputs: 256 pseudo-random values per test, plus edge cases:

  • identity, diagonal, permutation and singular matrices;
  • small and large scales;
  • zero vectors and zero quaternions, unit axes;
  • 180° rotations and opposite vectors;
  • NaN, ±∞ and ±0.

Tolerances are rounding-level (Float 2e−6, Double 1e−14; inverses and transcendental functions 2e−4 / 5e−12). Results that should be exact, such as transpose, conjugate, add/subtract, clamp, sign and step, are compared exactly.

Differences found and fixed (portable path only; the simd path is unchanged)

Function Apple simd Portable before
normalize(Quat) with zero length, and so act identity NaN
clamp(NaN, min, max) min NaN
fract(NaN), fract(±∞) 0.99999994 (largest value below 1) NaN
quaternion(from: v, to: -v) 180° about a deterministic axis NaN
matrix3x3/4x4(from:) with a non-unit quaternion x²−y²−z²+w² diagonal 1−2(y²+z²) (different result)
  • clamp and fract now use Float/Double.maximum/minimum (IEEE maxNum/minNum), as simd does.
  • quaternion(from:to:) is now a faithful port of simd's algorithm from the SDK header. Previously it was only the "reduced" formula, which Apple documents as unstable beyond 90°.
  • normalize returns the identity when the squared length is zero.
  • One deliberate exception: for quaternions whose squared length is subnormal (Float components around 1e−20), simd's fast reciprocal square root returns inf/nan. Copying that artifact makes no sense, so those inputs are left out of the tests.

Staying correct

  • Scripts/check-simd-differential-coverage.py (make check-simd-coverage) fails if a function with a simd branch has no test. Each test lists what it covers with // covers: name(Types) annotations, and the script also flags annotations that no longer match a function. It runs in the macOS CI job. I checked that removing one annotation makes it fail and name the function.
  • The macOS CI job already runs the tests with --disable-default-traits (from Speed up the portable 4x4 inverse, adjugate and quaternion multiply #76). make test-portable does the same locally.

Verified

  • All tests pass: 311 with simd and 379 without, including the 68 differential tests. Both check-inlinable configurations pass, and lint is clean.
  • Benchmarks are unchanged (Mat4x4f.fromQuaternion 7 ns portable), and allocations are still 0.

https://claude.ai/code/session_011WSUtoLptjea3s3iC5wTNC

The differential tests against Apple's simd found five places where the
same call returned different results with and without the simd trait,
i.e. on Linux/Windows vs. Apple platforms:

- normalize(Quat4f/Quat4d) of a zero-length quaternion: simd returns the
  identity, portable returned NaN (and so did act(_:_:), which
  normalizes). Portable now returns the identity when the squared length
  is zero, like simd.
- clamp(NaN, min, max): simd returns min (IEEE maxNum/minNum), portable
  returned NaN. Now uses Float/Double.maximum/minimum.
- fract(NaN/±inf): simd returns the largest value below 1, portable
  returned NaN. Now uses .minimum like simd_fract.
- quaternion(from:to:): portable used only the "reduced" formula, which
  simd documents as unstable beyond 90 degrees and which returned NaN for
  opposite vectors. Now a port of simd's algorithm: two-step rotation
  beyond 90 degrees and the same orthogonal axis for opposite vectors.
- matrix3x3/matrix4x4(from:) diagonal: 1 - 2(y^2 + z^2) vs simd's
  x^2 - y^2 - z^2 + w^2. Equal for unit quaternions, different for
  non-unit ones. Now uses simd's formula.

The simd code path is unchanged.

Claude-Session: https://claude.ai/code/session_011WSUtoLptjea3s3iC5wTNC
Extends the PortableVsSIMD differential tests from the functions changed
in #76 to all 108 functions that have both a simd and a portable
implementation: matrix multiplication, 3x3 determinant/inverse,
transpose, quaternion arithmetic and queries, act, vector dot/length/
normalize/cross, clamp/fract/mix/rsqrt/sign/smoothstep/step, and the
quaternion <-> matrix / angle-axis / from-to conversions.

Inputs are 256 pseudo-random values per test plus edge cases: identity,
diagonal, permutation and singular matrices, small and large scales,
zero vectors and quaternions, unit axes, 180-degree rotations, opposite
vectors, NaN, infinities and signed zeros. Shared inputs and helpers
live in PortableVsSIMDSupport.swift.

Every test names the functions it covers with `// covers:` comments.
Scripts/check-simd-differential-coverage.py fails if a function with a
simd branch is not covered or an annotation is stale; it runs in the
macOS CI job (make check-simd-coverage). make test-portable runs the
differential tests locally.

Claude-Session: https://claude.ai/code/session_011WSUtoLptjea3s3iC5wTNC
@ctreffs
ctreffs merged commit 3b65b0b into master Oct 17, 2026
16 checks passed
@ctreffs
ctreffs deleted the test/simd-differential-all branch October 17, 2026 19:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant