diff --git a/src/libraries/System.Numerics.Vectors/tests/Vector2Tests.cs b/src/libraries/System.Numerics.Vectors/tests/Vector2Tests.cs
index cc2b20ab5beb83..8040b219fcb6ae 100644
--- a/src/libraries/System.Numerics.Vectors/tests/Vector2Tests.cs
+++ b/src/libraries/System.Numerics.Vectors/tests/Vector2Tests.cs
@@ -1816,9 +1816,76 @@ public void ShuffleTest(float x, float y)
[Theory]
[InlineData(1.0f, 2.0f, 3.0f)]
[InlineData(5.0f, 6.0f, 11.0f)]
- public void SumTest(float x, float y, float expectedResult)
- {
- Assert.Equal(expectedResult, Vector2.Sum(Vector2.Create(x, y)));
+ [InlineData(-0.0f, -0.0f, -0.0f)]
+ [InlineData(1.0f, -1.0f, +0.0f)]
+ [InlineData(float.Epsilon, -float.Epsilon, +0.0f)]
+ public void SumAndDotTest(float x, float y, float expectedResult)
+ {
+ Vector2 value = Vector2.Create(x, y);
+ Assert.Equal(BitConverter.SingleToInt32Bits(expectedResult), BitConverter.SingleToInt32Bits(Vector2.Sum(value)));
+ Assert.Equal(BitConverter.SingleToInt32Bits(expectedResult), BitConverter.SingleToInt32Bits(Vector2.Dot(value, Vector2.One)));
+
+ Vector2 sum = Vector2.Create(Vector2.Sum(value));
+ Vector2 dot = Vector2.Create(Vector2.Dot(value, Vector2.One));
+ for (int i = 0; i < ElementCount; i++)
+ {
+ Assert.Equal(BitConverter.SingleToInt32Bits(expectedResult), BitConverter.SingleToInt32Bits(sum[i]));
+ Assert.Equal(BitConverter.SingleToInt32Bits(expectedResult), BitConverter.SingleToInt32Bits(dot[i]));
+ }
+ }
+
+ [Theory]
+ [InlineData(float.NaN, 1.0f, float.NaN)]
+ [InlineData(1.0f, float.NaN, float.NaN)]
+ [InlineData(float.PositiveInfinity, 1.0f, float.PositiveInfinity)]
+ [InlineData(1.0f, float.PositiveInfinity, float.PositiveInfinity)]
+ [InlineData(float.NegativeInfinity, 1.0f, float.NegativeInfinity)]
+ [InlineData(1.0f, float.NegativeInfinity, float.NegativeInfinity)]
+ [InlineData(float.PositiveInfinity, float.NegativeInfinity, float.NaN)]
+ [InlineData(float.NegativeInfinity, float.PositiveInfinity, float.NaN)]
+ public void ReductionsWithNonFiniteElementsTest(float x, float y, float expectedResult)
+ {
+ Vector2 value = Vector2.Create(x, y);
+ Assert.Equal(expectedResult, Vector2.Sum(value));
+ Assert.Equal(expectedResult, Vector2.Dot(value, Vector2.One));
+
+ float lengthSquared = (x * x) + (y * y);
+ float length = float.Sqrt(lengthSquared);
+ Assert.Equal(length, value.Length());
+ Assert.Equal(lengthSquared, value.LengthSquared());
+ Assert.Equal(length, Vector2.Distance(value, Vector2.Zero));
+ Assert.Equal(lengthSquared, Vector2.DistanceSquared(value, Vector2.Zero));
+
+ Vector2 reflected = Vector2.Reflect(value, Vector2.One);
+ float reflectionScale = -(expectedResult + expectedResult);
+ Vector2 normalized = Vector2.Normalize(value);
+ for (int i = 0; i < ElementCount; i++)
+ {
+ Assert.Equal(float.MultiplyAddEstimate(reflectionScale, 1.0f, value[i]), reflected[i]);
+ Assert.Equal(value[i] / length, normalized[i]);
+ }
+ }
+
+ [Theory]
+ [InlineData(float.NaN)]
+ [InlineData(float.PositiveInfinity)]
+ [InlineData(float.NegativeInfinity)]
+ [InlineData(42.0f)]
+ public void ReductionsIgnoreUpperElementsTest(float upper)
+ {
+ // The JIT can retain the upper SIMD lanes through AsVector2 and inlined reductions.
+ // This exercises poisoned lanes when retained, but their preservation is not guaranteed.
+ Vector2 value = Vector128.Create(3.0f, 4.0f, upper, upper).AsVector2();
+ Vector2 normal = Vector128.Create(1.0f, 0.0f, upper, upper).AsVector2();
+
+ Assert.Equal(7.0f, Vector2.Sum(value));
+ Assert.Equal(25.0f, Vector2.Dot(value, value));
+ Assert.Equal(5.0f, value.Length());
+ Assert.Equal(25.0f, value.LengthSquared());
+ Assert.Equal(5.0f, Vector2.Distance(value, Vector2.Zero));
+ Assert.Equal(25.0f, Vector2.DistanceSquared(value, Vector2.Zero));
+ Assert.Equal(Vector2.Create(3.0f / 5.0f, 4.0f / 5.0f), Vector2.Normalize(value));
+ Assert.Equal(Vector2.Create(-3.0f, 4.0f), Vector2.Reflect(value, normal));
}
[Theory]
diff --git a/src/libraries/System.Numerics.Vectors/tests/Vector3Tests.cs b/src/libraries/System.Numerics.Vectors/tests/Vector3Tests.cs
index 08399cd7b03c74..ee96308e06255a 100644
--- a/src/libraries/System.Numerics.Vectors/tests/Vector3Tests.cs
+++ b/src/libraries/System.Numerics.Vectors/tests/Vector3Tests.cs
@@ -1845,9 +1845,81 @@ public void ShuffleTest(float x, float y, float z)
[Theory]
[InlineData(1.0f, 2.0f, 3.0f, 6.0f)]
[InlineData(5.0f, 6.0f, 7.0f, 18.0f)]
- public void SumTest(float x, float y, float z, float expectedResult)
+ [InlineData(1.0f, -1.0f, -0.0f, +0.0f)]
+ [InlineData(float.Epsilon, -float.Epsilon, -0.0f, +0.0f)]
+ [InlineData(16777216.0f, -16777216.0f, 1.0f, 1.0f)]
+ public void SumAndDotTest(float x, float y, float z, float expectedResult)
+ {
+ Vector3 value = Vector3.Create(x, y, z);
+ Assert.Equal(BitConverter.SingleToInt32Bits(expectedResult), BitConverter.SingleToInt32Bits(Vector3.Sum(value)));
+ Assert.Equal(BitConverter.SingleToInt32Bits(expectedResult), BitConverter.SingleToInt32Bits(Vector3.Dot(value, Vector3.One)));
+
+ Vector3 sum = Vector3.Create(Vector3.Sum(value));
+ Vector3 dot = Vector3.Create(Vector3.Dot(value, Vector3.One));
+ Vector3 reflected = Vector3.Reflect(value, Vector3.One);
+ float reflectionScale = -(expectedResult + expectedResult);
+ for (int i = 0; i < ElementCount; i++)
+ {
+ Assert.Equal(BitConverter.SingleToInt32Bits(expectedResult), BitConverter.SingleToInt32Bits(sum[i]));
+ Assert.Equal(BitConverter.SingleToInt32Bits(expectedResult), BitConverter.SingleToInt32Bits(dot[i]));
+ Assert.Equal(
+ BitConverter.SingleToInt32Bits(float.MultiplyAddEstimate(reflectionScale, 1.0f, value[i])),
+ BitConverter.SingleToInt32Bits(reflected[i]));
+ }
+ }
+
+ [Fact]
+ [SkipOnMono("Mono's Vector3.Dot intrinsic can include an extra zero lane, changing negative zero to positive zero.")]
+ public void SumAndDotNegativeZeroTest()
{
- Assert.Equal(expectedResult, Vector3.Sum(Vector3.Create(x, y, z)));
+ SumAndDotTest(-0.0f, -0.0f, -0.0f, -0.0f);
+ }
+
+ [Theory]
+ [InlineData(float.NaN, 1.0f, 2.0f, float.NaN)]
+ [InlineData(1.0f, float.NaN, 2.0f, float.NaN)]
+ [InlineData(1.0f, 2.0f, float.NaN, float.NaN)]
+ [InlineData(float.PositiveInfinity, 1.0f, 2.0f, float.PositiveInfinity)]
+ [InlineData(1.0f, 2.0f, float.NegativeInfinity, float.NegativeInfinity)]
+ [InlineData(float.PositiveInfinity, float.NegativeInfinity, 1.0f, float.NaN)]
+ [InlineData(1.0f, float.PositiveInfinity, float.NegativeInfinity, float.NaN)]
+ public void ReductionsWithNonFiniteElementsTest(float x, float y, float z, float expectedResult)
+ {
+ Vector3 value = Vector3.Create(x, y, z);
+ Assert.Equal(expectedResult, Vector3.Sum(value));
+ Assert.Equal(expectedResult, Vector3.Dot(value, Vector3.One));
+
+ Vector3 reflected = Vector3.Reflect(value, Vector3.One);
+ float reflectionScale = -(expectedResult + expectedResult);
+ float length = float.Sqrt(((x * x) + (y * y)) + (z * z));
+ Vector3 normalized = Vector3.Normalize(value);
+ for (int i = 0; i < ElementCount; i++)
+ {
+ Assert.Equal(float.MultiplyAddEstimate(reflectionScale, 1.0f, value[i]), reflected[i]);
+ Assert.Equal(value[i] / length, normalized[i]);
+ }
+ }
+
+ [Theory]
+ [InlineData(float.NaN)]
+ [InlineData(float.PositiveInfinity)]
+ [InlineData(float.NegativeInfinity)]
+ [InlineData(42.0f)]
+ public void ReductionsIgnoreUpperElementsTest(float upper)
+ {
+ // The JIT can retain the upper SIMD lane through AsVector3 and inlined reductions.
+ // This exercises a poisoned lane when retained, but its preservation is not guaranteed.
+ Vector3 value = Vector128.Create(2.0f, 3.0f, 6.0f, upper).AsVector3();
+ Vector3 normal = Vector128.Create(1.0f, 0.0f, 0.0f, upper).AsVector3();
+
+ Assert.Equal(11.0f, Vector3.Sum(value));
+ Assert.Equal(49.0f, Vector3.Dot(value, value));
+ Assert.Equal(7.0f, value.Length());
+ Assert.Equal(49.0f, value.LengthSquared());
+ Assert.Equal(7.0f, Vector3.Distance(value, Vector3.Zero));
+ Assert.Equal(49.0f, Vector3.DistanceSquared(value, Vector3.Zero));
+ Assert.Equal(Vector3.Create(2.0f / 7.0f, 3.0f / 7.0f, 6.0f / 7.0f), Vector3.Normalize(value));
+ Assert.Equal(Vector3.Create(-2.0f, 3.0f, 6.0f), Vector3.Reflect(value, normal));
}
[Theory]
diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector2.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector2.cs
index f2ac590846e2e1..fa3cf3506a169b 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector2.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector2.cs
@@ -481,7 +481,11 @@ public static float Cross(Vector2 value1, Vector2 value2)
/// The distance.
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static float Distance(Vector2 value1, Vector2 value2) => Vector128.Distance(value1.AsVector128(), value2.AsVector128());
+ public static float Distance(Vector2 value1, Vector2 value2)
+ {
+ Vector128 difference = value1.AsVector128Unsafe() - value2.AsVector128Unsafe();
+ return float.Sqrt(Sum((difference * difference).AsVector2()));
+ }
/// Returns the Euclidean distance squared between two specified points.
/// The first point.
@@ -489,7 +493,11 @@ public static float Cross(Vector2 value1, Vector2 value2)
/// The distance squared.
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static float DistanceSquared(Vector2 value1, Vector2 value2) => Vector128.DistanceSquared(value1.AsVector128(), value2.AsVector128());
+ public static float DistanceSquared(Vector2 value1, Vector2 value2)
+ {
+ Vector128 difference = value1.AsVector128Unsafe() - value2.AsVector128Unsafe();
+ return Sum((difference * difference).AsVector2());
+ }
/// Divides the first vector by the second.
/// The first vector.
@@ -513,7 +521,7 @@ public static float Cross(Vector2 value1, Vector2 value2)
/// The dot product.
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static float Dot(Vector2 value1, Vector2 value2) => Vector128.Dot(value1.AsVector128(), value2.AsVector128());
+ public static float Dot(Vector2 value1, Vector2 value2) => Sum((value1.AsVector128Unsafe() * value2.AsVector128Unsafe()).AsVector2());
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -844,7 +852,11 @@ public static unsafe Vector2 LoadAligned(float* source)
/// The normalized vector.
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Vector2 Normalize(Vector2 value) => Vector128.Normalize(value.AsVector128()).AsVector2();
+ public static Vector2 Normalize(Vector2 value)
+ {
+ Vector128 vector = value.AsVector128Unsafe();
+ return (vector / Vector128.Sqrt(SumAndBroadcast(vector * vector).AsVector128Unsafe())).AsVector2();
+ }
///
[Intrinsic]
@@ -866,10 +878,10 @@ public static Vector2 Reflect(Vector2 vector, Vector2 normal)
// This implementation is based on the DirectX Math Library XMVector2Reflect method
// https://github.com/microsoft/DirectXMath/blob/master/Inc/DirectXMathVector.inl
- Vector128 vVector = vector.AsVector128();
- Vector128 vNormal = normal.AsVector128();
+ Vector128 vVector = vector.AsVector128Unsafe();
+ Vector128 vNormal = normal.AsVector128Unsafe();
- Vector128 tmp = Vector128.Create(Vector128.Dot(vVector, vNormal));
+ Vector128 tmp = SumAndBroadcast(vVector * vNormal).AsVector128Unsafe();
return Vector128.MultiplyAddEstimate(-(tmp + tmp), vNormal, vVector).AsVector2();
}
@@ -926,7 +938,14 @@ public static (Vector2 Sin, Vector2 Cos) SinCos(Vector2 vector)
///
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static float Sum(Vector2 value) => Vector128.Sum(value.AsVector128());
+ public static float Sum(Vector2 value) => SumAndBroadcast(value.AsVector128Unsafe()).ToScalar();
+
+ [Intrinsic]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static Vector2 SumAndBroadcast(Vector128 value)
+ {
+ return (value + Vector128.Shuffle(value, Vector128.Create(1, 0, 3, 2))).AsVector2();
+ }
/// Transforms a vector by a specified 3x2 matrix.
/// The vector to transform.
@@ -1104,7 +1123,11 @@ public readonly bool TryCopyTo(Span destination)
///
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly float Length() => Vector128.Length(this.AsVector128());
+ public readonly float Length()
+ {
+ Vector128 vector = this.AsVector128Unsafe();
+ return float.Sqrt(Sum((vector * vector).AsVector2()));
+ }
/// Returns the length of the vector squared.
/// The vector's length squared.
@@ -1112,7 +1135,11 @@ public readonly bool TryCopyTo(Span destination)
///
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly float LengthSquared() => Vector128.LengthSquared(this.AsVector128());
+ public readonly float LengthSquared()
+ {
+ Vector128 vector = this.AsVector128Unsafe();
+ return Sum((vector * vector).AsVector2());
+ }
/// Returns the string representation of the current instance using default formatting.
/// The string representation of the current instance.
diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector3.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector3.cs
index c090caec23a245..472e1b145c1aeb 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector3.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector3.cs
@@ -517,7 +517,11 @@ internal static Vector128 Cross(Vector128 vector1, Vector128The distance.
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static float Distance(Vector3 value1, Vector3 value2) => Vector128.Distance(value1.AsVector128(), value2.AsVector128());
+ public static float Distance(Vector3 value1, Vector3 value2)
+ {
+ Vector128 difference = value1.AsVector128Unsafe() - value2.AsVector128Unsafe();
+ return float.Sqrt(Sum((difference * difference).AsVector3()));
+ }
/// Returns the Euclidean distance squared between two specified points.
/// The first point.
@@ -525,7 +529,11 @@ internal static Vector128 Cross(Vector128 vector1, Vector128The distance squared.
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static float DistanceSquared(Vector3 value1, Vector3 value2) => Vector128.DistanceSquared(value1.AsVector128(), value2.AsVector128());
+ public static float DistanceSquared(Vector3 value1, Vector3 value2)
+ {
+ Vector128 difference = value1.AsVector128Unsafe() - value2.AsVector128Unsafe();
+ return Sum((difference * difference).AsVector3());
+ }
/// Divides the first vector by the second.
/// The first vector.
@@ -549,7 +557,7 @@ internal static Vector128 Cross(Vector128 vector1, Vector128The dot product.
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static float Dot(Vector3 vector1, Vector3 vector2) => Vector128.Dot(vector1.AsVector128(), vector2.AsVector128());
+ public static float Dot(Vector3 vector1, Vector3 vector2) => Sum((vector1.AsVector128Unsafe() * vector2.AsVector128Unsafe()).AsVector3());
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -881,7 +889,11 @@ public static unsafe Vector3 LoadAligned(float* source)
/// The normalized vector.
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Vector3 Normalize(Vector3 value) => Vector128.Normalize(value.AsVector128()).AsVector3();
+ public static Vector3 Normalize(Vector3 value)
+ {
+ Vector128 vector = value.AsVector128Unsafe();
+ return (vector / Vector128.Sqrt(SumAndBroadcast(vector * vector).AsVector128Unsafe())).AsVector3();
+ }
///
[Intrinsic]
@@ -903,10 +915,10 @@ public static Vector3 Reflect(Vector3 vector, Vector3 normal)
// This implementation is based on the DirectX Math Library XMVector3Reflect method
// https://github.com/microsoft/DirectXMath/blob/master/Inc/DirectXMathVector.inl
- Vector128 vVector = vector.AsVector128();
- Vector128 vNormal = normal.AsVector128();
+ Vector128 vVector = vector.AsVector128Unsafe();
+ Vector128 vNormal = normal.AsVector128Unsafe();
- Vector128 tmp = Vector128.Create(Vector128.Dot(vVector, vNormal));
+ Vector128 tmp = SumAndBroadcast(vVector * vNormal).AsVector128Unsafe();
return Vector128.MultiplyAddEstimate(-(tmp + tmp), vNormal, vVector).AsVector3();
}
@@ -964,7 +976,20 @@ public static (Vector3 Sin, Vector3 Cos) SinCos(Vector3 vector)
///
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static float Sum(Vector3 value) => Vector128.Sum(value.AsVector128());
+ public static float Sum(Vector3 value)
+ {
+ Vector128 vector = value.AsVector128Unsafe();
+ Vector128 sum = Vector128.Shuffle(vector, Vector128.Create(1, 0, 3, 2)) + vector;
+ return (Vector128.Shuffle(vector, Vector128.Create(2)) + sum).ToScalar();
+ }
+
+ [Intrinsic]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static Vector3 SumAndBroadcast(Vector128 value)
+ {
+ Vector128 sum = Vector128.Shuffle(value, Vector128.Create(1, 0, 3, 2)) + value;
+ return Vector128.Create((Vector128.Shuffle(value, Vector128.Create(2)) + sum).ToScalar()).AsVector3();
+ }
/// Transforms a vector by a specified 4x4 matrix.
/// The vector to transform.
@@ -1114,7 +1139,11 @@ public readonly bool TryCopyTo(Span destination)
///
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly float Length() => Vector128.Length(this.AsVector128());
+ public readonly float Length()
+ {
+ Vector128 vector = this.AsVector128Unsafe();
+ return float.Sqrt(Sum((vector * vector).AsVector3()));
+ }
/// Returns the length of the vector squared.
/// The vector's length squared.
@@ -1122,7 +1151,11 @@ public readonly bool TryCopyTo(Span destination)
///
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly float LengthSquared() => Vector128.LengthSquared(this.AsVector128());
+ public readonly float LengthSquared()
+ {
+ Vector128 vector = this.AsVector128Unsafe();
+ return Sum((vector * vector).AsVector3());
+ }
/// Returns the string representation of the current instance using default formatting.
/// The string representation of the current instance.