CNA-BUG-063: Vector3::Length, LengthSquared, Distance and DistanceSquared accumulate in float, not at the width CNA measured XNA to use
Evidence basis: source-verified at the pinned commit; recorded by CNA's own run (not repeated here); tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.
CNA's own measurement of the XNA 4.0 runtime found Vector3.Distance summed at extended precision (a float sum matched none of 200 pairs), and BoundingSphere.cpp follows that rule, but the public Vector3 length and distance functions still sum in float.
- Identifier
CNA-BUG-063- Category
- Bug
- Subsystem
- Math & geometry
- Status
- Open
- Verified against
- CNA
009d40f5(009d40f5dd085c4e674d3479675fac84b12b3e0a) - Severity
- Low (a triage suggestion, not a project priority)
- Evidence basis
- Recorded by CNA: CNA's own recorded run, not repeated here
- Tests touching this area
- Yes: see Current tests
- Affected contract
- Vector3::Length(), LengthSquared(), Vector3::Distance and Vector3::DistanceSquared (all overloads); the matching C ABI vector routes
Expected behaviour
XNA 4.0 is a 32-bit assembly whose float arithmetic runs on the x87 unit, so x*x + y*y + z*z is accumulated at extended precision and narrowed once. CNA recorded this against the genuine runtime: over 200 random point pairs a float accumulation reproduced XNA's Vector3.Distance on none and a double accumulation on all (plan_xna_sample_xnb_sweep.md, XNASWEEP-134, quoted in the comment on SquaredLength in BoundingSphere.cpp). CNA applies the same widening to Matrix::Multiply, Matrix::Invert and Vector3::Transform.
Actual behaviour at TARGET
In Vector3.cpp, Length() is std::sqrt((X * X) + (Y * Y) + (Z * Z)) and DistanceSquared sums the three squared differences, all in float; Distance is the square root of that. Only the private helpers SquaredLength, WideLength and WideDistance inside BoundingSphere.cpp accumulate in double. By CNA's own recorded measurement (a float accumulation matched XNA's Distance on none of 200 pairs) the public functions therefore differ from XNA in the last bit on those pairs; how often they differ on typical inputs was not measured. Vector2 and Vector4 have the same float shape; they were not measured.
Source locations
modules/math/src/Vector3.cpp— Vector3::Length, LengthSquared, Distance, DistanceSquaredmodules/math/src/BoundingSphere.cpp— SquaredLength, WideLength, WideDistance and the XNASWEEP-134 commentplans/plan_xna_sample_xnb_sweep.md— XNASWEEP-134: the recorded 200-pair measurementmodules/math/tests/Microsoft/Xna/Framework/BoundingSphereOracleTests.cpp— the oracle covers CreateFromPoints onlymodules/math/tests/Microsoft/Xna/Framework/Vector3Tests.cpp— length and distance tests use a tolerance
Evidence
Checked by reading at 009d40f5; nothing was built or executed for this entry. The float accumulation is read from the function bodies; the XNA behaviour is CNA's recorded measurement, not re-run here, and the public functions' own output was not compared with XNA. Hence the confidence is strong rather than reproduced.
Independent re-verification: Checked by reading at 009d40f5; nothing was built or executed for this entry. The float accumulation is read from the function bodies; the XNA behaviour is CNA's recorded measurement (XNASWEEP-134, quoted in the comment on SquaredLength), not re-run here, and its 200 pairs are not committed as a corpus: the committed framework oracle has multiply, invert, rotation, transform and angle families only. An audit-side Python model of the float and wide accumulation rules reproduces CNA's own committed counts (a float accumulation matches 48 of 168 whole-matrix products and 8 of 60 Vector3 transforms in matrix-oracle.txt, a wide one all of them), yet on individual scalar results the float rule differs from the wide one on only roughly 15 to 30 percent of Distance results and 30 to 45 percent of DistanceSquared results for uniform random points. A claim that the public functions differ from XNA for most inputs is therefore not established. The confidence is recorded-by-cna, not reproduced.
Focused reproduction
No focused reproduction is known. Nothing has been invented here; the evidence above is what exists.
Current tests
Vector3Tests.cpp checks length and distance with a tolerance; BoundingSphereOracleTests.cpp compares bits for CreateFromPoints only, and the matrix oracle does not include length or distance.
Regression test
Add Vector3.Length, Distance and DistanceSquared cases to the recorded framework oracle (the 200 measured pairs are the natural set) and compare bits, with a negative control showing that a float accumulation fails, as MatrixOracleTests does.
Blast radius
Every caller, with last-bit effects only: inside math, BoundingSphere::Contains and Intersects (sphere and point), CreateFromBoundingBox, CreateMerged, Plane::Normalize and the frustum's plane normalisation; outside it, callers in audio, content import and the C ABI. Exact-boundary containment answers can flip; nothing else changes.
Workaround
Where bit-exact XNA parity matters, accumulate the squares in double and narrow once, as BoundingSphere.cpp's private helpers do.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- User guide
- Math types: numerical behaviour
- Maintainer workflow
- Verification: the XNA oracle corpus
- Deep dives
- Compatibility and evidence levels
- Known issues
- Bug index