diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorPrimitives.Acosh.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorPrimitives.Acosh.cs index 7313cfe15f2d5d..bbc1f34ed93cdd 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorPrimitives.Acosh.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorPrimitives.Acosh.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Runtime.Intrinsics; namespace System.Numerics.Tensors @@ -26,14 +27,68 @@ public static void Acosh(ReadOnlySpan x, Span destination) InvokeSpanIntoSpan>(x, destination); /// T.Acosh(x) - private readonly struct AcoshOperator : IUnaryOperator + internal readonly struct AcoshOperator : IUnaryOperator where T : IHyperbolicFunctions { - public static bool Vectorizable => false; // TODO: Vectorize + public static bool Vectorizable => +#if NET11_0_OR_GREATER + typeof(T) == typeof(float) || typeof(T) == typeof(double); +#else + false; +#endif + public static T Invoke(T x) => T.Acosh(x); - public static Vector128 Invoke(Vector128 x) => throw new NotSupportedException(); - public static Vector256 Invoke(Vector256 x) => throw new NotSupportedException(); - public static Vector512 Invoke(Vector512 x) => throw new NotSupportedException(); + + public static Vector128 Invoke(Vector128 x) + { +#if NET11_0_OR_GREATER + if (typeof(T) == typeof(double)) + { + return Vector128.Acosh(x.AsDouble()).As(); + } + else + { + Debug.Assert(typeof(T) == typeof(float)); + return Vector128.Acosh(x.AsSingle()).As(); + } +#else + throw new NotSupportedException(); +#endif + } + + public static Vector256 Invoke(Vector256 x) + { +#if NET11_0_OR_GREATER + if (typeof(T) == typeof(double)) + { + return Vector256.Acosh(x.AsDouble()).As(); + } + else + { + Debug.Assert(typeof(T) == typeof(float)); + return Vector256.Acosh(x.AsSingle()).As(); + } +#else + throw new NotSupportedException(); +#endif + } + + public static Vector512 Invoke(Vector512 x) + { +#if NET11_0_OR_GREATER + if (typeof(T) == typeof(double)) + { + return Vector512.Acosh(x.AsDouble()).As(); + } + else + { + Debug.Assert(typeof(T) == typeof(float)); + return Vector512.Acosh(x.AsSingle()).As(); + } +#else + throw new NotSupportedException(); +#endif + } } } } diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitives.Generic.cs b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitives.Generic.cs index 771813177bb19c..16fa3052e9b7b5 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitives.Generic.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitives.Generic.cs @@ -457,7 +457,7 @@ public static IEnumerable SpanDestinationFunctionsToTest() // The current trigonometric algorithm depends on hardware FMA support for best precision. T? trigTolerance = IsFmaSupported ? null : Helpers.DetermineTolerance(doubleTolerance: 1e-10, floatTolerance: 1e-4f); - yield return Create(TensorPrimitives.Acosh, T.Acosh); + yield return Create(TensorPrimitives.Acosh, T.Acosh, Helpers.DetermineTolerance(doubleTolerance: 1e-14, floatTolerance: 1e-6f)); yield return Create(TensorPrimitives.AcosPi, T.AcosPi); yield return Create(TensorPrimitives.Acos, T.Acos); yield return Create(TensorPrimitives.Asinh, T.Asinh); 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 dc20ffbd556bf5..fc14df4cbc01fc 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 @@ -854,6 +854,53 @@ public static Vector128 Asin(Vector128 vector) } } + /// Computes the inverse hyperbolic cosine of each element in a vector. + /// The vector whose inverse hyperbolic cosine is to be computed. + /// A vector whose elements are the inverse hyperbolic cosine of the corresponding elements in . + /// The input should be greater than or equal to 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Acosh(Vector128 vector) + { + if (IsHardwareAccelerated) + { + return VectorMath.AcoshDouble, Vector128, Vector128>(vector); + } + else + { + return Create( + Vector64.Acosh(vector._lower), + Vector64.Acosh(vector._upper) + ); + } + } + + /// Computes the inverse hyperbolic cosine of each element in a vector. + /// The vector whose inverse hyperbolic cosine is to be computed. + /// A vector whose elements are the inverse hyperbolic cosine of the corresponding elements in . + /// The input should be greater than or equal to 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Acosh(Vector128 vector) + { + if (IsHardwareAccelerated) + { + if (Vector256.IsHardwareAccelerated) + { + return VectorMath.AcoshSingle, Vector128, Vector128, Vector256, Vector256, Vector256>(vector); + } + else + { + return VectorMath.AcoshSingle, Vector128, Vector128, Vector128, Vector128, Vector128>(vector); + } + } + else + { + return Create( + Vector64.Acosh(vector._lower), + Vector64.Acosh(vector._upper) + ); + } + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 Cos(Vector128 vector) 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 c51c1f5329ef74..69b699375d73a2 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 @@ -855,6 +855,53 @@ public static Vector256 Asin(Vector256 vector) } } + /// Computes the inverse hyperbolic cosine of each element in a vector. + /// The vector whose inverse hyperbolic cosine is to be computed. + /// A vector whose elements are the inverse hyperbolic cosine of the corresponding elements in . + /// The input should be greater than or equal to 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Acosh(Vector256 vector) + { + if (IsHardwareAccelerated) + { + return VectorMath.AcoshDouble, Vector256, Vector256>(vector); + } + else + { + return Create( + Vector128.Acosh(vector._lower), + Vector128.Acosh(vector._upper) + ); + } + } + + /// Computes the inverse hyperbolic cosine of each element in a vector. + /// The vector whose inverse hyperbolic cosine is to be computed. + /// A vector whose elements are the inverse hyperbolic cosine of the corresponding elements in . + /// The input should be greater than or equal to 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Acosh(Vector256 vector) + { + if (IsHardwareAccelerated) + { + if (Vector512.IsHardwareAccelerated) + { + return VectorMath.AcoshSingle, Vector256, Vector256, Vector512, Vector512, Vector512>(vector); + } + else + { + return VectorMath.AcoshSingle, Vector256, Vector256, Vector256, Vector256, Vector256>(vector); + } + } + else + { + return Create( + Vector128.Acosh(vector._lower), + Vector128.Acosh(vector._upper) + ); + } + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Cos(Vector256 vector) 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 5bc4b5e0964a52..c2267540a49aa5 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 @@ -758,6 +758,46 @@ public static Vector512 Asin(Vector512 vector) } } + /// Computes the inverse hyperbolic cosine of each element in a vector. + /// The vector whose inverse hyperbolic cosine is to be computed. + /// A vector whose elements are the inverse hyperbolic cosine of the corresponding elements in . + /// The input should be greater than or equal to 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Acosh(Vector512 vector) + { + if (IsHardwareAccelerated) + { + return VectorMath.AcoshDouble, Vector512, Vector512>(vector); + } + else + { + return Create( + Vector256.Acosh(vector._lower), + Vector256.Acosh(vector._upper) + ); + } + } + + /// Computes the inverse hyperbolic cosine of each element in a vector. + /// The vector whose inverse hyperbolic cosine is to be computed. + /// A vector whose elements are the inverse hyperbolic cosine of the corresponding elements in . + /// The input should be greater than or equal to 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Acosh(Vector512 vector) + { + if (IsHardwareAccelerated) + { + return VectorMath.AcoshSingle, Vector512, Vector512, Vector512, Vector512, Vector512>(vector); + } + else + { + return Create( + Vector256.Acosh(vector._lower), + Vector256.Acosh(vector._upper) + ); + } + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector512 Cos(Vector512 vector) 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 7077fe391347e6..6382ec6d536f30 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 @@ -819,6 +819,47 @@ public static Vector64 Asin(Vector64 vector) } } + /// Computes the inverse hyperbolic cosine of each element in a vector. + /// The vector whose inverse hyperbolic cosine is to be computed. + /// A vector whose elements are the inverse hyperbolic cosine of the corresponding elements in . + /// The input should be greater than or equal to 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector64 Acosh(Vector64 vector) + { + if (IsHardwareAccelerated) + { + return VectorMath.AcoshDouble, Vector64, Vector64>(vector); + } + else + { + return Acosh(vector); + } + } + + /// Computes the inverse hyperbolic cosine of each element in a vector. + /// The vector whose inverse hyperbolic cosine is to be computed. + /// A vector whose elements are the inverse hyperbolic cosine of the corresponding elements in . + /// The input should be greater than or equal to 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector64 Acosh(Vector64 vector) + { + if (IsHardwareAccelerated) + { + if (Vector128.IsHardwareAccelerated) + { + return VectorMath.AcoshSingle, Vector64, Vector64, Vector128, Vector128, Vector128>(vector); + } + else + { + return VectorMath.AcoshSingle, Vector64, Vector64, Vector64, Vector64, Vector64>(vector); + } + } + else + { + return Acosh(vector); + } + } + /// Computes the cos of each element in a vector. /// The vector that will have its Cos computed. /// A vector whose elements are the cos of the elements in . @@ -3713,6 +3754,20 @@ internal static Vector64 Asin(Vector64 vector) return result; } + internal static Vector64 Acosh(Vector64 vector) + where T : IHyperbolicFunctions + { + Unsafe.SkipInit(out Vector64 result); + + for (int index = 0; index < Vector64.Count; index++) + { + T value = T.Acosh(vector.GetElementUnsafe(index)); + result.SetElementUnsafe(index, value); + } + + return result; + } + internal static Vector64 Sin(Vector64 vector) where T : ITrigonometricFunctions { 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 index bdbe668efa326b..0598aeca7260d9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs @@ -3221,5 +3221,77 @@ private static TVectorDouble AsinSingleCoreDouble(TVectorDouble a return ax + ax * g * poly + (TVectorDouble.Create(PIBY2) & gtHalf); } + + public static TVectorDouble AcoshDouble(TVectorDouble x) + where TVectorDouble : unmanaged, ISimdVector + where TVectorInt64 : unmanaged, ISimdVector + where TVectorUInt64 : unmanaged, ISimdVector + { + // The AMD AOCL-LibM scalar acosh implementation (acosh.c) uses range-based + // polynomial lookup tables which cannot be trivially vectorized due to the cost + // of gather instructions. Instead, this uses the mathematical identity: + // acosh(x) = log(x + sqrt((x - 1) * (x + 1))) + // using (x-1)*(x+1) instead of x^2-1 to avoid catastrophic cancellation near x=1, + // with special handling for large x for improved accuracy. + + const double LN2 = 0.693147180559945309417; + const double LARGE_THRESHOLD = 268435456.0; // 2^28 + + // Return NaN for x < 1 + TVectorDouble nanMask = TVectorDouble.LessThan(x, TVectorDouble.One); + + // For large values (x > 2^28), use log(2) + log(x) + TVectorDouble largeMask = TVectorDouble.GreaterThan(x, TVectorDouble.Create(LARGE_THRESHOLD)); + + // Normal case: log(x + sqrt((x - 1) * (x + 1))) + // Using (x-1)*(x+1) avoids catastrophic cancellation when x is near 1 + TVectorDouble xm1 = x - TVectorDouble.One; + TVectorDouble xp1 = x + TVectorDouble.One; + TVectorDouble normal = LogDouble(x + TVectorDouble.Sqrt(xm1 * xp1)); + + // Large value case: log(2) + log(x) + TVectorDouble large = TVectorDouble.Create(LN2) + LogDouble(x); + + // Select appropriate result based on magnitude + TVectorDouble result = TVectorDouble.ConditionalSelect(largeMask, large, normal); + result = TVectorDouble.ConditionalSelect(nanMask, TVectorDouble.Create(double.NaN), result); + + return result; + } + + public static TVectorSingle AcoshSingle(TVectorSingle x) + where TVectorSingle : unmanaged, ISimdVector + where TVectorInt32 : unmanaged, ISimdVector + where TVectorUInt32 : unmanaged, ISimdVector + where TVectorDouble : unmanaged, ISimdVector + where TVectorInt64 : unmanaged, ISimdVector + where TVectorUInt64 : unmanaged, ISimdVector + { + // This code is based on `acoshf` from amd/aocl-libm-ose + // Copyright (C) 2008-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 + + // This implementation computes single-precision acosh by widening the + // input to double precision, calling AcoshDouble, and then narrowing + // the result back to single precision. AcoshDouble uses mathematical + // identities (no polynomial approximation) for improved accuracy. + + if (TVectorSingle.ElementCount == TVectorDouble.ElementCount) + { + TVectorDouble dx = Widen(x); + return Narrow(AcoshDouble(dx)); + } + else + { + TVectorDouble dxLo = WidenLower(x); + TVectorDouble dxHi = WidenUpper(x); + return Narrow( + AcoshDouble(dxLo), + AcoshDouble(dxHi) + ); + } + } } } 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 ada0999ed91e96..6c891ddd5f22cc 100644 --- a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs +++ b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs @@ -19,6 +19,8 @@ public static partial class Vector128 public static System.Runtime.Intrinsics.Vector128 AndNot(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static bool Any(System.Runtime.Intrinsics.Vector128 vector, T value) { throw null; } public static bool AnyWhereAllBitsSet(System.Runtime.Intrinsics.Vector128 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector128 Acosh(System.Runtime.Intrinsics.Vector128 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector128 Acosh(System.Runtime.Intrinsics.Vector128 vector) { throw null; } public static System.Runtime.Intrinsics.Vector128 Asin(System.Runtime.Intrinsics.Vector128 vector) { throw null; } public static System.Runtime.Intrinsics.Vector128 Asin(System.Runtime.Intrinsics.Vector128 vector) { throw null; } public static System.Runtime.Intrinsics.Vector128 AsByte(this System.Runtime.Intrinsics.Vector128 vector) { throw null; } @@ -482,6 +484,8 @@ public static partial class Vector256 public static System.Runtime.Intrinsics.Vector256 AndNot(System.Runtime.Intrinsics.Vector256 left, System.Runtime.Intrinsics.Vector256 right) { throw null; } public static bool Any(System.Runtime.Intrinsics.Vector256 vector, T value) { throw null; } public static bool AnyWhereAllBitsSet(System.Runtime.Intrinsics.Vector256 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector256 Acosh(System.Runtime.Intrinsics.Vector256 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector256 Acosh(System.Runtime.Intrinsics.Vector256 vector) { throw null; } public static System.Runtime.Intrinsics.Vector256 Asin(System.Runtime.Intrinsics.Vector256 vector) { throw null; } public static System.Runtime.Intrinsics.Vector256 Asin(System.Runtime.Intrinsics.Vector256 vector) { throw null; } public static System.Runtime.Intrinsics.Vector256 AsByte(this System.Runtime.Intrinsics.Vector256 vector) { throw null; } @@ -934,6 +938,8 @@ public static partial class Vector512 public static System.Runtime.Intrinsics.Vector512 AndNot(System.Runtime.Intrinsics.Vector512 left, System.Runtime.Intrinsics.Vector512 right) { throw null; } public static bool Any(System.Runtime.Intrinsics.Vector512 vector, T value) { throw null; } public static bool AnyWhereAllBitsSet(System.Runtime.Intrinsics.Vector512 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector512 Acosh(System.Runtime.Intrinsics.Vector512 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector512 Acosh(System.Runtime.Intrinsics.Vector512 vector) { throw null; } public static System.Runtime.Intrinsics.Vector512 Asin(System.Runtime.Intrinsics.Vector512 vector) { throw null; } public static System.Runtime.Intrinsics.Vector512 Asin(System.Runtime.Intrinsics.Vector512 vector) { throw null; } public static System.Runtime.Intrinsics.Vector512 AsByte(this System.Runtime.Intrinsics.Vector512 vector) { throw null; } @@ -1385,6 +1391,8 @@ public static partial class Vector64 public static System.Runtime.Intrinsics.Vector64 AndNot(System.Runtime.Intrinsics.Vector64 left, System.Runtime.Intrinsics.Vector64 right) { throw null; } public static bool Any(System.Runtime.Intrinsics.Vector64 vector, T value) { throw null; } public static bool AnyWhereAllBitsSet(System.Runtime.Intrinsics.Vector64 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector64 Acosh(System.Runtime.Intrinsics.Vector64 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector64 Acosh(System.Runtime.Intrinsics.Vector64 vector) { throw null; } public static System.Runtime.Intrinsics.Vector64 Asin(System.Runtime.Intrinsics.Vector64 vector) { throw null; } public static System.Runtime.Intrinsics.Vector64 Asin(System.Runtime.Intrinsics.Vector64 vector) { throw null; } public static System.Runtime.Intrinsics.Vector64 AsByte(this System.Runtime.Intrinsics.Vector64 vector) { throw null; }