CNA-BUG-027: Vector Clamp, Min and Max resolve inverted ranges and NaN operands differently from XNA

CNA snapshot 009d40f5  ·  Known Issues › Current bugs  ·  source links pinned to 009d40f5

✓

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::Clamp in MathHelper.cpp matches XNA.
  • Vector2/3/4::Clamp use a file-local ClampScalar defined as std::min(std::max(value, min), max) in Vector3.cpp and 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 Max and Min use std::max and std::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::Max and Min are ternaries (value1 > value2 ? value1 : value2), so MathHelper::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

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.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.

Known issues
Bug index