Skip to content

Compare CELT rotation paths within a tolerance - #254

Closed
hashirmuzaffar wants to merge 1 commit into
pion:mainfrom
hashirmuzaffar:celt-fma-tolerance
Closed

hashirmuzaffar wants to merge 1 commit into
pion:mainfrom
hashirmuzaffar:celt-fma-tolerance

Conversation

@hashirmuzaffar

Copy link
Copy Markdown

Fixes #241.

TestExpRotation1BlockOfFour compares the block-of-four rotation against the scalar reference with assert.Equal. Both are Go, both compiled for the host, so the comparison looked safe — but the spec lets an implementation fuse a multiply and a following add into one operation that rounds once instead of twice, and the two loop bodies are written differently enough that a compiler may fuse one and not the other. arm64 does. It reproduces on Go 1.24 through 1.27, on Linux and macOS, and passes on amd64.

One note on the issue text: the expected values aren't stored vectors generated on amd64. want is produced at runtime by expRotation1Scalar in the same file, so this is two Go functions disagreeing on the same machine rather than a recorded-vector mismatch.

Neither result is wrong. Running the same rotation in float64 and measuring both against it:

len= 8 stride=4   scalar  1 ulp    block-of-four  1 ulp
len=13 stride=4   scalar  4 ulp    block-of-four  3 ulp
len=18 stride=5   scalar 10 ulp    block-of-four 12 ulp
len=20 stride=7   scalar 11 ulp    block-of-four 12 ulp

Neither is consistently closer; they are both a few ulp from exact and differ in where the rounding lands.

The tolerance is chosen from measurement rather than picked. Across these four shapes and rotation angles of (0.9, 0.4), (0.6, 0.8), (0.99, 0.14) and (0.1, 0.99), the largest disagreement between the two paths is 1.91e-06 on values of magnitude ten, so the bound is 1e-5. A pure ulp bound would have needed to be around 64, because one output cancels down to 0.088 where a 4e-07 absolute difference is 52 ulp — relative error is not meaningful for a value that has cancelled.

It still catches real defects: introducing a 0.01 percent error into one coefficient of the unrolled forward path fails the test.

go test ./... passes on arm64 with this change; it fails on main without it.

TestExpRotation1BlockOfFour asserted that the block-of-four rotation and the
scalar reference produce identical float32 results. The language permits a
multiplication followed by an addition to be fused into one operation that
rounds once rather than twice, and the two loop bodies are written differently
enough that a compiler may fuse one and not the other. arm64 does, so the test
fails there on every Go version from 1.24 to 1.27 while passing on amd64.

Neither result is wrong. Measured against the same rotation carried out in
float64, both stay within a few units in the last place, and across these
lengths and a range of rotation angles the largest disagreement between them
is 2e-06 on values of magnitude ten. The tolerance is set well above that and
far below a real defect: introducing a 0.01 percent error in one coefficient
of the unrolled path still fails the test.

Fixes pion#241
@hashirmuzaffar

Copy link
Copy Markdown
Author

Superseded by #252, which landed the same fix. I missed that it was already open when I started — my fault for checking the issue but not the open pull requests.

Leaving one measurement here in case it is ever useful. Across the four shapes in the test and rotation angles of (0.9, 0.4), (0.6, 0.8), (0.99, 0.14) and (0.1, 0.99), the largest disagreement between the two paths is 1.91e-06 on values of magnitude ten, so the 1e-5 tolerance in #252 has roughly five times headroom. A pure ulp bound would have needed to be around 64, because one output cancels to 0.088 where a 4e-07 absolute difference is 52 ulp.

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.

go test ./... fails on arm64 in internal/celt without -race

1 participant