Reject raw-encode domains that collapse after f32 conversion#222
Reject raw-encode domains that collapse after f32 conversion#222Alek99 wants to merge 2 commits into
Conversation
_raw_wire_ok checked that the f64 span survives an f32 cast, but not that the *endpoints* stay distinct in f32 or that the f32 reciprocal of the f32 span is finite. [1e30, 1e30 + 1e20] passed while both endpoints (and every shipped value) round to the same f32 — the shader would map all values to the domain floor; [0, 1e-45] passed while its inverse span overflows to Inf in the map uniform. Both now fall back to the unit encode, which normalizes in f64 before the wire. Tests pin both cases.
…eview) The overflow guard tested finiteness by casting the reciprocal through np.float32, which emits an overflow warning for exactly the subnormal spans it exists to reject — and under np.seterr(over="raise") that warning becomes a FloatingPointError inside build_payload, turning a valid extreme domain into a crash instead of a unit-encode fallback. Decide by f64 comparison against np.finfo(np.float32).max instead; the test now runs the probe under np.errstate(over="raise") to pin it.
Greptile SummaryThis PR tightens the raw continuous-channel encoding guard. The main changes are:
Confidence Score: 4/5The raw-wire guard needs to reject domains whose mapping subtraction overflows in the shader. Wide opposite-sign domains can pass the Python guard, and the same domains produce non-finite f32 arithmetic in the shader. The endpoint-collapse and reciprocal-overflow cases are otherwise covered. python/xy/channels.py
What T-Rex did
Important Files Changed
Reviews (1): Last reviewed commit: "Compare the f32 reciprocal bound in f64,..." | Re-trigger Greptile |
| lo32, hi32 = float(np.float32(lo)), float(np.float32(hi)) | ||
| return lo32 < hi32 and 1.0 / (hi32 - lo32) <= float(np.finfo(np.float32).max) |
There was a problem hiding this comment.
Shader Subtraction Can Overflow
A domain such as (-3e38, 3e38) passes this guard because both endpoints are finite f32 values and the reciprocal is checked from a Python-f64 difference. The shader then evaluates a_cval - lo in f32, where values near the upper endpoint overflow to Inf, producing an invalid color coordinate or a saturated size. Reject domains whose f32 mapping subtraction is not finite.
| lo32, hi32 = float(np.float32(lo)), float(np.float32(hi)) | |
| return lo32 < hi32 and 1.0 / (hi32 - lo32) <= float(np.finfo(np.float32).max) | |
| lo32, hi32 = float(np.float32(lo)), float(np.float32(hi)) | |
| span32 = hi32 - lo32 | |
| return lo32 < hi32 and span32 <= float(np.finfo(np.float32).max) and 1.0 / span32 <= float( | |
| np.finfo(np.float32).max | |
| ) |
|
Folding back per review direction — keeping #221 as the single PR. These two fixes patch |
Merging this PR will not alter performance
Comparing Footnotes
|
Review fixes for #187's
_raw_wire_okguard, split out of #221 when that PR retargeted to main (these fixes belong to the raw-encode code, which lives on this stack).[1e30, 1e30 + 1e20]): every shipped value rounds tof32(lo)and the shader maps them all to the domain floor. It also accepted spans whose f32 reciprocal overflows ([0, 1e-45]), sendingInfinto the map uniform. Both now requirefloat32(lo) < float32(hi)and a finite f32 inverse span, falling back to the f64-normalized unit encode; tests pin both cases.np.finfo(np.float32).max, never cast throughnp.float32: the demoting cast warns on overflow for exactly the subnormal spans being rejected, andnp.seterr(over="raise")setups would turn that into aFloatingPointErrorinsidebuild_payload. The overflow test runs its probe undernp.errstate(over="raise")to pin it.Full
tests/test_scatter.pypasses (82); the fallback tests also pass with-W error::RuntimeWarning(no warnings emitted).