CNA-BUG-027: Vector Clamp, Min and Max resolve inverted ranges and NaN operands differently from XNA
Evidence basis: source-verified at the pinned commit; 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.
Vector2/3/4::Clamp use std::min(std::max(v, min), max), so max wins when min > max, whereas XNA's vector Clamp and CNA's MathHelper::Clamp let min win; vector and scalar Min/Max also return a different operand from XNA for NaN inputs.
- Identifier
CNA-BUG-027- Category
- Bug
- Subsystem
- Math & geometry
- Status
- Open
- Verified against
- CNA
009d40f5(009d40f5dd085c4e674d3479675fac84b12b3e0a) - Severity
- Low (a triage suggestion, not a project priority)
- Evidence basis
- Source-verified: read at TARGET, not executed
- Tests touching this area
- Yes: see Current tests
- Affected contract
- Vector2::Clamp, Vector3::Clamp, Vector4::Clamp, the vector Min and Max, MathHelper::Min and MathHelper::Max; the matching C ABI vector routes
Expected behaviour
XNA 4.0 (read from the IL of the genuine XNA 4.0 Microsoft.Xna.Framework assembly; not executed): MathHelper.Clamp and each component of Vector3.Clamp compute v = v > max ? max : v; v = v < min ? min : v, so min wins on an inverted range. Vector3.Max is a > b ? a : b and Vector3.Min is a < b ? a : b per component. MathHelper.Max and Min call System.Math.Max(float, float) and Min, which in .NET Framework 4 return the first argument when it is NaN (mscorlib IL read, not executed).
Actual behaviour at TARGET
MathHelper::ClampinMathHelper.cppmatches XNA.Vector2/3/4::Clampuse a file-localClampScalardefined asstd::min(std::max(value, min), max)inVector3.cppand its two siblings, so max wins:MathHelper::Clamp(5, 10, 1)is 10 while the vector clamp of the same components is 1.- The vector
MaxandMinusestd::maxandstd::min, which return the first operand unless it compares smaller (or larger). With a NaN first operand they return NaN where XNA returns the second; with a NaN second operand they return the first where XNA returns NaN; and for equal operands they pick the first (Max(+0, -0)is +0; XNA gives -0). MathHelper::MaxandMinare ternaries (value1 > value2 ? value1 : value2), soMathHelper::Max(NaN, 1)is 1 where XNA returns NaN.
None of this affects ordinary finite inputs with a valid range; the interpolation helpers that clamp an amount to 0..1 are unaffected.
Source locations
modules/math/src/Vector3.cpp— ClampScalar, Vector3::Clamp, Vector3::Max, Vector3::Minmodules/math/src/Vector2.cpp— ClampScalar, Vector2::Clamp, Vector2::Max, Vector2::Minmodules/math/src/Vector4.cpp— ClampScalar, Vector4::Clamp, Vector4::Max, Vector4::Minmodules/math/src/MathHelper.cpp— MathHelper::Clamp (matches XNA), MathHelper::Max and MathHelper::Min (ternaries)modules/math/tests/Microsoft/Xna/Framework/Vector3Tests.cpp— ClampKeepsComponentsInRange, MinReturnsComponentWiseMinimum, MaxReturnsComponentWiseMaximum: ordered ranges and finite values onlymodules/c-api/src/CnaCApiVectors.cpp— the C ABI vector clamp routes call the C++ functions
Evidence
Checked by reading at 009d40f5; nothing was built or executed for this entry. The operand selection follows from the standard definitions of std::min and std::max and from the ternaries. The XNA behaviour was read from the IL of MathHelper.Clamp, Vector3.Clamp, Vector3.Max and MathHelper.Max, and from the pinned .NET Framework 4 mscorlib IL of Math.Max(float, float). Vector2 and Vector4 were read to have the same shape; their XNA IL was not re-read.
Focused reproduction
Illustrative; not compiled or run for this entry.
float s = MathHelper::Clamp(5.0f, 10.0f, 1.0f); // 10 (XNA: 10)
Vector3 v = Vector3::Clamp(Vector3(5.0f), Vector3(10.0f), Vector3(1.0f)); // (1, 1, 1) (XNA: (10, 10, 10))
float n = MathHelper::Max(std::numeric_limits<float>::quiet_NaN(), 1.0f); // 1 (XNA: NaN)
Current tests
The Clamp, Min and Max cases in Vector3Tests.cpp and MathHelperTests.cpp use ordered ranges and finite values; none covers an inverted range, a NaN operand or signed zeros, and no oracle covers these functions.
Regression test
For each vector type: Clamp with min greater than max expecting min per component; Max and Min with NaN in either operand and with (+0, -0), expecting XNA's operand; and MathHelper::Max(NaN, 1) expecting NaN.
Blast radius
Only callers that pass an inverted range or NaN/signed-zero operands to these functions, including through the C ABI vector routes. The scalar clamp used internally for interpolation amounts (0 to 1) is unaffected.
Workaround
Order the bounds before clamping, or clamp per component with MathHelper::Clamp; test for NaN explicitly where it matters.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- User guide
- Math types: vectors and MathHelper
- Maintainer workflow
- I need to change public XNA behaviour
- Known issues
- Bug index