You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
import'dart:typed_data';
Stringf(double d) =>
d.isNaN ?'NaN': (d ==0.0&& d.isNegative ?'-0.0':'$d');
voidmain() {
final a =Float32x4(double.nan, 5.0, -0.0, 0.0);
final b =Float32x4(5.0, double.nan, 0.0, -0.0);
final mn = a.min(b);
final mx = a.max(b);
print('min: ${f(mn.x)} ${f(mn.y)} ${f(mn.z)} ${f(mn.w)}');
print('max: ${f(mx.x)} ${f(mx.y)} ${f(mx.z)} ${f(mx.w)}');
}
Today Float32x4.min, max & clamp behave as a lane-wise a < b ? a : b / a > b ? a : b.
================= current behavior (VM / dart2js / dart2wasm) =================
min: 5.0 NaN 0.0 -0.0
max: 5.0 NaN 0.0 -0.0
However, this is not what the WebAssembly SIMD f32x4.min / f32x4.max instructions compute. Those follow IEEE-style semantics: NaN propagates, and signed zero is handled per IEEE min/max:
================= native f32x4.min / f32x4.max (what the SIMD ops give) =================
min: NaN NaN -0.0 -0.0
max: NaN NaN 0.0 0.0
For example:
min(NaN, x) / max(NaN, x): naive returns x, the SIMD op returns NaN.
The same applies to Float64x2.min / max / clamp versus f64x2.min / f64x2.max.
Because of this, vectorizing Float32x4 on dart2wasm had to keep min, max, and clamp implemented as the scalar a < b ? a : b to preserve the current cross-backend behavior.
So, what should the specified behavior be?
Change min / max / clamp to the Wasm / IEEE SIMD semantics which is a breaking change. Every backend can then use the native min/max instructions, and the behavior matches what a SIMD user coming from C / Rust / hardware intrinsics probably expects. The observable change is limited to NaN and signed-zero inputs.
Keep the current naive a < b ? a : b semantics which blocks SIMD lowering of these ops on dart2wasm and is arguably surprising for a type whose whole point is SIMD.
I lean towards 1. SIMD dart:typed_data types are not widely used, and it seems very unlikely that anyone depends on the exact NaN / signed-zero result of Float32x4.min.
The result is the lesser of the two lane values if either is lesser. The result is unspecified if either lane contains a NaN value or if the values are -0.0 and 0.0, so that neither value is smaller or greater than the other. Different platforms may give different results in those cases, but always one of the lane values.
So the "result is unspecified if either lane contains a NaN value" which means it's unspecified and we can change it without going through breaking change process.
Maybe we should then actually specify what it does?
Activity
lrhn commentedon Aug 4, 2026
1, please.
Let's not enshrine a behavior that is expensive to implement.
mkustermann commentedon Aug 5, 2026
Also in favor of 1. Looking at the docs:
So the "result is unspecified if either lane contains a NaN value" which means it's unspecified and we can change it without going through breaking change process.
Maybe we should then actually specify what it does?
[typed_data] Implement IEEE min/max for Float32x4/Float64x2 and add a…
[typed_data] Implement Float32x4 and Float64x2 clamp with min and max