diff --git a/src/libraries/Common/tests/TestUtilities/System/AssertExtensions.cs b/src/libraries/Common/tests/TestUtilities/System/AssertExtensions.cs index 7c5585a4da608..f45b88c9478d4 100644 --- a/src/libraries/Common/tests/TestUtilities/System/AssertExtensions.cs +++ b/src/libraries/Common/tests/TestUtilities/System/AssertExtensions.cs @@ -5,6 +5,9 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#if NET6_0_OR_GREATER +using System.Runtime.Intrinsics; +#endif using System.Threading; using System.Threading.Tasks; using Xunit; diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorPrimitives.netcore.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorPrimitives.netcore.cs index 93d69b4d41ea7..5d06bd523c6bf 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorPrimitives.netcore.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorPrimitives.netcore.cs @@ -11766,6 +11766,395 @@ public static Vector512 Invoke(Vector512 t) /// T.Log2(x) internal readonly struct Log2Operator : IUnaryOperator where T : ILogarithmicFunctions + { + public static bool Vectorizable => (typeof(T) == typeof(double)) + || (typeof(T) == typeof(float)); + + public static T Invoke(T x) => T.Log2(x); + + public static Vector128 Invoke(Vector128 x) + { +#if NET9_0_OR_GREATER + if (typeof(T) == typeof(double)) + { + return Vector128.Log2(x.AsDouble()).As(); + } + else + { + Debug.Assert(typeof(T) == typeof(float)); + return Vector128.Log2(x.AsSingle()).As(); + } +#else + if (typeof(T) == typeof(double)) + { + return Log2OperatorDouble.Invoke(x.AsDouble()).As(); + } + else + { + Debug.Assert(typeof(T) == typeof(float)); + return Log2OperatorSingle.Invoke(x.AsSingle()).As(); + } +#endif + } + + public static Vector256 Invoke(Vector256 x) + { +#if NET9_0_OR_GREATER + if (typeof(T) == typeof(double)) + { + return Vector256.Log2(x.AsDouble()).As(); + } + else + { + Debug.Assert(typeof(T) == typeof(float)); + return Vector256.Log2(x.AsSingle()).As(); + } +#else + if (typeof(T) == typeof(double)) + { + return Log2OperatorDouble.Invoke(x.AsDouble()).As(); + } + else + { + Debug.Assert(typeof(T) == typeof(float)); + return Log2OperatorSingle.Invoke(x.AsSingle()).As(); + } +#endif + } + + public static Vector512 Invoke(Vector512 x) + { +#if NET9_0_OR_GREATER + if (typeof(T) == typeof(double)) + { + return Vector512.Log2(x.AsDouble()).As(); + } + else + { + Debug.Assert(typeof(T) == typeof(float)); + return Vector512.Log2(x.AsSingle()).As(); + } +#else + if (typeof(T) == typeof(double)) + { + return Log2OperatorDouble.Invoke(x.AsDouble()).As(); + } + else + { + Debug.Assert(typeof(T) == typeof(float)); + return Log2OperatorSingle.Invoke(x.AsSingle()).As(); + } +#endif + } + } + +#if !NET9_0_OR_GREATER + /// double.Log2(x) + internal readonly struct Log2OperatorDouble : IUnaryOperator + { + // This code is based on `vrd2_log2` from amd/aocl-libm-ose + // Copyright (C) 2021-2022 Advanced Micro Devices, Inc. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + // Reduce x into the form: + // x = (-1)^s*2^n*m + // s will be always zero, as log is defined for positive numbers + // n is an integer known as the exponent + // m is mantissa + // + // x is reduced such that the mantissa, m lies in [2/3,4/3] + // x = 2^n*m where m is in [2/3,4/3] + // log2(x) = log2(2^n*m) We have log(a*b) = log(a)+log(b) + // = log2(2^n) + log2(m) We have log(a^n) = n*log(a) + // = n + log2(m) + // = n + log2(1+(m-1)) + // = n + ln(1+f) * log2(e) Where f = m-1 + // = n + log1p(f) * log2(e) f lies in [-1/3,+1/3] + // + // Thus we have : + // log(x) = n + log1p(f) * log2(e) + // The second term log1p(F) is approximated by using a polynomial + + private const ulong V_MIN = 0x00100000_00000000; // SmallestNormal + private const ulong V_MAX = 0x7FF00000_00000000; // +Infinity + private const ulong V_MSK = 0x000FFFFF_FFFFFFFF; // (1 << 52) - 1 + private const ulong V_OFF = 0x3FE55555_55555555; // 2.0 / 3.0\ + + private const double LN2_HEAD = 1.44269180297851562500E+00; + private const double LN2_TAIL = 3.23791044778235969970E-06; + + private const double C02 = -0.499999999999999560; + private const double C03 = +0.333333333333414750; + private const double C04 = -0.250000000000297430; + private const double C05 = +0.199999999975985220; + private const double C06 = -0.166666666608919500; + private const double C07 = +0.142857145600277100; + private const double C08 = -0.125000005127831270; + private const double C09 = +0.111110952357159440; + private const double C10 = -0.099999750495501240; + private const double C11 = +0.090914349823462390; + private const double C12 = -0.083340600527551860; + private const double C13 = +0.076817603328311300; + private const double C14 = -0.071296718946287310; + private const double C15 = +0.067963465211535730; + private const double C16 = -0.063995035098960040; + private const double C17 = +0.049370587082412105; + private const double C18 = -0.045370170994891980; + private const double C19 = +0.088970636003577750; + private const double C20 = -0.086906174116908760; + + public static bool Vectorizable => true; + + public static double Invoke(double x) => double.Log2(x); + + public static Vector128 Invoke(Vector128 x) + { + Vector128 specialResult = x; + + // x is zero, subnormal, infinity, or NaN + Vector128 specialMask = Vector128.GreaterThanOrEqual(x.AsUInt64() - Vector128.Create(V_MIN), Vector128.Create(V_MAX - V_MIN)); + + if (specialMask != Vector128.Zero) + { + Vector128 xBits = x.AsInt64(); + + // (x < 0) ? float.NaN : x + Vector128 lessThanZeroMask = Vector128.LessThan(xBits, Vector128.Zero).AsDouble(); + + specialResult = Vector128.ConditionalSelect( + lessThanZeroMask, + Vector128.Create(double.NaN), + specialResult + ); + + // double.IsZero(x) ? double.NegativeInfinity : x + Vector128 zeroMask = Vector128.Equals(xBits << 1, Vector128.Zero).AsDouble(); + + specialResult = Vector128.ConditionalSelect( + zeroMask, + Vector128.Create(double.NegativeInfinity), + specialResult + ); + + // double.IsZero(x) | (x < 0) | double.IsNaN(x) | double.IsPositiveInfinity(x) + Vector128 temp = zeroMask + | lessThanZeroMask + | Vector128.GreaterThanOrEqual(xBits, Vector128.Create(double.PositiveInfinity).AsInt64()).AsDouble(); + + // subnormal + Vector128 subnormalMask = Vector128.AndNot(specialMask.AsDouble(), temp); + + // multiply by 2^52, then normalize + x = Vector128.ConditionalSelect( + subnormalMask, + ((x * 4503599627370496.0).AsUInt64() - Vector128.Create(52ul << 52)).AsDouble(), + x + ); + + specialMask = Unsafe.BitCast, Vector128>(temp); + } + + // Reduce the mantissa to [+2/3, +4/3] + Vector128 vx = x.AsUInt64() - Vector128.Create(V_OFF); + Vector128 n = Vector128.ConvertToDouble(vx.AsInt64() >> 52); + vx = (vx & Vector128.Create(V_MSK)) + Vector128.Create(V_OFF); + + // Adjust the mantissa to [-1/3, +1/3] + Vector128 r = vx.AsDouble() - Vector128.One; + + Vector128 r02 = r * r; + Vector128 r04 = r02 * r02; + Vector128 r08 = r04 * r04; + Vector128 r16 = r08 * r08; + + // Compute log(x + 1) using polynomial approximation + // C0 + (r * C1) + (r^2 * C2) + ... + (r^20 * C20) + + Vector128 poly = (((r04 * C20) + + ((((r * C19) + Vector128.Create(C18)) * r02) + + ((r * C17) + Vector128.Create(C16)))) * r16) + + (((((((r * C15) + Vector128.Create(C14)) * r02) + + ((r * C13) + Vector128.Create(C12))) * r04) + + ((((r * C11) + Vector128.Create(C10)) * r02) + + ((r * C09) + Vector128.Create(C08)))) * r08) + + (((((r * C07) + Vector128.Create(C06)) * r02) + + ((r * C05) + Vector128.Create(C04))) * r04) + + ((((r * C03) + Vector128.Create(C02)) * r02) + r); + + return Vector128.ConditionalSelect( + specialMask.AsDouble(), + specialResult, + (poly * LN2_HEAD) + ((poly * LN2_TAIL) + n) + ); + } + + public static Vector256 Invoke(Vector256 x) + { + Vector256 specialResult = x; + + // x is zero, subnormal, infinity, or NaN + Vector256 specialMask = Vector256.GreaterThanOrEqual(x.AsUInt64() - Vector256.Create(V_MIN), Vector256.Create(V_MAX - V_MIN)); + + if (specialMask != Vector256.Zero) + { + Vector256 xBits = x.AsInt64(); + + // (x < 0) ? float.NaN : x + Vector256 lessThanZeroMask = Vector256.LessThan(xBits, Vector256.Zero).AsDouble(); + + specialResult = Vector256.ConditionalSelect( + lessThanZeroMask, + Vector256.Create(double.NaN), + specialResult + ); + + // double.IsZero(x) ? double.NegativeInfinity : x + Vector256 zeroMask = Vector256.Equals(xBits << 1, Vector256.Zero).AsDouble(); + + specialResult = Vector256.ConditionalSelect( + zeroMask, + Vector256.Create(double.NegativeInfinity), + specialResult + ); + + // double.IsZero(x) | (x < 0) | double.IsNaN(x) | double.IsPositiveInfinity(x) + Vector256 temp = zeroMask + | lessThanZeroMask + | Vector256.GreaterThanOrEqual(xBits, Vector256.Create(double.PositiveInfinity).AsInt64()).AsDouble(); + + // subnormal + Vector256 subnormalMask = Vector256.AndNot(specialMask.AsDouble(), temp); + + // multiply by 2^52, then normalize + x = Vector256.ConditionalSelect( + subnormalMask, + ((x * 4503599627370496.0).AsUInt64() - Vector256.Create(52ul << 52)).AsDouble(), + x + ); + + specialMask = Unsafe.BitCast, Vector256>(temp); + } + + // Reduce the mantissa to [+2/3, +4/3] + Vector256 vx = x.AsUInt64() - Vector256.Create(V_OFF); + Vector256 n = Vector256.ConvertToDouble(vx.AsInt64() >> 52); + vx = (vx & Vector256.Create(V_MSK)) + Vector256.Create(V_OFF); + + // Adjust the mantissa to [-1/3, +1/3] + Vector256 r = vx.AsDouble() - Vector256.One; + + Vector256 r02 = r * r; + Vector256 r04 = r02 * r02; + Vector256 r08 = r04 * r04; + Vector256 r16 = r08 * r08; + + // Compute log(x + 1) using polynomial approximation + // C0 + (r * C1) + (r^2 * C2) + ... + (r^20 * C20) + + Vector256 poly = (((r04 * C20) + + ((((r * C19) + Vector256.Create(C18)) * r02) + + ((r * C17) + Vector256.Create(C16)))) * r16) + + (((((((r * C15) + Vector256.Create(C14)) * r02) + + ((r * C13) + Vector256.Create(C12))) * r04) + + ((((r * C11) + Vector256.Create(C10)) * r02) + + ((r * C09) + Vector256.Create(C08)))) * r08) + + (((((r * C07) + Vector256.Create(C06)) * r02) + + ((r * C05) + Vector256.Create(C04))) * r04) + + ((((r * C03) + Vector256.Create(C02)) * r02) + r); + + return Vector256.ConditionalSelect( + specialMask.AsDouble(), + specialResult, + (poly * LN2_HEAD) + ((poly * LN2_TAIL) + n) + ); + } + + public static Vector512 Invoke(Vector512 x) + { + Vector512 specialResult = x; + + // x is zero, subnormal, infinity, or NaN + Vector512 specialMask = Vector512.GreaterThanOrEqual(x.AsUInt64() - Vector512.Create(V_MIN), Vector512.Create(V_MAX - V_MIN)); + + if (specialMask != Vector512.Zero) + { + Vector512 xBits = x.AsInt64(); + + // (x < 0) ? float.NaN : x + Vector512 lessThanZeroMask = Vector512.LessThan(xBits, Vector512.Zero).AsDouble(); + + specialResult = Vector512.ConditionalSelect( + lessThanZeroMask, + Vector512.Create(double.NaN), + specialResult + ); + + // double.IsZero(x) ? double.NegativeInfinity : x + Vector512 zeroMask = Vector512.Equals(xBits << 1, Vector512.Zero).AsDouble(); + + specialResult = Vector512.ConditionalSelect( + zeroMask, + Vector512.Create(double.NegativeInfinity), + specialResult + ); + + // double.IsZero(x) | (x < 0) | double.IsNaN(x) | double.IsPositiveInfinity(x) + Vector512 temp = zeroMask + | lessThanZeroMask + | Vector512.GreaterThanOrEqual(xBits, Vector512.Create(double.PositiveInfinity).AsInt64()).AsDouble(); + + // subnormal + Vector512 subnormalMask = Vector512.AndNot(specialMask.AsDouble(), temp); + + // multiply by 2^52, then normalize + x = Vector512.ConditionalSelect( + subnormalMask, + ((x * 4503599627370496.0).AsUInt64() - Vector512.Create(52ul << 52)).AsDouble(), + x + ); + + specialMask = Unsafe.BitCast, Vector512>(temp); + } + + // Reduce the mantissa to [+2/3, +4/3] + Vector512 vx = x.AsUInt64() - Vector512.Create(V_OFF); + Vector512 n = Vector512.ConvertToDouble(vx.AsInt64() >> 52); + vx = (vx & Vector512.Create(V_MSK)) + Vector512.Create(V_OFF); + + // Adjust the mantissa to [-1/3, +1/3] + Vector512 r = vx.AsDouble() - Vector512.One; + + Vector512 r02 = r * r; + Vector512 r04 = r02 * r02; + Vector512 r08 = r04 * r04; + Vector512 r16 = r08 * r08; + + // Compute log(x + 1) using polynomial approximation + // C0 + (r * C1) + (r^2 * C2) + ... + (r^20 * C20) + + Vector512 poly = (((r04 * C20) + + ((((r * C19) + Vector512.Create(C18)) * r02) + + ((r * C17) + Vector512.Create(C16)))) * r16) + + (((((((r * C15) + Vector512.Create(C14)) * r02) + + ((r * C13) + Vector512.Create(C12))) * r04) + + ((((r * C11) + Vector512.Create(C10)) * r02) + + ((r * C09) + Vector512.Create(C08)))) * r08) + + (((((r * C07) + Vector512.Create(C06)) * r02) + + ((r * C05) + Vector512.Create(C04))) * r04) + + ((((r * C03) + Vector512.Create(C02)) * r02) + r); + + return Vector512.ConditionalSelect( + specialMask.AsDouble(), + specialResult, + (poly * LN2_HEAD) + ((poly * LN2_TAIL) + n) + ); + } + } + + /// float.Log2(x) + internal readonly struct Log2OperatorSingle : IUnaryOperator { // This code is based on `vrs4_log2f` from amd/aocl-libm-ose // Copyright (C) 2021-2022 Advanced Micro Devices, Inc. All rights reserved. @@ -11833,15 +12222,12 @@ public static Vector512 Invoke(Vector512 t) private const float C8 = -0.22616665f; private const float C9 = 0.21228963f; - public static bool Vectorizable => typeof(T) == typeof(float); + public static bool Vectorizable => true; - public static T Invoke(T x) => T.Log2(x); + public static float Invoke(float x) => float.Log2(x); - public static Vector128 Invoke(Vector128 t) + public static Vector128 Invoke(Vector128 x) { - Debug.Assert(typeof(T) == typeof(float)); - Vector128 x = t.AsSingle(); - Vector128 specialResult = x; // x is subnormal or infinity or NaN @@ -11906,14 +12292,11 @@ public static Vector128 Invoke(Vector128 t) specialMask.AsSingle(), specialResult, n + poly - ).As(); + ); } - public static Vector256 Invoke(Vector256 t) + public static Vector256 Invoke(Vector256 x) { - Debug.Assert(typeof(T) == typeof(float)); - Vector256 x = t.AsSingle(); - Vector256 specialResult = x; // x is subnormal or infinity or NaN @@ -11978,14 +12361,11 @@ public static Vector256 Invoke(Vector256 t) specialMask.AsSingle(), specialResult, n + poly - ).As(); + ); } - public static Vector512 Invoke(Vector512 t) + public static Vector512 Invoke(Vector512 x) { - Debug.Assert(typeof(T) == typeof(float)); - Vector512 x = t.AsSingle(); - Vector512 specialResult = x; // x is subnormal or infinity or NaN @@ -12050,9 +12430,10 @@ public static Vector512 Invoke(Vector512 t) specialMask.AsSingle(), specialResult, n + poly - ).As(); + ); } } +#endif [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector128 ElementWiseSelect(Vector128 mask, Vector128 left, Vector128 right) diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 3605169e9b9d5..e166a7c85a09d 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -602,7 +602,6 @@ - @@ -1040,6 +1039,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Double.cs b/src/libraries/System.Private.CoreLib/src/System/Double.cs index 5a96ade08d880..b4177addb4ab3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -105,6 +105,9 @@ public readonly struct Double internal const int TrailingSignificandLength = 52; internal const int SignificandLength = TrailingSignificandLength + 1; + internal const long PositiveInfinityBits = 0x7FF00000_00000000; + internal const long SmallestNormalBits = 0x00100000_00000000; + internal ushort BiasedExponent { get diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index 88d96c09eeca3..5bf8b74de416d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -292,9 +292,9 @@ public static double BitIncrement(double x) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double CopySign(double x, double y) { - if (Sse2.IsSupported || AdvSimd.IsSupported) + if (Vector128.IsHardwareAccelerated) { - return VectorMath.ConditionalSelectBitwise(Vector128.CreateScalarUnsafe(-0.0), Vector128.CreateScalarUnsafe(y), Vector128.CreateScalarUnsafe(x)).ToScalar(); + return Vector128.ConditionalSelect(Vector128.CreateScalarUnsafe(-0.0), Vector128.CreateScalarUnsafe(y), Vector128.CreateScalarUnsafe(x)).ToScalar(); } else { diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index b3c27fc13da06..8d9473d3c0fc2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -105,9 +105,9 @@ public static float BitIncrement(float x) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float CopySign(float x, float y) { - if (Sse.IsSupported || AdvSimd.IsSupported) + if (Vector128.IsHardwareAccelerated) { - return VectorMath.ConditionalSelectBitwise(Vector128.CreateScalarUnsafe(-0.0f), Vector128.CreateScalarUnsafe(y), Vector128.CreateScalarUnsafe(x)).ToScalar(); + return Vector128.ConditionalSelect(Vector128.CreateScalarUnsafe(-0.0f), Vector128.CreateScalarUnsafe(y), Vector128.CreateScalarUnsafe(x)).ToScalar(); } else { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs index 3f57e5c50f444..ea8cf23dd06ec 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs @@ -1781,6 +1781,38 @@ internal static Vector128 LoadUnsafe(ref char source) => internal static Vector128 LoadUnsafe(ref char source, nuint elementOffset) => LoadUnsafe(ref Unsafe.As(ref source), elementOffset); + /// + public static Vector128 Log2(Vector128 vector) + { + if (IsHardwareAccelerated) + { + return VectorMath.Log2Double, Vector128, Vector128>(vector); + } + else + { + return Create( + Vector64.Log2(vector._lower), + Vector64.Log2(vector._upper) + ); + } + } + + /// + public static Vector128 Log2(Vector128 vector) + { + if (IsHardwareAccelerated) + { + return VectorMath.Log2Single, Vector128, Vector128>(vector); + } + else + { + return Create( + Vector64.Log2(vector._lower), + Vector64.Log2(vector._upper) + ); + } + } + /// Computes the maximum of two vectors on a per-element basis. /// The type of the elements in the vector. /// The vector to compare with . diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs index 1c92fb6d54dc5..7d70941d31b8a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs @@ -1755,6 +1755,38 @@ internal static Vector256 LoadUnsafe(ref char source) => internal static Vector256 LoadUnsafe(ref char source, nuint elementOffset) => LoadUnsafe(ref Unsafe.As(ref source), elementOffset); + /// + public static Vector256 Log2(Vector256 vector) + { + if (IsHardwareAccelerated) + { + return VectorMath.Log2Double, Vector256, Vector256>(vector); + } + else + { + return Create( + Vector128.Log2(vector._lower), + Vector128.Log2(vector._upper) + ); + } + } + + /// + public static Vector256 Log2(Vector256 vector) + { + if (IsHardwareAccelerated) + { + return VectorMath.Log2Single, Vector256, Vector256>(vector); + } + else + { + return Create( + Vector128.Log2(vector._lower), + Vector128.Log2(vector._upper) + ); + } + } + /// Computes the maximum of two vectors on a per-element basis. /// The type of the elements in the vector. /// The vector to compare with . diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs index 9a56d233e6831..b926ff67edf66 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs @@ -1806,6 +1806,38 @@ internal static Vector512 LoadUnsafe(ref char source) => internal static Vector512 LoadUnsafe(ref char source, nuint elementOffset) => LoadUnsafe(ref Unsafe.As(ref source), elementOffset); + /// + public static Vector512 Log2(Vector512 vector) + { + if (IsHardwareAccelerated) + { + return VectorMath.Log2Double, Vector512, Vector512>(vector); + } + else + { + return Create( + Vector256.Log2(vector._lower), + Vector256.Log2(vector._upper) + ); + } + } + + /// + public static Vector512 Log2(Vector512 vector) + { + if (IsHardwareAccelerated) + { + return VectorMath.Log2Single, Vector512, Vector512>(vector); + } + else + { + return Create( + Vector256.Log2(vector._lower), + Vector256.Log2(vector._upper) + ); + } + } + /// Computes the maximum of two vectors on a per-element basis. /// The type of the elements in the vector. /// The vector to compare with . diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64.cs index 44a03c3f2a6a2..79de8e2ad2ef3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64.cs @@ -1571,6 +1571,50 @@ public static Vector64 LoadUnsafe(ref readonly T source, nuint elementOffs return Unsafe.ReadUnaligned>(in address); } + internal static Vector64 Log2(Vector64 vector) + where T : ILogarithmicFunctions + { + Unsafe.SkipInit(out Vector64 result); + + for (int index = 0; index < Vector64.Count; index++) + { + T value = T.Log2(vector.GetElement(index)); + result.SetElementUnsafe(index, value); + } + + return result; + } + + /// Computes the log2 of each element in a vector. + /// The vector that will have its log2 computed. + /// A vector whose elements are the log2 of the elements in . + public static Vector64 Log2(Vector64 vector) + { + if (IsHardwareAccelerated) + { + return VectorMath.Log2Double, Vector64, Vector64>(vector); + } + else + { + return Log2(vector); + } + } + + /// Computes the log2 of each element in a vector. + /// The vector that will have its log2 computed. + /// A vector whose elements are the log2 of the elements in . + public static Vector64 Log2(Vector64 vector) + { + if (IsHardwareAccelerated) + { + return VectorMath.Log2Single, Vector64, Vector64>(vector); + } + else + { + return Log2(vector); + } + } + /// Computes the maximum of two vectors on a per-element basis. /// The type of the elements in the vector. /// The vector to compare with . diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs new file mode 100644 index 0000000000000..8cdb1d91010bc --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs @@ -0,0 +1,352 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System.Runtime.Intrinsics +{ + internal static class VectorMath + { + public static TVectorDouble Log2Double(TVectorDouble x) + where TVectorDouble : unmanaged, ISimdVector + where TVectorInt64 : unmanaged, ISimdVector + where TVectorUInt64 : unmanaged, ISimdVector + { + // This code is based on `vrd2_log2` from amd/aocl-libm-ose + // Copyright (C) 2021-2022 Advanced Micro Devices, Inc. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + // Reduce x into the form: + // x = (-1)^s*2^n*m + // s will be always zero, as log is defined for positive numbers + // n is an integer known as the exponent + // m is mantissa + // + // x is reduced such that the mantissa, m lies in [2/3,4/3] + // x = 2^n*m where m is in [2/3,4/3] + // log2(x) = log2(2^n*m) We have log(a*b) = log(a)+log(b) + // = log2(2^n) + log2(m) We have log(a^n) = n*log(a) + // = n + log2(m) + // = n + log2(1+(m-1)) + // = n + ln(1+f) * log2(e) Where f = m-1 + // = n + log1p(f) * log2(e) f lies in [-1/3,+1/3] + // + // Thus we have : + // log(x) = n + log1p(f) * log2(e) + // The second term log1p(F) is approximated by using a polynomial + + const ulong V_MIN = double.SmallestNormalBits; + const ulong V_MAX = double.PositiveInfinityBits; + const ulong V_MSK = 0x000FFFFF_FFFFFFFF; // (1 << 52) - 1 + const ulong V_OFF = 0x3FE55555_55555555; // 2.0 / 3.0\ + + const double LN2_HEAD = 1.44269180297851562500E+00; + const double LN2_TAIL = 3.23791044778235969970E-06; + + const double C02 = -0.499999999999999560; + const double C03 = +0.333333333333414750; + const double C04 = -0.250000000000297430; + const double C05 = +0.199999999975985220; + const double C06 = -0.166666666608919500; + const double C07 = +0.142857145600277100; + const double C08 = -0.125000005127831270; + const double C09 = +0.111110952357159440; + const double C10 = -0.099999750495501240; + const double C11 = +0.090914349823462390; + const double C12 = -0.083340600527551860; + const double C13 = +0.076817603328311300; + const double C14 = -0.071296718946287310; + const double C15 = +0.067963465211535730; + const double C16 = -0.063995035098960040; + const double C17 = +0.049370587082412105; + const double C18 = -0.045370170994891980; + const double C19 = +0.088970636003577750; + const double C20 = -0.086906174116908760; + + TVectorDouble specialResult = x; + + // x is zero, subnormal, infinity, or NaN + TVectorUInt64 specialMask = TVectorUInt64.GreaterThanOrEqual(Unsafe.BitCast(x) - TVectorUInt64.Create(V_MIN), TVectorUInt64.Create(V_MAX - V_MIN)); + + if (specialMask != TVectorUInt64.Zero) + { + TVectorInt64 xBits = Unsafe.BitCast(x); + + // (x < 0) ? float.NaN : x + TVectorDouble lessThanZeroMask = Unsafe.BitCast(TVectorInt64.LessThan(xBits, TVectorInt64.Zero)); + + specialResult = TVectorDouble.ConditionalSelect( + lessThanZeroMask, + TVectorDouble.Create(double.NaN), + specialResult + ); + + // double.IsZero(x) ? double.NegativeInfinity : x + TVectorDouble zeroMask = Unsafe.BitCast(TVectorInt64.Equals(xBits << 1, TVectorInt64.Zero)); + + specialResult = TVectorDouble.ConditionalSelect( + zeroMask, + TVectorDouble.Create(double.NegativeInfinity), + specialResult + ); + + // double.IsZero(x) | (x < 0) | double.IsNaN(x) | double.IsPositiveInfinity(x) + TVectorDouble temp = zeroMask + | lessThanZeroMask + | Unsafe.BitCast(TVectorInt64.GreaterThanOrEqual(xBits, TVectorInt64.Create(double.PositiveInfinityBits))); + + // subnormal + TVectorDouble subnormalMask = TVectorDouble.AndNot(Unsafe.BitCast(specialMask), temp); + + // multiply by 2^52, then normalize + x = TVectorDouble.ConditionalSelect( + subnormalMask, + Unsafe.BitCast(Unsafe.BitCast(x * 4503599627370496.0) - TVectorUInt64.Create(52ul << 52)), + x + ); + + specialMask = Unsafe.BitCast(temp); + } + + // Reduce the mantissa to [+2/3, +4/3] + TVectorUInt64 vx = Unsafe.BitCast(x) - TVectorUInt64.Create(V_OFF); + TVectorDouble n = ConvertToDouble(Unsafe.BitCast(vx) >> 52); + vx = (vx & TVectorUInt64.Create(V_MSK)) + TVectorUInt64.Create(V_OFF); + + // Adjust the mantissa to [-1/3, +1/3] + TVectorDouble r = Unsafe.BitCast(vx) - TVectorDouble.One; + + TVectorDouble r02 = r * r; + TVectorDouble r04 = r02 * r02; + TVectorDouble r08 = r04 * r04; + TVectorDouble r16 = r08 * r08; + + // Compute log(x + 1) using polynomial approximation + // C0 + (r * C1) + (r^2 * C2) + ... + (r^20 * C20) + + TVectorDouble poly = (((r04 * C20) + + ((((r * C19) + TVectorDouble.Create(C18)) * r02) + + ((r * C17) + TVectorDouble.Create(C16)))) * r16) + + (((((((r * C15) + TVectorDouble.Create(C14)) * r02) + + ((r * C13) + TVectorDouble.Create(C12))) * r04) + + ((((r * C11) + TVectorDouble.Create(C10)) * r02) + + ((r * C09) + TVectorDouble.Create(C08)))) * r08) + + (((((r * C07) + TVectorDouble.Create(C06)) * r02) + + ((r * C05) + TVectorDouble.Create(C04))) * r04) + + ((((r * C03) + TVectorDouble.Create(C02)) * r02) + r); + + return TVectorDouble.ConditionalSelect( + Unsafe.BitCast(specialMask), + specialResult, + (poly * LN2_HEAD) + ((poly * LN2_TAIL) + n) + ); + } + + public static TVectorSingle Log2Single(TVectorSingle x) + where TVectorSingle : unmanaged, ISimdVector + where TVectorInt32 : unmanaged, ISimdVector + where TVectorUInt32 : unmanaged, ISimdVector + { + // This code is based on `vrs4_log2f` from amd/aocl-libm-ose + // Copyright (C) 2021-2022 Advanced Micro Devices, Inc. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + // Spec: + // log2f(x) + // = log2f(x) if x ∈ F and x > 0 + // = x if x = qNaN + // = 0 if x = 1 + // = -inf if x = (-0, 0} + // = NaN otherwise + // + // Assumptions/Expectations + // - Maximum ULP is observed to be at 4 + // - Some FPU Exceptions may not be available + // - Performance is at least 3x + // + // Implementation Notes: + // 1. Range Reduction: + // x = 2^n*(1+f) .... (1) + // where n is exponent and is an integer + // (1+f) is mantissa ∈ [1,2). i.e., 1 ≤ 1+f < 2 .... (2) + // + // From (1), taking log on both sides + // log2(x) = log2(2^n * (1+f)) + // = n + log2(1+f) .... (3) + // + // let z = 1 + f + // log2(z) = log2(k) + log2(z) - log2(k) + // log2(z) = log2(kz) - log2(k) + // + // From (2), range of z is [1, 2) + // by simply dividing range by 'k', z is in [1/k, 2/k) .... (4) + // Best choice of k is the one which gives equal and opposite values + // at extrema +- -+ + // 1 | 2 | + // --- - 1 = - |--- - 1 | + // k | k | .... (5) + // +- -+ + // + // Solving for k, k = 3/2, + // From (4), using 'k' value, range is therefore [-0.3333, 0.3333] + // + // 2. Polynomial Approximation: + // More information refer to tools/sollya/vrs4_logf.sollya + // + // 7th Deg - Error abs: 0x1.04c4ac98p-22 rel: 0x1.2216e6f8p-19 + + const uint V_MIN = float.SmallestNormalBits; + const uint V_MAX = float.PositiveInfinityBits; + const uint V_MSK = 0x007F_FFFF; // (1 << 23) - 1 + const uint V_OFF = 0x3F2A_AAAB; // 2.0 / 3.0 + + const float C1 = +1.44269510f; + const float C2 = -0.72134554f; + const float C3 = +0.48089063f; + const float C4 = -0.36084408f; + const float C5 = +0.28889710f; + const float C6 = -0.23594281f; + const float C7 = +0.19948183f; + const float C8 = -0.22616665f; + const float C9 = +0.21228963f; + + TVectorSingle specialResult = x; + + // x is zero, subnormal, infinity, or NaN + TVectorUInt32 specialMask = TVectorUInt32.GreaterThanOrEqual(Unsafe.BitCast(x) - TVectorUInt32.Create(V_MIN), TVectorUInt32.Create(V_MAX - V_MIN)); + + if (specialMask != TVectorUInt32.Zero) + { + TVectorInt32 xBits = Unsafe.BitCast(x); + + // (x < 0) ? float.NaN : x + TVectorSingle lessThanZeroMask = Unsafe.BitCast(TVectorInt32.LessThan(xBits, TVectorInt32.Zero)); + + specialResult = TVectorSingle.ConditionalSelect( + lessThanZeroMask, + TVectorSingle.Create(float.NaN), + specialResult + ); + + // float.IsZero(x) ? float.NegativeInfinity : x + TVectorSingle zeroMask = Unsafe.BitCast(TVectorInt32.Equals(xBits << 1, TVectorInt32.Zero)); + + specialResult = TVectorSingle.ConditionalSelect( + zeroMask, + TVectorSingle.Create(float.NegativeInfinity), + specialResult + ); + + // (x < 0) | float.IsZero(x) | float.IsNaN(x) | float.IsPositiveInfinity(x) + TVectorSingle temp = zeroMask + | lessThanZeroMask + | Unsafe.BitCast(TVectorInt32.GreaterThanOrEqual(xBits, TVectorInt32.Create(float.PositiveInfinityBits))); + + // subnormal + TVectorSingle subnormalMask = TVectorSingle.AndNot(Unsafe.BitCast(specialMask), temp); + + // multiply by 2^23, then normalize + x = TVectorSingle.ConditionalSelect( + subnormalMask, + Unsafe.BitCast(Unsafe.BitCast(x * 8388608.0f) - TVectorUInt32.Create(23u << 23)), + x + ); + + specialMask = Unsafe.BitCast(temp); + } + + // Reduce the mantissa to [+2/3, +4/3] + TVectorUInt32 vx = Unsafe.BitCast(x) - TVectorUInt32.Create(V_OFF); + TVectorSingle n = ConvertToSingle(Unsafe.BitCast(vx) >> 23); + vx = (vx & TVectorUInt32.Create(V_MSK)) + TVectorUInt32.Create(V_OFF); + + // Adjust the mantissa to [-1/3, +1/3] + TVectorSingle r = Unsafe.BitCast(vx) - TVectorSingle.One; + + // Compute log(x + 1) using polynomial approximation + // C0 + (r * C1) + (r^2 * C2) + ... + (r^9 * C9) + + TVectorSingle r2 = r * r; + TVectorSingle r4 = r2 * r2; + TVectorSingle r8 = r4 * r4; + + TVectorSingle poly = (((TVectorSingle.Create(C9) * r) + TVectorSingle.Create(C8)) * r8) + + ((((((TVectorSingle.Create(C7) * r) + TVectorSingle.Create(C6)) * r2) + + ((TVectorSingle.Create(C5) * r) + TVectorSingle.Create(C4))) * r4) + + ((((TVectorSingle.Create(C3) * r) + TVectorSingle.Create(C2)) * r2) + + (TVectorSingle.Create(C1) * r))); + + return TVectorSingle.ConditionalSelect( + Unsafe.BitCast(specialMask), + specialResult, + n + poly + ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TVectorDouble ConvertToDouble(TVectorInt64 vector) + where TVectorDouble : unmanaged, ISimdVector + where TVectorInt64 : unmanaged, ISimdVector + { + Unsafe.SkipInit(out TVectorDouble result); + + if (typeof(TVectorInt64) == typeof(Vector64)) + { + return (TVectorDouble)(object)Vector64.ConvertToDouble((Vector64)(object)vector); + } + else if (typeof(TVectorInt64) == typeof(Vector128)) + { + return (TVectorDouble)(object)Vector128.ConvertToDouble((Vector128)(object)vector); + } + else if (typeof(TVectorInt64) == typeof(Vector256)) + { + return (TVectorDouble)(object)Vector256.ConvertToDouble((Vector256)(object)vector); + } + else if (typeof(TVectorInt64) == typeof(Vector512)) + { + return (TVectorDouble)(object)Vector512.ConvertToDouble((Vector512)(object)vector); + } + else + { + ThrowHelper.ThrowNotSupportedException(); + } + + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TVectorSingle ConvertToSingle(TVectorInt32 vector) + where TVectorSingle : unmanaged, ISimdVector + where TVectorInt32 : unmanaged, ISimdVector + { + Unsafe.SkipInit(out TVectorSingle result); + + if (typeof(TVectorInt32) == typeof(Vector64)) + { + return (TVectorSingle)(object)Vector64.ConvertToSingle((Vector64)(object)vector); + } + else if (typeof(TVectorInt32) == typeof(Vector128)) + { + return (TVectorSingle)(object)Vector128.ConvertToSingle((Vector128)(object)vector); + } + else if (typeof(TVectorInt32) == typeof(Vector256)) + { + return (TVectorSingle)(object)Vector256.ConvertToSingle((Vector256)(object)vector); + } + else if (typeof(TVectorInt32) == typeof(Vector512)) + { + return (TVectorSingle)(object)Vector512.ConvertToSingle((Vector512)(object)vector); + } + else + { + ThrowHelper.ThrowNotSupportedException(); + } + + return result; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Single.cs b/src/libraries/System.Private.CoreLib/src/System/Single.cs index 0c3484dbdd923..44b48ba6b5bbb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -105,6 +105,9 @@ public readonly struct Single internal const int TrailingSignificandLength = 23; internal const int SignificandLength = TrailingSignificandLength + 1; + internal const int PositiveInfinityBits = 0x7F80_0000; + internal const int SmallestNormalBits = 0x0080_0000; + internal byte BiasedExponent { get diff --git a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs index f58be580e073c..2ce1c80ddd229 100644 --- a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs +++ b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs @@ -185,6 +185,8 @@ public static void CopyTo(this System.Runtime.Intrinsics.Vector128 vector, public static System.Runtime.Intrinsics.Vector128 LoadUnsafe(ref readonly T source) { throw null; } [System.CLSCompliantAttribute(false)] public static System.Runtime.Intrinsics.Vector128 LoadUnsafe(ref readonly T source, nuint elementOffset) { throw null; } + public static System.Runtime.Intrinsics.Vector128 Log2(System.Runtime.Intrinsics.Vector128 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector128 Log2(System.Runtime.Intrinsics.Vector128 vector) { throw null; } public static System.Runtime.Intrinsics.Vector128 Max(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 Min(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 Multiply(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } @@ -512,6 +514,8 @@ public static void CopyTo(this System.Runtime.Intrinsics.Vector256 vector, public static System.Runtime.Intrinsics.Vector256 LoadUnsafe(ref readonly T source) { throw null; } [System.CLSCompliantAttribute(false)] public static System.Runtime.Intrinsics.Vector256 LoadUnsafe(ref readonly T source, nuint elementOffset) { throw null; } + public static System.Runtime.Intrinsics.Vector256 Log2(System.Runtime.Intrinsics.Vector256 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector256 Log2(System.Runtime.Intrinsics.Vector256 vector) { throw null; } public static System.Runtime.Intrinsics.Vector256 Max(System.Runtime.Intrinsics.Vector256 left, System.Runtime.Intrinsics.Vector256 right) { throw null; } public static System.Runtime.Intrinsics.Vector256 Min(System.Runtime.Intrinsics.Vector256 left, System.Runtime.Intrinsics.Vector256 right) { throw null; } public static System.Runtime.Intrinsics.Vector256 Multiply(System.Runtime.Intrinsics.Vector256 left, System.Runtime.Intrinsics.Vector256 right) { throw null; } @@ -839,6 +843,8 @@ public static void CopyTo(this System.Runtime.Intrinsics.Vector512 vector, public static System.Runtime.Intrinsics.Vector512 LoadUnsafe(ref readonly T source) { throw null; } [System.CLSCompliantAttribute(false)] public static System.Runtime.Intrinsics.Vector512 LoadUnsafe(ref readonly T source, nuint elementOffset) { throw null; } + public static System.Runtime.Intrinsics.Vector512 Log2(System.Runtime.Intrinsics.Vector512 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector512 Log2(System.Runtime.Intrinsics.Vector512 vector) { throw null; } public static System.Runtime.Intrinsics.Vector512 Max(System.Runtime.Intrinsics.Vector512 left, System.Runtime.Intrinsics.Vector512 right) { throw null; } public static System.Runtime.Intrinsics.Vector512 Min(System.Runtime.Intrinsics.Vector512 left, System.Runtime.Intrinsics.Vector512 right) { throw null; } public static System.Runtime.Intrinsics.Vector512 Multiply(System.Runtime.Intrinsics.Vector512 left, System.Runtime.Intrinsics.Vector512 right) { throw null; } @@ -1138,6 +1144,8 @@ public static void CopyTo(this System.Runtime.Intrinsics.Vector64 vector, public static System.Runtime.Intrinsics.Vector64 LoadUnsafe(ref readonly T source) { throw null; } [System.CLSCompliantAttribute(false)] public static System.Runtime.Intrinsics.Vector64 LoadUnsafe(ref readonly T source, nuint elementOffset) { throw null; } + public static System.Runtime.Intrinsics.Vector64 Log2(System.Runtime.Intrinsics.Vector64 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector64 Log2(System.Runtime.Intrinsics.Vector64 vector) { throw null; } public static System.Runtime.Intrinsics.Vector64 Max(System.Runtime.Intrinsics.Vector64 left, System.Runtime.Intrinsics.Vector64 right) { throw null; } public static System.Runtime.Intrinsics.Vector64 Min(System.Runtime.Intrinsics.Vector64 left, System.Runtime.Intrinsics.Vector64 right) { throw null; } public static System.Runtime.Intrinsics.Vector64 Multiply(System.Runtime.Intrinsics.Vector64 left, System.Runtime.Intrinsics.Vector64 right) { throw null; } diff --git a/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj b/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj index 9bd04def26623..f4dee9ab306cd 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj +++ b/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj @@ -13,6 +13,7 @@ + diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector128Tests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector128Tests.cs index 3e8fc990ee510..d253dbf508543 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector128Tests.cs +++ b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector128Tests.cs @@ -11,6 +11,28 @@ namespace System.Runtime.Intrinsics.Tests.Vectors { public sealed class Vector128Tests { + /// Verifies that two values are equal, within the . + /// The expected value + /// The value to be compared against + /// The total variance allowed between the expected and actual results. + /// Thrown when the values are not equal + internal static void AssertEqual(Vector128 expected, Vector128 actual, Vector128 variance) + { + Vector64Tests.AssertEqual(expected.GetLower(), actual.GetLower(), variance.GetLower()); + Vector64Tests.AssertEqual(expected.GetUpper(), actual.GetUpper(), variance.GetUpper()); + } + + /// Verifies that two values are equal, within the . + /// The expected value + /// The value to be compared against + /// The total variance allowed between the expected and actual results. + /// Thrown when the values are not equal + internal static void AssertEqual(Vector128 expected, Vector128 actual, Vector128 variance) + { + Vector64Tests.AssertEqual(expected.GetLower(), actual.GetLower(), variance.GetLower()); + Vector64Tests.AssertEqual(expected.GetUpper(), actual.GetUpper(), variance.GetUpper()); + } + [Fact] [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(Vector128))] [ActiveIssue("https://github.com/dotnet/runtime/issues/81785", TestPlatforms.Browser)] @@ -4664,5 +4686,21 @@ private static void TestGetOne() MethodInfo methodInfo = typeof(Vector128).GetProperty("One", BindingFlags.Public | BindingFlags.Static).GetMethod; Assert.Equal((Vector128)methodInfo.Invoke(null, null), Vector128.Create(T.One)); } + + [Theory] + [MemberData(nameof(VectorTestMemberData.Log2Double), MemberType = typeof(VectorTestMemberData))] + public void Log2DoubleTest(double value, double expectedResult, double variance) + { + Vector128 actualResult = Vector128.Log2(Vector128.Create(value)); + AssertEqual(Vector128.Create(expectedResult), actualResult, Vector128.Create(variance)); + } + + [Theory] + [MemberData(nameof(VectorTestMemberData.Log2Single), MemberType = typeof(VectorTestMemberData))] + public void Log2SingleTest(float value, float expectedResult, float variance) + { + Vector128 actualResult = Vector128.Log2(Vector128.Create(value)); + AssertEqual(Vector128.Create(expectedResult), actualResult, Vector128.Create(variance)); + } } } diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector256Tests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector256Tests.cs index dd7c5b73d5015..0f61b9d50b641 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector256Tests.cs +++ b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector256Tests.cs @@ -10,6 +10,28 @@ namespace System.Runtime.Intrinsics.Tests.Vectors { public sealed class Vector256Tests { + /// Verifies that two values are equal, within the . + /// The expected value + /// The value to be compared against + /// The total variance allowed between the expected and actual results. + /// Thrown when the values are not equal + internal static void AssertEqual(Vector256 expected, Vector256 actual, Vector256 variance) + { + Vector128Tests.AssertEqual(expected.GetLower(), actual.GetLower(), variance.GetLower()); + Vector128Tests.AssertEqual(expected.GetUpper(), actual.GetUpper(), variance.GetUpper()); + } + + /// Verifies that two values are equal, within the . + /// The expected value + /// The value to be compared against + /// The total variance allowed between the expected and actual results. + /// Thrown when the values are not equal + internal static void AssertEqual(Vector256 expected, Vector256 actual, Vector256 variance) + { + Vector128Tests.AssertEqual(expected.GetLower(), actual.GetLower(), variance.GetLower()); + Vector128Tests.AssertEqual(expected.GetUpper(), actual.GetUpper(), variance.GetUpper()); + } + [Fact] public unsafe void Vector256IsHardwareAcceleratedTest() { @@ -5679,5 +5701,21 @@ private static void TestGetOne() MethodInfo methodInfo = typeof(Vector256).GetProperty("One", BindingFlags.Public | BindingFlags.Static).GetMethod; Assert.Equal((Vector256)methodInfo.Invoke(null, null), Vector256.Create(T.One)); } + + [Theory] + [MemberData(nameof(VectorTestMemberData.Log2Double), MemberType = typeof(VectorTestMemberData))] + public void Log2DoubleTest(double value, double expectedResult, double variance) + { + Vector256 actualResult = Vector256.Log2(Vector256.Create(value)); + AssertEqual(Vector256.Create(expectedResult), actualResult, Vector256.Create(variance)); + } + + [Theory] + [MemberData(nameof(VectorTestMemberData.Log2Single), MemberType = typeof(VectorTestMemberData))] + public void Log2SingleTest(float value, float expectedResult, float variance) + { + Vector256 actualResult = Vector256.Log2(Vector256.Create(value)); + AssertEqual(Vector256.Create(expectedResult), actualResult, Vector256.Create(variance)); + } } } diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector512Tests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector512Tests.cs index b84058b1032ae..778bf2bbc3702 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector512Tests.cs +++ b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector512Tests.cs @@ -9,6 +9,28 @@ namespace System.Runtime.Intrinsics.Tests.Vectors { public sealed class Vector512Tests { + /// Verifies that two values are equal, within the . + /// The expected value + /// The value to be compared against + /// The total variance allowed between the expected and actual results. + /// Thrown when the values are not equal + internal static void AssertEqual(Vector512 expected, Vector512 actual, Vector512 variance) + { + Vector256Tests.AssertEqual(expected.GetLower(), actual.GetLower(), variance.GetLower()); + Vector256Tests.AssertEqual(expected.GetUpper(), actual.GetUpper(), variance.GetUpper()); + } + + /// Verifies that two values are equal, within the . + /// The expected value + /// The value to be compared against + /// The total variance allowed between the expected and actual results. + /// Thrown when the values are not equal + internal static void AssertEqual(Vector512 expected, Vector512 actual, Vector512 variance) + { + Vector256Tests.AssertEqual(expected.GetLower(), actual.GetLower(), variance.GetLower()); + Vector256Tests.AssertEqual(expected.GetUpper(), actual.GetUpper(), variance.GetUpper()); + } + [Fact] public unsafe void Vector512IsHardwareAcceleratedTest() { @@ -5111,5 +5133,21 @@ private static void TestIsNotSupported() MethodInfo methodInfo = typeof(Vector512).GetProperty("IsSupported", BindingFlags.Public | BindingFlags.Static).GetMethod; Assert.False((bool)methodInfo.Invoke(null, null)); } + + [Theory] + [MemberData(nameof(VectorTestMemberData.Log2Double), MemberType = typeof(VectorTestMemberData))] + public void Log2DoubleTest(double value, double expectedResult, double variance) + { + Vector512 actualResult = Vector512.Log2(Vector512.Create(value)); + AssertEqual(Vector512.Create(expectedResult), actualResult, Vector512.Create(variance)); + } + + [Theory] + [MemberData(nameof(VectorTestMemberData.Log2Single), MemberType = typeof(VectorTestMemberData))] + public void Log2SingleTest(float value, float expectedResult, float variance) + { + Vector512 actualResult = Vector512.Log2(Vector512.Create(value)); + AssertEqual(Vector512.Create(expectedResult), actualResult, Vector512.Create(variance)); + } } } diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector64Tests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector64Tests.cs index 7b3f9e97c33ab..7706630bec31d 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector64Tests.cs +++ b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector64Tests.cs @@ -10,6 +10,32 @@ namespace System.Runtime.Intrinsics.Tests.Vectors { public sealed class Vector64Tests { + /// Verifies that two values are equal, within the . + /// The expected value + /// The value to be compared against + /// The total variance allowed between the expected and actual results. + /// Thrown when the values are not equal + internal static void AssertEqual(Vector64 expected, Vector64 actual, Vector64 variance) + { + for (int i = 0; i < Vector64.Count; i++) + { + AssertExtensions.Equal(expected.GetElement(i), actual.GetElement(i), variance.GetElement(i)); + } + } + + /// Verifies that two values are equal, within the . + /// The expected value + /// The value to be compared against + /// The total variance allowed between the expected and actual results. + /// Thrown when the values are not equal + internal static void AssertEqual(Vector64 expected, Vector64 actual, Vector64 variance) + { + for (int i = 0; i < Vector64.Count; i++) + { + AssertExtensions.Equal(expected.GetElement(i), actual.GetElement(i), variance.GetElement(i)); + } + } + [Fact] public unsafe void Vector64IsHardwareAcceleratedTest() { @@ -4077,5 +4103,23 @@ private static void TestGetOne() MethodInfo methodInfo = typeof(Vector64).GetProperty("One", BindingFlags.Public | BindingFlags.Static).GetMethod; Assert.Equal((Vector64)methodInfo.Invoke(null, null), Vector64.Create(T.One)); } + + [Theory] + [MemberData(nameof(VectorTestMemberData.Log2Double), MemberType = typeof(VectorTestMemberData))] + public void Log2DoubleTest(double value, double expectedResult, double variance) + { + Vector64 actualResult = Vector64.Log2(Vector64.Create(value)); + AssertEqual(Vector64.Create(expectedResult), actualResult, Vector64.Create(variance)); + } + + [Theory] + [MemberData(nameof(VectorTestMemberData.Log2Single), MemberType = typeof(VectorTestMemberData))] + public void Log2SingleTest(float value, float expectedResult, float variance) + { + AssertExtensions.Equal(0.0f, 0.0f, 0.0f); + + Vector64 actualResult = Vector64.Log2(Vector64.Create(value)); + AssertEqual(Vector64.Create(expectedResult), actualResult, Vector64.Create(variance)); + } } } diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/VectorTestMemberData.cs b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/VectorTestMemberData.cs new file mode 100644 index 0000000000000..c035baa8fffd3 --- /dev/null +++ b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/VectorTestMemberData.cs @@ -0,0 +1,122 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace System.Runtime.Intrinsics.Tests.Vectors +{ + internal static class VectorTestMemberData + { + // binary64 (double) has a machine epsilon of 2^-52 (approx. 2.22e-16). However, this + // is slightly too accurate when writing tests meant to run against libm implementations + // for various platforms. 2^-50 (approx. 8.88e-16) seems to be as accurate as we can get. + // + // The tests themselves will take CrossPlatformMachineEpsilon and adjust it according to the expected result + // so that the delta used for comparison will compare the most significant digits and ignore + // any digits that are outside the double precision range (15-17 digits). + // + // For example, a test with an expect result in the format of 0.xxxxxxxxxxxxxxxxx will use + // CrossPlatformMachineEpsilon for the variance, while an expected result in the format of 0.0xxxxxxxxxxxxxxxxx + // will use CrossPlatformMachineEpsilon / 10 and expected result in the format of x.xxxxxxxxxxxxxxxx will + // use CrossPlatformMachineEpsilon * 10. + internal const double DoubleCrossPlatformMachineEpsilon = 8.8817841970012523e-16; + + // binary32 (float) has a machine epsilon of 2^-23 (approx. 1.19e-07). However, this + // is slightly too accurate when writing tests meant to run against libm implementations + // for various platforms. 2^-21 (approx. 4.76e-07) seems to be as accurate as we can get. + // + // The tests themselves will take CrossPlatformMachineEpsilon and adjust it according to the expected result + // so that the delta used for comparison will compare the most significant digits and ignore + // any digits that are outside the single precision range (6-9 digits). + // + // For example, a test with an expect result in the format of 0.xxxxxxxxx will use + // CrossPlatformMachineEpsilon for the variance, while an expected result in the format of 0.0xxxxxxxxx + // will use CrossPlatformMachineEpsilon / 10 and expected result in the format of x.xxxxxx will + // use CrossPlatformMachineEpsilon * 10. + private const float SingleCrossPlatformMachineEpsilon = 4.76837158e-07f; + + public static IEnumerable Log2Double + { + get + { + yield return new object[] { double.NegativeInfinity, double.NaN, 0.0 }; + yield return new object[] {-0.11331473229676087, double.NaN, 0.0 }; + yield return new object[] {-0.0, double.NegativeInfinity, 0.0 }; + yield return new object[] { double.NaN, double.NaN, 0.0 }; + yield return new object[] { 0.0, double.NegativeInfinity, 0.0 }; + yield return new object[] { 0.11331473229676087, -3.1415926535897932, DoubleCrossPlatformMachineEpsilon * 10 }; // expected: -(pi) + yield return new object[] { 0.15195522325791297, -2.7182818284590453, DoubleCrossPlatformMachineEpsilon * 10 }; // expected: -(e) + yield return new object[] { 0.20269956628651730, -2.3025850929940460, DoubleCrossPlatformMachineEpsilon * 10 }; // expected: -(ln(10)) + yield return new object[] { 0.33662253682241906, -1.5707963267948966, DoubleCrossPlatformMachineEpsilon * 10 }; // expected: -(pi / 2) + yield return new object[] { 0.36787944117144232, -1.4426950408889634, DoubleCrossPlatformMachineEpsilon * 10 }; // expected: -(log2(e)) + yield return new object[] { 0.37521422724648177, -1.4142135623730950, DoubleCrossPlatformMachineEpsilon * 10 }; // expected: -(sqrt(2)) + yield return new object[] { 0.45742934732229695, -1.1283791670955126, DoubleCrossPlatformMachineEpsilon * 10 }; // expected: -(2 / sqrt(pi)) + yield return new object[] { 0.5, -1.0, 0.0f }; + yield return new object[] { 0.58019181037172444, -0.78539816339744840, DoubleCrossPlatformMachineEpsilon }; // expected: -(pi / 4) + yield return new object[] { 0.61254732653606592, -0.70710678118654750, DoubleCrossPlatformMachineEpsilon }; // expected: -(1 / sqrt(2)) + yield return new object[] { 0.61850313780157598, -0.69314718055994537, DoubleCrossPlatformMachineEpsilon }; // expected: -(ln(2)) + yield return new object[] { 0.64321824193300488, -0.63661977236758126, DoubleCrossPlatformMachineEpsilon }; // expected: -(2 / pi) + yield return new object[] { 0.74005557395545179, -0.43429448190325190, DoubleCrossPlatformMachineEpsilon }; // expected: -(log10(e)) + yield return new object[] { 0.80200887896145195, -0.31830988618379073, DoubleCrossPlatformMachineEpsilon }; // expected: -(1 / pi) + yield return new object[] { 1, 0.0, 0.0 }; + yield return new object[] { 1.2468689889006383, 0.31830988618379073, DoubleCrossPlatformMachineEpsilon }; // expected: (1 / pi) + yield return new object[] { 1.3512498725672678, 0.43429448190325226, DoubleCrossPlatformMachineEpsilon }; // expected: (log10(e)) + yield return new object[] { 1.5546822754821001, 0.63661977236758126, DoubleCrossPlatformMachineEpsilon }; // expected: (2 / pi) + yield return new object[] { 1.6168066722416747, 0.69314718055994537, DoubleCrossPlatformMachineEpsilon }; // expected: (ln(2)) + yield return new object[] { 1.6325269194381528, 0.70710678118654750, DoubleCrossPlatformMachineEpsilon }; // expected: (1 / sqrt(2)) + yield return new object[] { 1.7235679341273495, 0.78539816339744830, DoubleCrossPlatformMachineEpsilon }; // expected: (pi / 4) + yield return new object[] { 2, 1.0, 0.0 }; // value: (e) + yield return new object[] { 2.1861299583286618, 1.1283791670955128, DoubleCrossPlatformMachineEpsilon * 10 }; // expected: (2 / sqrt(pi)) + yield return new object[] { 2.6651441426902252, 1.4142135623730950, DoubleCrossPlatformMachineEpsilon * 10 }; // expected: (sqrt(2)) + yield return new object[] { 2.7182818284590452, 1.4426950408889632, DoubleCrossPlatformMachineEpsilon * 10 }; // expected: (log2(e)) + yield return new object[] { 2.9706864235520193, 1.5707963267948966, DoubleCrossPlatformMachineEpsilon * 10 }; // expected: (pi / 2) + yield return new object[] { 4.9334096679145963, 2.3025850929940460, DoubleCrossPlatformMachineEpsilon * 10 }; // expected: (ln(10)) + yield return new object[] { 6.5808859910179210, 2.7182818284590455, DoubleCrossPlatformMachineEpsilon * 10 }; // expected: (e) + yield return new object[] { 8.8249778270762876, 3.1415926535897932, DoubleCrossPlatformMachineEpsilon * 10 }; // expected: (pi) + yield return new object[] { double.PositiveInfinity, double.PositiveInfinity, 0.0 }; + } + } + + public static IEnumerable Log2Single + { + get + { + yield return new object[] { float.NegativeInfinity, float.NaN, 0.0f }; + yield return new object[] { -0.113314732f, float.NaN, 0.0f }; + yield return new object[] { -0.0f, float.NegativeInfinity, 0.0f }; + yield return new object[] { float.NaN, float.NaN, 0.0f }; + yield return new object[] { 0.0f, float.NegativeInfinity, 0.0f }; + yield return new object[] { 0.113314732f, -3.14159265f, SingleCrossPlatformMachineEpsilon * 10 }; // expected: -(pi) + yield return new object[] { 0.151955223f, -2.71828200f, SingleCrossPlatformMachineEpsilon * 10 }; // expected: -(e) + yield return new object[] { 0.202699566f, -2.30258509f, SingleCrossPlatformMachineEpsilon * 10 }; // expected: -(ln(10)) + yield return new object[] { 0.336622537f, -1.57079630f, SingleCrossPlatformMachineEpsilon * 10 }; // expected: -(pi / 2) + yield return new object[] { 0.367879441f, -1.44269500f, SingleCrossPlatformMachineEpsilon * 10 }; // expected: -(log2(e)) + yield return new object[] { 0.375214227f, -1.41421360f, SingleCrossPlatformMachineEpsilon * 10 }; // expected: -(sqrt(2)) + yield return new object[] { 0.457429347f, -1.12837910f, SingleCrossPlatformMachineEpsilon * 10 }; // expected: -(2 / sqrt(pi)) + yield return new object[] { 0.5f, -1.0f, 0.0f }; + yield return new object[] { 0.580191810f, -0.785398211f, SingleCrossPlatformMachineEpsilon }; // expected: -(pi / 4) + yield return new object[] { 0.612547327f, -0.707106700f, SingleCrossPlatformMachineEpsilon }; // expected: -(1 / sqrt(2)) + yield return new object[] { 0.618503138f, -0.693147144f, SingleCrossPlatformMachineEpsilon }; // expected: -(ln(2)) + yield return new object[] { 0.643218242f, -0.636619823f, SingleCrossPlatformMachineEpsilon }; // expected: -(2 / pi) + yield return new object[] { 0.740055574f, -0.434294550f, SingleCrossPlatformMachineEpsilon }; // expected: -(log10(e)) + yield return new object[] { 0.802008879f, -0.318309900f, SingleCrossPlatformMachineEpsilon }; // expected: -(1 / pi) + yield return new object[] { 1, 0.0f, 0.0f }; + yield return new object[] { 1.24686899f, 0.318309870f, SingleCrossPlatformMachineEpsilon }; // expected: (1 / pi) + yield return new object[] { 1.35124987f, 0.434294340f, SingleCrossPlatformMachineEpsilon }; // expected: (log10(e)) + yield return new object[] { 1.55468228f, 0.636619823f, SingleCrossPlatformMachineEpsilon }; // expected: (2 / pi) + yield return new object[] { 1.61680667f, 0.693147144f, SingleCrossPlatformMachineEpsilon }; // expected: (ln(2)) + yield return new object[] { 1.63252692f, 0.707106700f, SingleCrossPlatformMachineEpsilon }; // expected: (1 / sqrt(2)) + yield return new object[] { 1.72356793f, 0.785398211f, SingleCrossPlatformMachineEpsilon }; // expected: (pi / 4) + yield return new object[] { 2, 1.0f, 0.0f }; // value: (e) + yield return new object[] { 2.18612996f, 1.12837920f, SingleCrossPlatformMachineEpsilon * 10 }; // expected: (2 / sqrt(pi)) + yield return new object[] { 2.66514414f, 1.41421360f, SingleCrossPlatformMachineEpsilon * 10 }; // expected: (sqrt(2)) + yield return new object[] { 2.71828183f, 1.44269490f, SingleCrossPlatformMachineEpsilon * 10 }; // expected: (log2(e)) + yield return new object[] { 2.97068642f, 1.57079630f, SingleCrossPlatformMachineEpsilon * 10 }; // expected: (pi / 2) + yield return new object[] { 4.93340967f, 2.30258509f, SingleCrossPlatformMachineEpsilon * 10 }; // expected: (ln(10)) + yield return new object[] { 6.58088599f, 2.71828170f, SingleCrossPlatformMachineEpsilon * 10 }; // expected: (e) + yield return new object[] { 8.82497783f, 3.14159265f, SingleCrossPlatformMachineEpsilon * 10 }; // expected: (pi) + yield return new object[] { float.PositiveInfinity, float.PositiveInfinity, 0.0f }; + } + } + } +}