From f1bcae0a23271725611ce207d205cd6c9db095f7 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 3 Jun 2021 20:45:52 +0800 Subject: [PATCH 01/15] SinPi --- .../System.Private.CoreLib/src/System/Math.cs | 54 +++++++++++++++++++ .../src/System/MathF.cs | 54 +++++++++++++++++++ .../tests/System/Math.cs | 36 +++++++++++++ .../System.Runtime/ref/System.Runtime.cs | 2 + 4 files changed, 146 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index c215d7a68ef259..050605f90df871 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -1449,5 +1449,59 @@ public static double ScaleB(double x, int n) double u = BitConverter.Int64BitsToDouble(((long)(0x3ff + n) << 52)); return y * u; } + + /// + /// Returns the sine of the specified angle measured in half-turns. + /// + /// An angle, measured in half-turns. + /// The sine of . If is equal to , , + /// or , this method returns . + /// + /// This method is effectively Sin(x * PI), with higher precision. + /// It guarantees to return -1, 0, or 1 when is integer or half-integer. + /// + public static double SinPi(double x) + { + // Implementation based on https://github.com/boostorg/math/blob/develop/include/boost/math/special_functions/sin_pi.hpp + + if (x < 0) + { + return -SinPi(-x); + } + + bool invert; + if (x < 0.5) + { + return Sin(x * PI); + } + if (x < 1) + { + invert = true; + x = -x; + } + else + { + invert = false; + } + + double floor = Floor(x); + if (((int)floor & 1) != 0) + { + invert = !invert; + } + + double rem = x - floor; + if (rem > 0.5) + { + rem = 1 - rem; + } + else if (rem == 0.5) + { + return invert ? -1 : 1; + } + + double sin = Sin(rem * PI); + return invert ? -sin : sin; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index 0050f5d43368cc..cb6e04d6d379b2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -519,5 +519,59 @@ public static float ScaleB(float x, int n) float u = BitConverter.Int32BitsToSingle(((int)(0x7f + n) << 23)); return y * u; } + + /// + /// Returns the sine of the specified angle measured in half-turns. + /// + /// An angle, measured in half-turns. + /// The sine of . If is equal to , , + /// or , this method returns . + /// + /// This method is effectively Sin(x * PI), with higher precision. + /// It guarantees to return -1, 0, or 1 when is integer or half-integer. + /// + public static float SinPi(float x) + { + // Implementation based on https://github.com/boostorg/math/blob/develop/include/boost/math/special_functions/sin_pi.hpp + + if (x < 0) + { + return -SinPi(-x); + } + + bool invert; + if (x < 0.5f) + { + return Sin(x * PI); + } + if (x < 1) + { + invert = true; + x = -x; + } + else + { + invert = false; + } + + float floor = Floor(x); + if (((int)floor & 1) != 0) + { + invert = !invert; + } + + float rem = x - floor; + if (rem > 0.5f) + { + rem = 1 - rem; + } + else if (rem == 0.5f) + { + return invert ? -1 : 1; + } + + float sin = Sin(rem * PI); + return invert ? -sin : sin; + } } } diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Math.cs b/src/libraries/System.Runtime.Extensions/tests/System/Math.cs index fbe995267afea0..dd5eecc84df318 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Math.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Math.cs @@ -3176,5 +3176,41 @@ public static void Round_Float_Constant_Arg() Assert.Equal( 4, MathF.Round( 3.5f, MidpointRounding.AwayFromZero)); Assert.Equal(-4, MathF.Round(-3.5f, MidpointRounding.AwayFromZero)); } + + [Fact] + public static void SinPi_Double_Precision() + { + Assert.Equal( 0, Math.SinPi( 0.0)); + Assert.Equal( 1, Math.SinPi( 0.5)); + Assert.Equal(-1, Math.SinPi(-0.5)); + Assert.Equal( 0, Math.SinPi( 1.0)); + Assert.Equal( 0, Math.SinPi(-1.0)); + Assert.Equal(-1, Math.SinPi( 1.5)); + Assert.Equal( 1, Math.SinPi(-1.5)); + Assert.Equal( 0, Math.SinPi( 2.0)); + Assert.Equal( 0, Math.SinPi(-2.0)); + + Assert.Equal(0, Math.SinPi(0.1234) + Math.SinPi(-0.1234)); + Assert.Equal(0, Math.SinPi(0.6789) + Math.SinPi(-0.6789)); + Assert.Equal(0, Math.SinPi(1.2345) + Math.SinPi(-1.2345)); + } + + [Fact] + public static void SinPi_Float_Precision() + { + Assert.Equal( 0, MathF.SinPi( 0.0f)); + Assert.Equal( 1, MathF.SinPi( 0.5f)); + Assert.Equal(-1, MathF.SinPi(-0.5f)); + Assert.Equal( 0, MathF.SinPi( 1.0f)); + Assert.Equal( 0, MathF.SinPi(-1.0f)); + Assert.Equal(-1, MathF.SinPi( 1.5f)); + Assert.Equal( 1, MathF.SinPi(-1.5f)); + Assert.Equal( 0, MathF.SinPi( 2.0f)); + Assert.Equal( 0, MathF.SinPi(-2.0f)); + + Assert.Equal(0, MathF.SinPi(0.1234f) + MathF.SinPi(-0.1234f)); + Assert.Equal(0, MathF.SinPi(0.6789f) + MathF.SinPi(-0.6789f)); + Assert.Equal(0, MathF.SinPi(1.2345f) + MathF.SinPi(-1.2345f)); + } } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index ba30ccf66a0aa9..721c185cd96b0d 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -2873,6 +2873,7 @@ public static partial class Math public static int Sign(sbyte value) { throw null; } public static int Sign(float value) { throw null; } public static double Sin(double a) { throw null; } + public static double SinPi(double x) { throw null; } public static (double Sin, double Cos) SinCos(double x) { throw null; } public static double Sinh(double value) { throw null; } public static double Sqrt(double d) { throw null; } @@ -2924,6 +2925,7 @@ public static partial class MathF public static float ScaleB(float x, int n) { throw null; } public static int Sign(float x) { throw null; } public static float Sin(float x) { throw null; } + public static float SinPi(float x) { throw null; } public static (float Sin, float Cos) SinCos(float x) { throw null; } public static float Sinh(float x) { throw null; } public static float Sqrt(float x) { throw null; } From dd97080f0d55e3ed151bb06a04e98424dfe9ff41 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 3 Jun 2021 21:22:12 +0800 Subject: [PATCH 02/15] CosPi --- .../System.Private.CoreLib/src/System/Math.cs | 46 +++++++++++++++++++ .../src/System/MathF.cs | 46 +++++++++++++++++++ .../tests/System/Math.cs | 36 +++++++++++++++ .../System.Runtime/ref/System.Runtime.cs | 6 ++- 4 files changed, 132 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index 050605f90df871..20cad1205612a7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -1503,5 +1503,51 @@ public static double SinPi(double x) double sin = Sin(rem * PI); return invert ? -sin : sin; } + + /// + /// Returns the cosine of the specified angle measured in half-turns. + /// + /// An angle, measured in half-turns. + /// The cosine of . If is equal to , , + /// or , this method returns . + /// + /// This method is effectively Cos(x * PI), with higher precision. + /// It guarantees to return -1, 0, or 1 when is integer or half-integer. + /// + public static double CosPi(double x) + { + // Implementation based on https://github.com/boostorg/math/blob/develop/include/boost/math/special_functions/cos_pi.hpp + + if (Abs(x) < 0.25) + { + return Cos(x * PI); + } + + if (x < 0) + { + x = -x; + } + + bool invert = false; + double floor = Floor(x); + if (((int)floor & 1) != 0) + { + invert = !invert; + } + + double rem = x - floor; + if (rem > 0.5) + { + rem = 1 - rem; + invert = !invert; + } + else if (rem == 0.5) + { + return 0; + } + + double cos = rem > 0.25 ? Cos(0.5 - rem) : Cos(rem); + return invert ? -cos : cos; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index cb6e04d6d379b2..c06fa246ee1eda 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -573,5 +573,51 @@ public static float SinPi(float x) float sin = Sin(rem * PI); return invert ? -sin : sin; } + + /// + /// Returns the cosine of the specified angle measured in half-turns. + /// + /// An angle, measured in half-turns. + /// The cosine of . If is equal to , , + /// or , this method returns . + /// + /// This method is effectively Cos(x * PI), with higher precision. + /// It guarantees to return -1, 0, or 1 when is integer or half-integer. + /// + public static float CosPi(float x) + { + // Implementation based on https://github.com/boostorg/math/blob/develop/include/boost/math/special_functions/cos_pi.hpp + + if (Abs(x) < 0.25f) + { + return Cos(x * PI); + } + + if (x < 0) + { + x = -x; + } + + bool invert = false; + float floor = Floor(x); + if (((int)floor & 1) != 0) + { + invert = !invert; + } + + float rem = x - floor; + if (rem > 0.5f) + { + rem = 1 - rem; + invert = !invert; + } + else if (rem == 0.5f) + { + return 0; + } + + float cos = rem > 0.25f ? Cos(0.5f - rem) : Cos(rem); + return invert ? -cos : cos; + } } } diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Math.cs b/src/libraries/System.Runtime.Extensions/tests/System/Math.cs index dd5eecc84df318..ea35a5f006e534 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Math.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Math.cs @@ -3212,5 +3212,41 @@ public static void SinPi_Float_Precision() Assert.Equal(0, MathF.SinPi(0.6789f) + MathF.SinPi(-0.6789f)); Assert.Equal(0, MathF.SinPi(1.2345f) + MathF.SinPi(-1.2345f)); } + + [Fact] + public static void CosPi_Double_Precision() + { + Assert.Equal( 1, Math.CosPi( 0.0)); + Assert.Equal( 0, Math.CosPi( 0.5)); + Assert.Equal( 0, Math.CosPi(-0.5)); + Assert.Equal(-1, Math.CosPi( 1.0)); + Assert.Equal(-1, Math.CosPi(-1.0)); + Assert.Equal( 0, Math.CosPi( 1.5)); + Assert.Equal( 0, Math.CosPi(-1.5)); + Assert.Equal( 1, Math.CosPi( 2.0)); + Assert.Equal( 1, Math.CosPi(-2.0)); + + Assert.Equal(0, Math.CosPi(0.1234) - Math.CosPi(-0.1234)); + Assert.Equal(0, Math.CosPi(0.6789) - Math.CosPi(-0.6789)); + Assert.Equal(0, Math.CosPi(1.2345) - Math.CosPi(-1.2345)); + } + + [Fact] + public static void CosPi_Float_Precision() + { + Assert.Equal( 1, MathF.CosPi( 0.0f)); + Assert.Equal( 0, MathF.CosPi( 0.5f)); + Assert.Equal( 0, MathF.CosPi(-0.5f)); + Assert.Equal(-1, MathF.CosPi( 1.0f)); + Assert.Equal(-1, MathF.CosPi(-1.0f)); + Assert.Equal( 0, MathF.CosPi( 1.5f)); + Assert.Equal( 0, MathF.CosPi(-1.5f)); + Assert.Equal( 1, MathF.CosPi( 2.0f)); + Assert.Equal( 1, MathF.CosPi(-2.0f)); + + Assert.Equal(0, MathF.CosPi(0.1234f) - MathF.CosPi(-0.1234f)); + Assert.Equal(0, MathF.CosPi(0.6789f) - MathF.CosPi(-0.6789f)); + Assert.Equal(0, MathF.CosPi(1.2345f) - MathF.CosPi(-1.2345f)); + } } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 721c185cd96b0d..a0f276707ae246 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -2786,6 +2786,7 @@ public static partial class Math public static double CopySign(double x, double y) { throw null; } public static double Cos(double d) { throw null; } public static double Cosh(double value) { throw null; } + public static double CosPi(double x) { throw null; } public static int DivRem(int a, int b, out int result) { throw null; } public static long DivRem(long a, long b, out long result) { throw null; } public static (byte Quotient, byte Remainder) DivRem(byte left, byte right) { throw null; } @@ -2873,9 +2874,9 @@ public static partial class Math public static int Sign(sbyte value) { throw null; } public static int Sign(float value) { throw null; } public static double Sin(double a) { throw null; } - public static double SinPi(double x) { throw null; } public static (double Sin, double Cos) SinCos(double x) { throw null; } public static double Sinh(double value) { throw null; } + public static double SinPi(double x) { throw null; } public static double Sqrt(double d) { throw null; } public static double Tan(double a) { throw null; } public static double Tanh(double value) { throw null; } @@ -2902,6 +2903,7 @@ public static partial class MathF public static float CopySign(float x, float y) { throw null; } public static float Cos(float x) { throw null; } public static float Cosh(float x) { throw null; } + public static float CosPi(float x) { throw null; } public static float Exp(float x) { throw null; } public static float Floor(float x) { throw null; } public static float FusedMultiplyAdd(float x, float y, float z) { throw null; } @@ -2925,9 +2927,9 @@ public static partial class MathF public static float ScaleB(float x, int n) { throw null; } public static int Sign(float x) { throw null; } public static float Sin(float x) { throw null; } - public static float SinPi(float x) { throw null; } public static (float Sin, float Cos) SinCos(float x) { throw null; } public static float Sinh(float x) { throw null; } + public static float SinPi(float x) { throw null; } public static float Sqrt(float x) { throw null; } public static float Tan(float x) { throw null; } public static float Tanh(float x) { throw null; } From 800455947fa86202224b173f664ca0ce9942b526 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 3 Jun 2021 21:40:11 +0800 Subject: [PATCH 03/15] TanPi --- .../System.Private.CoreLib/src/System/Math.cs | 12 ++++++++ .../src/System/MathF.cs | 12 ++++++++ .../tests/System/Math.cs | 28 +++++++++++++++++++ .../System.Runtime/ref/System.Runtime.cs | 2 ++ 4 files changed, 54 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index 20cad1205612a7..022c40b4e39baf 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -1549,5 +1549,17 @@ public static double CosPi(double x) double cos = rem > 0.25 ? Cos(0.5 - rem) : Cos(rem); return invert ? -cos : cos; } + + /// + /// Returns the tangent of the specified angle measured in half-turns. + /// + /// An angle, measured in half-turns. + /// The tangent of . If is equal to , , + /// or , this method returns . + /// + /// This method is effectively Tan(x * PI), with higher precision. + /// It guarantees to return 0, or when is integer or half-integer. + /// + public static double TanPi(double x) => SinPi(x) / CosPi(x); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index c06fa246ee1eda..79bfd402c0be10 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -619,5 +619,17 @@ public static float CosPi(float x) float cos = rem > 0.25f ? Cos(0.5f - rem) : Cos(rem); return invert ? -cos : cos; } + + /// + /// Returns the tangent of the specified angle measured in half-turns. + /// + /// An angle, measured in half-turns. + /// The tangent of . If is equal to , , + /// or , this method returns . + /// + /// This method is effectively Tan(x * PI), with higher precision. + /// It guarantees to return 0, or when is integer or half-integer. + /// + public static float TanPi(float x) => SinPi(x) / CosPi(x); } } diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Math.cs b/src/libraries/System.Runtime.Extensions/tests/System/Math.cs index ea35a5f006e534..c92262f113224d 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Math.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Math.cs @@ -3248,5 +3248,33 @@ public static void CosPi_Float_Precision() Assert.Equal(0, MathF.CosPi(0.6789f) - MathF.CosPi(-0.6789f)); Assert.Equal(0, MathF.CosPi(1.2345f) - MathF.CosPi(-1.2345f)); } + + [Fact] + public static void TanPi_Double_Precision() + { + Assert.Equal( 0, Math.TanPi( 0.0)); + Assert.Equal(double.PositiveInfinity, Math.TanPi( 0.5)); + Assert.Equal(double.NegativeInfinity, Math.TanPi(-0.5)); + Assert.Equal( 0, Math.TanPi( 1.0)); + Assert.Equal( 0, Math.TanPi(-1.0)); + Assert.Equal(double.NegativeInfinity, Math.TanPi( 1.5)); + Assert.Equal(double.PositiveInfinity, Math.TanPi(-1.5)); + Assert.Equal( 0, Math.TanPi( 2.0)); + Assert.Equal( 0, Math.TanPi(-2.0)); + } + + [Fact] + public static void TanPi_Float_Precision() + { + Assert.Equal( 0, MathF.TanPi( 0.0f)); + Assert.Equal(float.PositiveInfinity, MathF.TanPi( 0.5f)); + Assert.Equal(float.NegativeInfinity, MathF.TanPi(-0.5f)); + Assert.Equal( 0, MathF.TanPi( 1.0f)); + Assert.Equal( 0, MathF.TanPi(-1.0f)); + Assert.Equal(float.NegativeInfinity, MathF.TanPi( 1.5f)); + Assert.Equal(float.PositiveInfinity, MathF.TanPi(-1.5f)); + Assert.Equal( 0, MathF.TanPi( 2.0f)); + Assert.Equal( 0, MathF.TanPi(-2.0f)); + } } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index a0f276707ae246..6e89d500c4e975 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -2880,6 +2880,7 @@ public static partial class Math public static double Sqrt(double d) { throw null; } public static double Tan(double a) { throw null; } public static double Tanh(double value) { throw null; } + public static double TanPi(double x) { throw null; } public static decimal Truncate(decimal d) { throw null; } public static double Truncate(double d) { throw null; } } @@ -2933,6 +2934,7 @@ public static partial class MathF public static float Sqrt(float x) { throw null; } public static float Tan(float x) { throw null; } public static float Tanh(float x) { throw null; } + public static float TanPi(float x) { throw null; } public static float Truncate(float x) { throw null; } } public partial class MemberAccessException : System.SystemException From 4e9532fbdb5349840f0b4ea8ea775263e64f7b01 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 4 Jun 2021 20:24:53 +0800 Subject: [PATCH 04/15] Test IEEE compliance of +0 and -0 --- .../tests/System/Math.cs | 148 ++++++++++-------- 1 file changed, 81 insertions(+), 67 deletions(-) diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Math.cs b/src/libraries/System.Runtime.Extensions/tests/System/Math.cs index c92262f113224d..3f82724dce828f 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Math.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Math.cs @@ -3180,101 +3180,115 @@ public static void Round_Float_Constant_Arg() [Fact] public static void SinPi_Double_Precision() { - Assert.Equal( 0, Math.SinPi( 0.0)); - Assert.Equal( 1, Math.SinPi( 0.5)); - Assert.Equal(-1, Math.SinPi(-0.5)); - Assert.Equal( 0, Math.SinPi( 1.0)); - Assert.Equal( 0, Math.SinPi(-1.0)); - Assert.Equal(-1, Math.SinPi( 1.5)); - Assert.Equal( 1, Math.SinPi(-1.5)); - Assert.Equal( 0, Math.SinPi( 2.0)); - Assert.Equal( 0, Math.SinPi(-2.0)); - - Assert.Equal(0, Math.SinPi(0.1234) + Math.SinPi(-0.1234)); - Assert.Equal(0, Math.SinPi(0.6789) + Math.SinPi(-0.6789)); - Assert.Equal(0, Math.SinPi(1.2345) + Math.SinPi(-1.2345)); + AssertEqual( 0.0, Math.SinPi( 0.0), 0.0); + AssertEqual(-0.0, Math.SinPi(-0.0), 0.0); + AssertEqual( 1.0, Math.SinPi( 0.5), 0.0); + AssertEqual(-1.0, Math.SinPi(-0.5), 0.0); + AssertEqual( 0.0, Math.SinPi( 1.0), 0.0); + AssertEqual(-0.0, Math.SinPi(-1.0), 0.0); + AssertEqual(-1.0, Math.SinPi( 1.5), 0.0); + AssertEqual( 1.0, Math.SinPi(-1.5), 0.0); + AssertEqual( 0.0, Math.SinPi( 2.0), 0.0); + AssertEqual(-0.0, Math.SinPi(-2.0), 0.0); + + AssertEqual(0.0, Math.SinPi(0.1234) + Math.SinPi(-0.1234), 0.0); + AssertEqual(0.0, Math.SinPi(0.6789) + Math.SinPi(-0.6789), 0.0); + AssertEqual(0.0, Math.SinPi(1.2345) + Math.SinPi(-1.2345), 0.0); } [Fact] public static void SinPi_Float_Precision() { - Assert.Equal( 0, MathF.SinPi( 0.0f)); - Assert.Equal( 1, MathF.SinPi( 0.5f)); - Assert.Equal(-1, MathF.SinPi(-0.5f)); - Assert.Equal( 0, MathF.SinPi( 1.0f)); - Assert.Equal( 0, MathF.SinPi(-1.0f)); - Assert.Equal(-1, MathF.SinPi( 1.5f)); - Assert.Equal( 1, MathF.SinPi(-1.5f)); - Assert.Equal( 0, MathF.SinPi( 2.0f)); - Assert.Equal( 0, MathF.SinPi(-2.0f)); + AssertEqual( 0.0f, MathF.SinPi( 0.0f), 0.0f); + AssertEqual(-0.0f, MathF.SinPi(-0.0f), 0.0f); + AssertEqual( 1.0f, MathF.SinPi( 0.5f), 0.0f); + AssertEqual(-1.0f, MathF.SinPi(-0.5f), 0.0f); + AssertEqual( 0.0f, MathF.SinPi( 1.0f), 0.0f); + AssertEqual(-0.0f, MathF.SinPi(-1.0f), 0.0f); + AssertEqual(-1.0f, MathF.SinPi( 1.5f), 0.0f); + AssertEqual( 1.0f, MathF.SinPi(-1.5f), 0.0f); + AssertEqual( 0.0f, MathF.SinPi( 2.0f), 0.0f); + AssertEqual(-0.0f, MathF.SinPi(-2.0f), 0.0f); - Assert.Equal(0, MathF.SinPi(0.1234f) + MathF.SinPi(-0.1234f)); - Assert.Equal(0, MathF.SinPi(0.6789f) + MathF.SinPi(-0.6789f)); - Assert.Equal(0, MathF.SinPi(1.2345f) + MathF.SinPi(-1.2345f)); + AssertEqual(0.0f, MathF.SinPi(0.1234f) + MathF.SinPi(-0.1234f), 0.0f); + AssertEqual(0.0f, MathF.SinPi(0.6789f) + MathF.SinPi(-0.6789f), 0.0f); + AssertEqual(0.0f, MathF.SinPi(1.2345f) + MathF.SinPi(-1.2345f), 0.0f); } [Fact] public static void CosPi_Double_Precision() { - Assert.Equal( 1, Math.CosPi( 0.0)); - Assert.Equal( 0, Math.CosPi( 0.5)); - Assert.Equal( 0, Math.CosPi(-0.5)); - Assert.Equal(-1, Math.CosPi( 1.0)); - Assert.Equal(-1, Math.CosPi(-1.0)); - Assert.Equal( 0, Math.CosPi( 1.5)); - Assert.Equal( 0, Math.CosPi(-1.5)); - Assert.Equal( 1, Math.CosPi( 2.0)); - Assert.Equal( 1, Math.CosPi(-2.0)); + AssertEqual( 1.0, Math.CosPi( 0.0), 0.0); + AssertEqual( 1.0, Math.CosPi(-0.0), 0.0); + AssertEqual( 0.0, Math.CosPi( 0.5), 0.0); + AssertEqual( 0.0, Math.CosPi(-0.5), 0.0); + AssertEqual(-1.0, Math.CosPi( 1.0), 0.0); + AssertEqual(-1.0, Math.CosPi(-1.0), 0.0); + AssertEqual( 0.0, Math.CosPi( 1.5), 0.0); + AssertEqual( 0.0, Math.CosPi(-1.5), 0.0); + AssertEqual( 1.0, Math.CosPi( 2.0), 0.0); + AssertEqual( 1.0, Math.CosPi(-2.0), 0.0); - Assert.Equal(0, Math.CosPi(0.1234) - Math.CosPi(-0.1234)); - Assert.Equal(0, Math.CosPi(0.6789) - Math.CosPi(-0.6789)); - Assert.Equal(0, Math.CosPi(1.2345) - Math.CosPi(-1.2345)); + AssertEqual(0.0, Math.CosPi(0.1234) - Math.CosPi(-0.1234), 0.0); + AssertEqual(0.0, Math.CosPi(0.6789) - Math.CosPi(-0.6789), 0.0); + AssertEqual(0.0, Math.CosPi(1.2345) - Math.CosPi(-1.2345), 0.0); } [Fact] public static void CosPi_Float_Precision() { - Assert.Equal( 1, MathF.CosPi( 0.0f)); - Assert.Equal( 0, MathF.CosPi( 0.5f)); - Assert.Equal( 0, MathF.CosPi(-0.5f)); - Assert.Equal(-1, MathF.CosPi( 1.0f)); - Assert.Equal(-1, MathF.CosPi(-1.0f)); - Assert.Equal( 0, MathF.CosPi( 1.5f)); - Assert.Equal( 0, MathF.CosPi(-1.5f)); - Assert.Equal( 1, MathF.CosPi( 2.0f)); - Assert.Equal( 1, MathF.CosPi(-2.0f)); + AssertEqual( 1.0f, MathF.CosPi( 0.0f), 0.0f); + AssertEqual( 1.0f, MathF.CosPi(-0.0f), 0.0f); + AssertEqual( 0.0f, MathF.CosPi( 0.5f), 0.0f); + AssertEqual( 0.0f, MathF.CosPi(-0.5f), 0.0f); + AssertEqual(-1.0f, MathF.CosPi( 1.0f), 0.0f); + AssertEqual(-1.0f, MathF.CosPi(-1.0f), 0.0f); + AssertEqual( 0.0f, MathF.CosPi( 1.5f), 0.0f); + AssertEqual( 0.0f, MathF.CosPi(-1.5f), 0.0f); + AssertEqual( 1.0f, MathF.CosPi( 2.0f), 0.0f); + AssertEqual( 1.0f, MathF.CosPi(-2.0f), 0.0f); - Assert.Equal(0, MathF.CosPi(0.1234f) - MathF.CosPi(-0.1234f)); - Assert.Equal(0, MathF.CosPi(0.6789f) - MathF.CosPi(-0.6789f)); - Assert.Equal(0, MathF.CosPi(1.2345f) - MathF.CosPi(-1.2345f)); + AssertEqual(0.0f, MathF.CosPi(0.1234f) - MathF.CosPi(-0.1234f), 0.0f); + AssertEqual(0.0f, MathF.CosPi(0.6789f) - MathF.CosPi(-0.6789f), 0.0f); + AssertEqual(0.0f, MathF.CosPi(1.2345f) - MathF.CosPi(-1.2345f), 0.0f); } [Fact] public static void TanPi_Double_Precision() { - Assert.Equal( 0, Math.TanPi( 0.0)); - Assert.Equal(double.PositiveInfinity, Math.TanPi( 0.5)); - Assert.Equal(double.NegativeInfinity, Math.TanPi(-0.5)); - Assert.Equal( 0, Math.TanPi( 1.0)); - Assert.Equal( 0, Math.TanPi(-1.0)); - Assert.Equal(double.NegativeInfinity, Math.TanPi( 1.5)); - Assert.Equal(double.PositiveInfinity, Math.TanPi(-1.5)); - Assert.Equal( 0, Math.TanPi( 2.0)); - Assert.Equal( 0, Math.TanPi(-2.0)); + AssertEqual( 0.0, Math.TanPi( 0.0), 0.0); + AssertEqual( -0.0, Math.TanPi(-0.0), 0.0); + AssertEqual(double.PositiveInfinity, Math.TanPi( 0.5), 0.0); + AssertEqual(double.NegativeInfinity, Math.TanPi(-0.5), 0.0); + AssertEqual( -0.0, Math.TanPi( 1.0), 0.0); + AssertEqual( 0.0, Math.TanPi(-1.0), 0.0); + AssertEqual(double.NegativeInfinity, Math.TanPi( 1.5), 0.0); + AssertEqual(double.PositiveInfinity, Math.TanPi(-1.5), 0.0); + AssertEqual( 0.0, Math.TanPi( 2.0), 0.0); + AssertEqual( -0.0, Math.TanPi(-2.0), 0.0); + + AssertEqual(0.0, Math.TanPi(0.1234) + Math.TanPi(-0.1234), 0.0); + AssertEqual(0.0, Math.TanPi(0.6789) + Math.TanPi(-0.6789), 0.0); + AssertEqual(0.0, Math.TanPi(1.2345) + Math.TanPi(-1.2345), 0.0); } [Fact] public static void TanPi_Float_Precision() { - Assert.Equal( 0, MathF.TanPi( 0.0f)); - Assert.Equal(float.PositiveInfinity, MathF.TanPi( 0.5f)); - Assert.Equal(float.NegativeInfinity, MathF.TanPi(-0.5f)); - Assert.Equal( 0, MathF.TanPi( 1.0f)); - Assert.Equal( 0, MathF.TanPi(-1.0f)); - Assert.Equal(float.NegativeInfinity, MathF.TanPi( 1.5f)); - Assert.Equal(float.PositiveInfinity, MathF.TanPi(-1.5f)); - Assert.Equal( 0, MathF.TanPi( 2.0f)); - Assert.Equal( 0, MathF.TanPi(-2.0f)); + AssertEqual( 0.0f, MathF.TanPi( 0.0f), 0.0f); + AssertEqual( -0.0f, MathF.TanPi(-0.0f), 0.0f); + AssertEqual(float.PositiveInfinity, MathF.TanPi( 0.5f), 0.0f); + AssertEqual(float.NegativeInfinity, MathF.TanPi(-0.5f), 0.0f); + AssertEqual( -0.0f, MathF.TanPi( 1.0f), 0.0f); + AssertEqual( 0.0f, MathF.TanPi(-1.0f), 0.0f); + AssertEqual(float.NegativeInfinity, MathF.TanPi( 1.5f), 0.0f); + AssertEqual(float.PositiveInfinity, MathF.TanPi(-1.5f), 0.0f); + AssertEqual( 0.0f, MathF.TanPi( 2.0f), 0.0f); + AssertEqual( -0.0f, MathF.TanPi(-2.0f), 0.0f); + + AssertEqual(0.0f, MathF.TanPi(0.1234f) + MathF.TanPi(-0.1234f), 0.0f); + AssertEqual(0.0f, MathF.TanPi(0.6789f) + MathF.TanPi(-0.6789f), 0.0f); + AssertEqual(0.0f, MathF.TanPi(1.2345f) + MathF.TanPi(-1.2345f), 0.0f); } } } From d2c82a4cfb370b8ea4359599c34aa19629ee9783 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 4 Jun 2021 21:33:57 +0800 Subject: [PATCH 05/15] Rewrite SinPi and CosPi --- .../System.Private.CoreLib/src/System/Math.cs | 68 +++++++++++-------- .../src/System/MathF.cs | 64 +++++++++-------- 2 files changed, 76 insertions(+), 56 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index 022c40b4e39baf..a5913a142b6d97 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -1462,42 +1462,43 @@ public static double ScaleB(double x, int n) /// public static double SinPi(double x) { - // Implementation based on https://github.com/boostorg/math/blob/develop/include/boost/math/special_functions/sin_pi.hpp - - if (x < 0) - { - return -SinPi(-x); - } - - bool invert; - if (x < 0.5) + if (Abs(x) < 0.5 || !double.IsFinite(x)) { + // Fast path for small/special values, also covers +0/-0 return Sin(x * PI); } - if (x < 1) + + bool invert = false; + if (x < 0) { - invert = true; x = -x; + invert = true; } - else + + double floor = Floor(x); + if (x == floor) { - invert = false; + // +0 for +n and -0 for -n + return invert ? -0.0 : 0.0; } - double floor = Floor(x); - if (((int)floor & 1) != 0) + // fold all input into (0, 0.5] + if (((long)floor & 1) != 0) { + // sin(x + PI) = -sin(x) invert = !invert; } double rem = x - floor; - if (rem > 0.5) + if (rem == 0.5) { - rem = 1 - rem; + return invert ? -1 : 1; } - else if (rem == 0.5) + + if (rem > 0.5) { - return invert ? -1 : 1; + // sin(PI - x) = sin(x) + rem = 1 - rem; } double sin = Sin(rem * PI); @@ -1516,37 +1517,46 @@ public static double SinPi(double x) /// public static double CosPi(double x) { - // Implementation based on https://github.com/boostorg/math/blob/develop/include/boost/math/special_functions/cos_pi.hpp - - if (Abs(x) < 0.25) + if (Abs(x) < 0.5 || !double.IsFinite(x)) { + // Fast path for small/special values, also covers +0/-0 return Cos(x * PI); } + bool invert = false; if (x < 0) { x = -x; } - bool invert = false; double floor = Floor(x); - if (((int)floor & 1) != 0) + + // fold all input into [0, 0.5] + if (((long)floor & 1) != 0) { + // cos(x + PI) = -cos(x) invert = !invert; } + if (x == floor) + { + return invert ? -1 : 1; + } + double rem = x - floor; + if (rem == 0.5) + { + return 0.0; + } + if (rem > 0.5) { + // cos(PI - x) = -cos(x) rem = 1 - rem; invert = !invert; } - else if (rem == 0.5) - { - return 0; - } - double cos = rem > 0.25 ? Cos(0.5 - rem) : Cos(rem); + double cos = Cos(rem * PI); return invert ? -cos : cos; } diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index 79bfd402c0be10..9c0c9d47127dc7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -532,42 +532,43 @@ public static float ScaleB(float x, int n) /// public static float SinPi(float x) { - // Implementation based on https://github.com/boostorg/math/blob/develop/include/boost/math/special_functions/sin_pi.hpp - - if (x < 0) - { - return -SinPi(-x); - } - - bool invert; - if (x < 0.5f) + if (Abs(x) < 0.5f || !float.IsFinite(x)) { + // Fast path for small/special values, also covers +0/-0 return Sin(x * PI); } - if (x < 1) + + bool invert = false; + if (x < 0) { - invert = true; x = -x; + invert = true; } - else + + float floor = Floor(x); + if (x == floor) { - invert = false; + // +0 for +n and -0 for -n + return invert ? -0.0f : 0.0f; } - float floor = Floor(x); + // fold all input into (0, 0.5] if (((int)floor & 1) != 0) { + // sin(x + PI) = -sin(x) invert = !invert; } float rem = x - floor; - if (rem > 0.5f) + if (rem == 0.5f) { - rem = 1 - rem; + return invert ? -1 : 1; } - else if (rem == 0.5f) + + if (rem > 0.5f) { - return invert ? -1 : 1; + // sin(PI - x) = sin(x) + rem = 1 - rem; } float sin = Sin(rem * PI); @@ -586,37 +587,46 @@ public static float SinPi(float x) /// public static float CosPi(float x) { - // Implementation based on https://github.com/boostorg/math/blob/develop/include/boost/math/special_functions/cos_pi.hpp - - if (Abs(x) < 0.25f) + if (Abs(x) < 0.5f || !double.IsFinite(x)) { + // Fast path for small/special values, also covers +0/-0 return Cos(x * PI); } + bool invert = false; if (x < 0) { x = -x; } - bool invert = false; float floor = Floor(x); + + // fold all input into [0, 0.5] if (((int)floor & 1) != 0) { + // cos(x + PI) = -cos(x) invert = !invert; } + if (x == floor) + { + return invert ? -1 : 1; + } + float rem = x - floor; + if (rem == 0.5f) + { + return 0.0f; + } + if (rem > 0.5f) { + // cos(PI - x) = -cos(x) rem = 1 - rem; invert = !invert; } - else if (rem == 0.5f) - { - return 0; - } - float cos = rem > 0.25f ? Cos(0.5f - rem) : Cos(rem); + float cos = Cos(rem * PI); return invert ? -cos : cos; } From 9d2576a50c0406886c025ae67b5c8186ede0ba87 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 4 Jun 2021 21:50:11 +0800 Subject: [PATCH 06/15] Add real implementation for TanPi --- .../System.Private.CoreLib/src/System/Math.cs | 51 +++++++++++++++++- .../src/System/MathF.cs | 54 ++++++++++++++++++- 2 files changed, 102 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index a5913a142b6d97..9de5f280fd5890 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -1570,6 +1570,55 @@ public static double CosPi(double x) /// This method is effectively Tan(x * PI), with higher precision. /// It guarantees to return 0, or when is integer or half-integer. /// - public static double TanPi(double x) => SinPi(x) / CosPi(x); + public static double TanPi(double x) + { + if (Abs(x) < 0.5 || !double.IsFinite(x)) + { + // Fast path for small/special values, also covers +0/-0 + return Tan(x * PI); + } + + bool invert = false; + if (x < 0) + { + x = -x; + invert = true; + } + + double floor = Floor(x); + if (x == floor) + { + // +0 for +2n and -0 for +2n+1 + // -0 for -2n and +0 for -2n-1 + if (((long)floor & 1) != 0) + { + invert = !invert; + } + return invert ? -0.0 : 0.0; + } + + // fold all input into (0, 0.5] + double rem = x - floor; + if (rem == 0.5) + { + // +inf for +2n+0.5 and -inf for +2n+1.5 + // -inf for -2n-0.5 and +inf for -2n-1.5 + if (((long)floor & 1) != 0) + { + invert = !invert; + } + return invert ? double.NegativeInfinity : double.PositiveInfinity; + } + + if (rem > 0.5) + { + // tan(PI - x) = -tan(x) + rem = 1 - rem; + invert = !invert; + } + + double tan = Tan(rem * PI); + return invert ? -tan : tan; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index 9c0c9d47127dc7..89a2faa71dae02 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -587,7 +587,7 @@ public static float SinPi(float x) /// public static float CosPi(float x) { - if (Abs(x) < 0.5f || !double.IsFinite(x)) + if (Abs(x) < 0.5f || !float.IsFinite(x)) { // Fast path for small/special values, also covers +0/-0 return Cos(x * PI); @@ -640,6 +640,56 @@ public static float CosPi(float x) /// This method is effectively Tan(x * PI), with higher precision. /// It guarantees to return 0, or when is integer or half-integer. /// - public static float TanPi(float x) => SinPi(x) / CosPi(x); + public static float TanPi(float x) + { + + if (Abs(x) < 0.5f || !float.IsFinite(x)) + { + // Fast path for small/special values, also covers +0/-0 + return Tan(x * PI); + } + + bool invert = false; + if (x < 0) + { + x = -x; + invert = true; + } + + float floor = Floor(x); + if (x == floor) + { + // +0 for +2n and -0 for +2n+1 + // -0 for -2n and +0 for -2n-1 + if (((int)floor & 1) != 0) + { + invert = !invert; + } + return invert ? -0.0f : 0.0f; + } + + // fold all input into (0, 0.5] + float rem = x - floor; + if (rem == 0.5f) + { + // +inf for +2n+0.5 and -inf for +2n+1.5 + // -inf for -2n-0.5 and +inf for -2n-1.5 + if (((long)floor & 1) != 0) + { + invert = !invert; + } + return invert ? float.NegativeInfinity : float.PositiveInfinity; + } + + if (rem > 0.5) + { + // tan(PI - x) = -tan(x) + rem = 1 - rem; + invert = !invert; + } + + float tan = Tan(rem * PI); + return invert ? -tan : tan; + } } } From 792825f2c8a2ec2593a30c64c3a970f834ce5cf2 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 4 Jun 2021 22:05:14 +0800 Subject: [PATCH 07/15] Add correctness test --- .../tests/System/Math.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Math.cs b/src/libraries/System.Runtime.Extensions/tests/System/Math.cs index 3f82724dce828f..6a525490589d55 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Math.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Math.cs @@ -3290,5 +3290,29 @@ public static void TanPi_Float_Precision() AssertEqual(0.0f, MathF.TanPi(0.6789f) + MathF.TanPi(-0.6789f), 0.0f); AssertEqual(0.0f, MathF.TanPi(1.2345f) + MathF.TanPi(-1.2345f), 0.0f); } + + [Theory] + [InlineData(0.1234)] + [InlineData(0.6789)] + [InlineData(1.2345)] + [InlineData(1.9876)] + public static void TriPi_Double_Correctness(double x) + { + AssertEqual(Math.Sin(x * Math.PI), Math.SinPi(x), CrossPlatformMachineEpsilonForEstimates); + AssertEqual(Math.Cos(x * Math.PI), Math.CosPi(x), CrossPlatformMachineEpsilonForEstimates); + AssertEqual(Math.Tan(x * Math.PI), Math.TanPi(x), CrossPlatformMachineEpsilonForEstimates); + } + + [Theory] + [InlineData(0.1234f)] + [InlineData(0.6789f)] + [InlineData(1.2345f)] + [InlineData(1.9876f)] + public static void TriPi_Float_Correctness(float x) + { + AssertEqual(MathF.Sin(x * MathF.PI), MathF.SinPi(x), (float)CrossPlatformMachineEpsilonForEstimates); + AssertEqual(MathF.Cos(x * MathF.PI), MathF.CosPi(x), (float)CrossPlatformMachineEpsilonForEstimates); + AssertEqual(MathF.Tan(x * MathF.PI), MathF.TanPi(x), (float)CrossPlatformMachineEpsilonForEstimates); + } } } From ced1e7e8e90a76349cc2b99986f4749a910514d5 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 4 Jun 2021 22:39:15 +0800 Subject: [PATCH 08/15] Add AsinPi, AcosPi, AtanPi and Atan2Pi --- .../System.Private.CoreLib/src/System/Math.cs | 63 +++++++++++++++++++ .../src/System/MathF.cs | 63 +++++++++++++++++++ .../System.Runtime/ref/System.Runtime.cs | 8 +++ 3 files changed, 134 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index 9de5f280fd5890..f49f2574565986 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -1620,5 +1620,68 @@ public static double TanPi(double x) double tan = Tan(rem * PI); return invert ? -tan : tan; } + + // We don't need to special case inverse-trigs as long as getting precise PI/2 and PI. + // Guarded in unit test. + + /// + /// Returns the angle measured in half-turns whose sine is the specified number. + /// + /// A number representing a sine, where must be greater than or equal to -1, but + /// less than or equal to 1. + /// An angle, θ, measured in half-turns, such that -0.5 ≤ θ ≤ 0.5. + /// -or- + /// if < -1 or > 1 + /// or equals . + public static double AsinPi(double x) => Asin(x) / PI; + + /// + /// Returns the angle measured in half-turns whose cosine is the specified number. + /// + /// A number representing a cosine, where must be greater than or equal to -1, but + /// less than or equal to 1. + /// An angle, θ, measured in half-turns, such that -0.5 ≤ θ ≤ 0.5. + /// -or- + /// if < -1 or > 1 + /// or equals . + public static double AcosPi(double x) => Acos(x) / PI; + + /// + /// Returns the angle measured in half-turns whose tangent is the specified number. + /// + /// A number representing a tangent. + /// An angle, θ, measured in half-turns, such that -0.5 ≤ θ ≤ 0.5. + /// -or- + /// if equals , + /// -0.5 if equals , + /// or 0.5 if equals . + public static double AtanPi(double x) => Atan(x) / PI; + + /// + /// Returns the angle whose measured in half-turns tangent is the quotient of two specified numbers. + /// + /// The y coordinate of a point. + /// The x coordinate of a point. + /// An angle, θ, measured in half-turns, such that -1 ≤ θ ≤ 1, and tan(θ) = y / x, + /// where (x, y) is a point in the Cartesian plane. Observe the following: + /// + /// For (x, y) in quadrant 1, 0 < θ < 0.5. + /// For (x, y) in quadrant 2, 0.5 < θ ≤ 1. + /// For (x, y) in quadrant 3, -1 < θ < -0.5. + /// For (x, y) in quadrant 4, -0.5 < θ < 0. + /// + /// For points on the boundaries of the quadrants, the return value is the following: + /// + /// If y is 0 and x is not negative, θ = 0. + /// If y is 0 and x is negative, θ = 1. + /// If y is positive and x is 0, θ = 0.5. + /// If y is negative and x is 0, θ = -0.5. + /// If y is 0 and x is 0, θ = 0. + /// If y is 0 and x is 0, θ = 0. + /// + /// If x or y is , or if x and y are either + /// or , + /// the method returns . + public static double Atan2Pi(double y, double x) => Atan2(y, x) / PI; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index 89a2faa71dae02..fbdc50aa1c16a4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -691,5 +691,68 @@ public static float TanPi(float x) float tan = Tan(rem * PI); return invert ? -tan : tan; } + + // We don't need to special case inverse-trigs as long as getting precise PI/2 and PI. + // Guarded in unit test. + + /// + /// Returns the angle measured in half-turns whose sine is the specified number. + /// + /// A number representing a sine, where must be greater than or equal to -1, but + /// less than or equal to 1. + /// An angle, θ, measured in half-turns, such that -0.5 ≤ θ ≤ 0.5. + /// -or- + /// if < -1 or > 1 + /// or equals . + public static float AsinPi(float x) => Asin(x) / PI; + + /// + /// Returns the angle measured in half-turns whose cosine is the specified number. + /// + /// A number representing a cosine, where must be greater than or equal to -1, but + /// less than or equal to 1. + /// An angle, θ, measured in half-turns, such that -0.5 ≤ θ ≤ 0.5. + /// -or- + /// if < -1 or > 1 + /// or equals . + public static float AcosPi(float x) => Acos(x) / PI; + + /// + /// Returns the angle measured in half-turns whose tangent is the specified number. + /// + /// A number representing a tangent. + /// An angle, θ, measured in half-turns, such that -0.5 ≤ θ ≤ 0.5. + /// -or- + /// if equals , + /// -0.5 if equals , + /// or 0.5 if equals . + public static float AtanPi(float x) => Atan(x) / PI; + + /// + /// Returns the angle whose measured in half-turns tangent is the quotient of two specified numbers. + /// + /// The y coordinate of a point. + /// The x coordinate of a point. + /// An angle, θ, measured in half-turns, such that -1 ≤ θ ≤ 1, and tan(θ) = y / x, + /// where (x, y) is a point in the Cartesian plane. Observe the following: + /// + /// For (x, y) in quadrant 1, 0 < θ < 0.5. + /// For (x, y) in quadrant 2, 0.5 < θ ≤ 1. + /// For (x, y) in quadrant 3, -1 < θ < -0.5. + /// For (x, y) in quadrant 4, -0.5 < θ < 0. + /// + /// For points on the boundaries of the quadrants, the return value is the following: + /// + /// If y is 0 and x is not negative, θ = 0. + /// If y is 0 and x is negative, θ = 1. + /// If y is positive and x is 0, θ = 0.5. + /// If y is negative and x is 0, θ = -0.5. + /// If y is 0 and x is 0, θ = 0. + /// If y is 0 and x is 0, θ = 0. + /// + /// If x or y is , or if x and y are either + /// or , + /// the method returns . + public static float Atan2Pi(float y, float x) => Atan2(y, x) / PI; } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 6e89d500c4e975..56ab5e7c211f8d 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -2751,11 +2751,15 @@ public static partial class Math public static float Abs(float value) { throw null; } public static double Acos(double d) { throw null; } public static double Acosh(double d) { throw null; } + public static double AcosPi(double x) { throw null; } public static double Asin(double d) { throw null; } public static double Asinh(double d) { throw null; } + public static double AsinPi(double x) { throw null; } public static double Atan(double d) { throw null; } public static double Atan2(double y, double x) { throw null; } + public static double Atan2Pi(double y, double x) { throw null; } public static double Atanh(double d) { throw null; } + public static double AtanPi(double x) { throw null; } public static long BigMul(int a, int b) { throw null; } public static long BigMul(long a, long b, out long low) { throw null; } [System.CLSCompliantAttribute(false)] @@ -2892,11 +2896,15 @@ public static partial class MathF public static float Abs(float x) { throw null; } public static float Acos(float x) { throw null; } public static float Acosh(float x) { throw null; } + public static float AcosPi(float x) { throw null; } public static float Asin(float x) { throw null; } public static float Asinh(float x) { throw null; } + public static float AsinPi(float x) { throw null; } public static float Atan(float x) { throw null; } public static float Atan2(float y, float x) { throw null; } + public static float Atan2Pi(float y, float x) { throw null; } public static float Atanh(float x) { throw null; } + public static float AtanPi(float x) { throw null; } public static float BitDecrement(float x) { throw null; } public static float BitIncrement(float x) { throw null; } public static float Cbrt(float x) { throw null; } From e702e36eb0da7e70c3754fe66bb97f479e7426aa Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 4 Jun 2021 22:44:41 +0800 Subject: [PATCH 09/15] Use the term half-revolution to match IEEE spec --- .../System.Private.CoreLib/src/System/Math.cs | 28 +++++++++---------- .../src/System/MathF.cs | 28 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index f49f2574565986..942aa1fcba82f0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -1451,9 +1451,9 @@ public static double ScaleB(double x, int n) } /// - /// Returns the sine of the specified angle measured in half-turns. + /// Returns the sine of the specified angle measured in half-revolutions. /// - /// An angle, measured in half-turns. + /// An angle, measured in half-revolutions. /// The sine of . If is equal to , , /// or , this method returns . /// @@ -1506,9 +1506,9 @@ public static double SinPi(double x) } /// - /// Returns the cosine of the specified angle measured in half-turns. + /// Returns the cosine of the specified angle measured in half-revolutions. /// - /// An angle, measured in half-turns. + /// An angle, measured in half-revolutions. /// The cosine of . If is equal to , , /// or , this method returns . /// @@ -1561,9 +1561,9 @@ public static double CosPi(double x) } /// - /// Returns the tangent of the specified angle measured in half-turns. + /// Returns the tangent of the specified angle measured in half-revolutions. /// - /// An angle, measured in half-turns. + /// An angle, measured in half-revolutions. /// The tangent of . If is equal to , , /// or , this method returns . /// @@ -1625,32 +1625,32 @@ public static double TanPi(double x) // Guarded in unit test. /// - /// Returns the angle measured in half-turns whose sine is the specified number. + /// Returns the angle measured in half-revolutions whose sine is the specified number. /// /// A number representing a sine, where must be greater than or equal to -1, but /// less than or equal to 1. - /// An angle, θ, measured in half-turns, such that -0.5 ≤ θ ≤ 0.5. + /// An angle, θ, measured in half-revolutions, such that -0.5 ≤ θ ≤ 0.5. /// -or- /// if < -1 or > 1 /// or equals . public static double AsinPi(double x) => Asin(x) / PI; /// - /// Returns the angle measured in half-turns whose cosine is the specified number. + /// Returns the angle measured in half-revolutions whose cosine is the specified number. /// /// A number representing a cosine, where must be greater than or equal to -1, but /// less than or equal to 1. - /// An angle, θ, measured in half-turns, such that -0.5 ≤ θ ≤ 0.5. + /// An angle, θ, measured in half-revolutions, such that -0.5 ≤ θ ≤ 0.5. /// -or- /// if < -1 or > 1 /// or equals . public static double AcosPi(double x) => Acos(x) / PI; /// - /// Returns the angle measured in half-turns whose tangent is the specified number. + /// Returns the angle measured in half-revolutions whose tangent is the specified number. /// /// A number representing a tangent. - /// An angle, θ, measured in half-turns, such that -0.5 ≤ θ ≤ 0.5. + /// An angle, θ, measured in half-revolutions, such that -0.5 ≤ θ ≤ 0.5. /// -or- /// if equals , /// -0.5 if equals , @@ -1658,11 +1658,11 @@ public static double TanPi(double x) public static double AtanPi(double x) => Atan(x) / PI; /// - /// Returns the angle whose measured in half-turns tangent is the quotient of two specified numbers. + /// Returns the angle whose measured in half-revolutions tangent is the quotient of two specified numbers. /// /// The y coordinate of a point. /// The x coordinate of a point. - /// An angle, θ, measured in half-turns, such that -1 ≤ θ ≤ 1, and tan(θ) = y / x, + /// An angle, θ, measured in half-revolutions, such that -1 ≤ θ ≤ 1, and tan(θ) = y / x, /// where (x, y) is a point in the Cartesian plane. Observe the following: /// /// For (x, y) in quadrant 1, 0 < θ < 0.5. diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index fbdc50aa1c16a4..1306ee5ab0e19a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -521,9 +521,9 @@ public static float ScaleB(float x, int n) } /// - /// Returns the sine of the specified angle measured in half-turns. + /// Returns the sine of the specified angle measured in half-revolutions. /// - /// An angle, measured in half-turns. + /// An angle, measured in half-revolutions. /// The sine of . If is equal to , , /// or , this method returns . /// @@ -576,9 +576,9 @@ public static float SinPi(float x) } /// - /// Returns the cosine of the specified angle measured in half-turns. + /// Returns the cosine of the specified angle measured in half-revolutions. /// - /// An angle, measured in half-turns. + /// An angle, measured in half-revolutions. /// The cosine of . If is equal to , , /// or , this method returns . /// @@ -631,9 +631,9 @@ public static float CosPi(float x) } /// - /// Returns the tangent of the specified angle measured in half-turns. + /// Returns the tangent of the specified angle measured in half-revolutions. /// - /// An angle, measured in half-turns. + /// An angle, measured in half-revolutions. /// The tangent of . If is equal to , , /// or , this method returns . /// @@ -696,32 +696,32 @@ public static float TanPi(float x) // Guarded in unit test. /// - /// Returns the angle measured in half-turns whose sine is the specified number. + /// Returns the angle measured in half-revolutions whose sine is the specified number. /// /// A number representing a sine, where must be greater than or equal to -1, but /// less than or equal to 1. - /// An angle, θ, measured in half-turns, such that -0.5 ≤ θ ≤ 0.5. + /// An angle, θ, measured in half-revolutions, such that -0.5 ≤ θ ≤ 0.5. /// -or- /// if < -1 or > 1 /// or equals . public static float AsinPi(float x) => Asin(x) / PI; /// - /// Returns the angle measured in half-turns whose cosine is the specified number. + /// Returns the angle measured in half-revolutions whose cosine is the specified number. /// /// A number representing a cosine, where must be greater than or equal to -1, but /// less than or equal to 1. - /// An angle, θ, measured in half-turns, such that -0.5 ≤ θ ≤ 0.5. + /// An angle, θ, measured in half-revolutions, such that -0.5 ≤ θ ≤ 0.5. /// -or- /// if < -1 or > 1 /// or equals . public static float AcosPi(float x) => Acos(x) / PI; /// - /// Returns the angle measured in half-turns whose tangent is the specified number. + /// Returns the angle measured in half-revolutions whose tangent is the specified number. /// /// A number representing a tangent. - /// An angle, θ, measured in half-turns, such that -0.5 ≤ θ ≤ 0.5. + /// An angle, θ, measured in half-revolutions, such that -0.5 ≤ θ ≤ 0.5. /// -or- /// if equals , /// -0.5 if equals , @@ -729,11 +729,11 @@ public static float TanPi(float x) public static float AtanPi(float x) => Atan(x) / PI; /// - /// Returns the angle whose measured in half-turns tangent is the quotient of two specified numbers. + /// Returns the angle whose measured in half-revolutions tangent is the quotient of two specified numbers. /// /// The y coordinate of a point. /// The x coordinate of a point. - /// An angle, θ, measured in half-turns, such that -1 ≤ θ ≤ 1, and tan(θ) = y / x, + /// An angle, θ, measured in half-revolutions, such that -1 ≤ θ ≤ 1, and tan(θ) = y / x, /// where (x, y) is a point in the Cartesian plane. Observe the following: /// /// For (x, y) in quadrant 1, 0 < θ < 0.5. From 1a43b7a6b674177200d5a1613de006fdccc31599 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 4 Jun 2021 23:13:15 +0800 Subject: [PATCH 10/15] Test AsinPi, AcosPi, AtanPi and Atan2Pi --- .../tests/System/Math.cs | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Math.cs b/src/libraries/System.Runtime.Extensions/tests/System/Math.cs index 6a525490589d55..25d8a3483eec5a 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Math.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Math.cs @@ -3314,5 +3314,97 @@ public static void TriPi_Float_Correctness(float x) AssertEqual(MathF.Cos(x * MathF.PI), MathF.CosPi(x), (float)CrossPlatformMachineEpsilonForEstimates); AssertEqual(MathF.Tan(x * MathF.PI), MathF.TanPi(x), (float)CrossPlatformMachineEpsilonForEstimates); } + + [Fact] + public static void AsinPi_Double_Precision() + { + AssertEqual( 0.5, Math.AsinPi( 1.0), 0.0); + AssertEqual(-0.5, Math.AsinPi(-1.0), 0.0); + } + + [Fact] + public static void AsinPi_Float_Precision() + { + AssertEqual( 0.5f, MathF.AsinPi( 1.0f), 0.0f); + AssertEqual(-0.5f, MathF.AsinPi(-1.0f), 0.0f); + } + + [Fact] + public static void AcosPi_Double_Precision() + { + AssertEqual( 0.5, Math.AcosPi( 0.0), 0.0); + AssertEqual( 0.5, Math.AcosPi(-0.0), 0.0); + AssertEqual( 0.0, Math.AcosPi( 1.0), 0.0); + AssertEqual( 1.0, Math.AcosPi(-1.0), 0.0); + } + + [Fact] + public static void AcosPi_Float_Precision() + { + AssertEqual( 0.5f, MathF.AcosPi( 0.0f), 0.0f); + AssertEqual( 0.5f, MathF.AcosPi(-0.0f), 0.0f); + AssertEqual( 0.0f, MathF.AcosPi( 1.0f), 0.0f); + AssertEqual( 1.0f, MathF.AcosPi(-1.0f), 0.0f); + } + + [Fact] + public static void AtanPi_Double_Precision() + { + AssertEqual( 0.5, Math.AtanPi(double.PositiveInfinity), 0.0); + AssertEqual(-0.5, Math.AtanPi(double.NegativeInfinity), 0.0); + } + + [Fact] + public static void AtanPi_Float_Precision() + { + AssertEqual( 0.5f, MathF.AtanPi(float.PositiveInfinity), 0.0f); + AssertEqual(-0.5f, MathF.AtanPi(float.NegativeInfinity), 0.0f); + } + + [Fact] + public static void Atan2Pi_Double_Precision() + { + AssertEqual( 1.00, Math.Atan2Pi( 0.0, -0.0), 0.0); + AssertEqual(-1.00, Math.Atan2Pi(-0.0, -0.0), 0.0); + AssertEqual( 1.00, Math.Atan2Pi( 0.0, -1.0), 0.0); + AssertEqual(-1.00, Math.Atan2Pi(-0.0, -1.0), 0.0); + AssertEqual(-0.50, Math.Atan2Pi(-1.0, 0.0), 0.0); + AssertEqual(-0.50, Math.Atan2Pi(-1.0, -0.0), 0.0); + AssertEqual( 0.50, Math.Atan2Pi( 1.0, 0.0), 0.0); + AssertEqual( 0.50, Math.Atan2Pi( 1.0, -0.0), 0.0); + AssertEqual( 1.00, Math.Atan2Pi( 1.0, double.NegativeInfinity), 0.0); + AssertEqual(-1.00, Math.Atan2Pi(-1.0, double.NegativeInfinity), 0.0); + AssertEqual( 0.50, Math.Atan2Pi(double.PositiveInfinity, 1.0), 0.0); + AssertEqual( 0.50, Math.Atan2Pi(double.PositiveInfinity, -1.0), 0.0); + AssertEqual(-0.50, Math.Atan2Pi(double.NegativeInfinity, 1.0), 0.0); + AssertEqual(-0.50, Math.Atan2Pi(double.NegativeInfinity, -1.0), 0.0); + AssertEqual( 0.75, Math.Atan2Pi(double.PositiveInfinity, double.NegativeInfinity), 0.0); + AssertEqual(-0.75, Math.Atan2Pi(double.NegativeInfinity, double.NegativeInfinity), 0.0); + AssertEqual( 0.25, Math.Atan2Pi(double.PositiveInfinity, double.PositiveInfinity), 0.0); + AssertEqual(-0.25, Math.Atan2Pi(double.NegativeInfinity, double.PositiveInfinity), 0.0); + } + + [Fact] + public static void Atan2Pi_Float_Precision() + { + AssertEqual( 1.00f, MathF.Atan2Pi( 0.0f, -0.0f), 0.0f); + AssertEqual(-1.00f, MathF.Atan2Pi(-0.0f, -0.0f), 0.0f); + AssertEqual( 1.00f, MathF.Atan2Pi( 0.0f, -1.0f), 0.0f); + AssertEqual(-1.00f, MathF.Atan2Pi(-0.0f, -1.0f), 0.0f); + AssertEqual(-0.50f, MathF.Atan2Pi(-1.0f, 0.0f), 0.0f); + AssertEqual(-0.50f, MathF.Atan2Pi(-1.0f, -0.0f), 0.0f); + AssertEqual( 0.50f, MathF.Atan2Pi( 1.0f, 0.0f), 0.0f); + AssertEqual( 0.50f, MathF.Atan2Pi( 1.0f, -0.0f), 0.0f); + AssertEqual( 1.00f, MathF.Atan2Pi( 1.0f, float.NegativeInfinity), 0.0f); + AssertEqual(-1.00f, MathF.Atan2Pi(-1.0f, float.NegativeInfinity), 0.0f); + AssertEqual( 0.50f, MathF.Atan2Pi(float.PositiveInfinity, 1.0f), 0.0f); + AssertEqual( 0.50f, MathF.Atan2Pi(float.PositiveInfinity, -1.0f), 0.0f); + AssertEqual(-0.50f, MathF.Atan2Pi(float.NegativeInfinity, 1.0f), 0.0f); + AssertEqual(-0.50f, MathF.Atan2Pi(float.NegativeInfinity, -1.0f), 0.0f); + AssertEqual( 0.75f, MathF.Atan2Pi(float.PositiveInfinity, float.NegativeInfinity), 0.0f); + AssertEqual(-0.75f, MathF.Atan2Pi(float.NegativeInfinity, float.NegativeInfinity), 0.0f); + AssertEqual( 0.25f, MathF.Atan2Pi(float.PositiveInfinity, float.PositiveInfinity), 0.0f); + AssertEqual(-0.25f, MathF.Atan2Pi(float.NegativeInfinity, float.PositiveInfinity), 0.0f); + } } } From a81b6a2b227e4c772303af006ddc49e8bd4ca8e2 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 5 Jun 2021 15:58:48 +0800 Subject: [PATCH 11/15] Convert Atan2Pi test to theory to observe failures --- .../tests/System/Math.cs | 90 ++++++++++--------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Math.cs b/src/libraries/System.Runtime.Extensions/tests/System/Math.cs index 25d8a3483eec5a..482ae3d190c7dc 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Math.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Math.cs @@ -3361,50 +3361,52 @@ public static void AtanPi_Float_Precision() AssertEqual(-0.5f, MathF.AtanPi(float.NegativeInfinity), 0.0f); } - [Fact] - public static void Atan2Pi_Double_Precision() - { - AssertEqual( 1.00, Math.Atan2Pi( 0.0, -0.0), 0.0); - AssertEqual(-1.00, Math.Atan2Pi(-0.0, -0.0), 0.0); - AssertEqual( 1.00, Math.Atan2Pi( 0.0, -1.0), 0.0); - AssertEqual(-1.00, Math.Atan2Pi(-0.0, -1.0), 0.0); - AssertEqual(-0.50, Math.Atan2Pi(-1.0, 0.0), 0.0); - AssertEqual(-0.50, Math.Atan2Pi(-1.0, -0.0), 0.0); - AssertEqual( 0.50, Math.Atan2Pi( 1.0, 0.0), 0.0); - AssertEqual( 0.50, Math.Atan2Pi( 1.0, -0.0), 0.0); - AssertEqual( 1.00, Math.Atan2Pi( 1.0, double.NegativeInfinity), 0.0); - AssertEqual(-1.00, Math.Atan2Pi(-1.0, double.NegativeInfinity), 0.0); - AssertEqual( 0.50, Math.Atan2Pi(double.PositiveInfinity, 1.0), 0.0); - AssertEqual( 0.50, Math.Atan2Pi(double.PositiveInfinity, -1.0), 0.0); - AssertEqual(-0.50, Math.Atan2Pi(double.NegativeInfinity, 1.0), 0.0); - AssertEqual(-0.50, Math.Atan2Pi(double.NegativeInfinity, -1.0), 0.0); - AssertEqual( 0.75, Math.Atan2Pi(double.PositiveInfinity, double.NegativeInfinity), 0.0); - AssertEqual(-0.75, Math.Atan2Pi(double.NegativeInfinity, double.NegativeInfinity), 0.0); - AssertEqual( 0.25, Math.Atan2Pi(double.PositiveInfinity, double.PositiveInfinity), 0.0); - AssertEqual(-0.25, Math.Atan2Pi(double.NegativeInfinity, double.PositiveInfinity), 0.0); - } - - [Fact] - public static void Atan2Pi_Float_Precision() - { - AssertEqual( 1.00f, MathF.Atan2Pi( 0.0f, -0.0f), 0.0f); - AssertEqual(-1.00f, MathF.Atan2Pi(-0.0f, -0.0f), 0.0f); - AssertEqual( 1.00f, MathF.Atan2Pi( 0.0f, -1.0f), 0.0f); - AssertEqual(-1.00f, MathF.Atan2Pi(-0.0f, -1.0f), 0.0f); - AssertEqual(-0.50f, MathF.Atan2Pi(-1.0f, 0.0f), 0.0f); - AssertEqual(-0.50f, MathF.Atan2Pi(-1.0f, -0.0f), 0.0f); - AssertEqual( 0.50f, MathF.Atan2Pi( 1.0f, 0.0f), 0.0f); - AssertEqual( 0.50f, MathF.Atan2Pi( 1.0f, -0.0f), 0.0f); - AssertEqual( 1.00f, MathF.Atan2Pi( 1.0f, float.NegativeInfinity), 0.0f); - AssertEqual(-1.00f, MathF.Atan2Pi(-1.0f, float.NegativeInfinity), 0.0f); - AssertEqual( 0.50f, MathF.Atan2Pi(float.PositiveInfinity, 1.0f), 0.0f); - AssertEqual( 0.50f, MathF.Atan2Pi(float.PositiveInfinity, -1.0f), 0.0f); - AssertEqual(-0.50f, MathF.Atan2Pi(float.NegativeInfinity, 1.0f), 0.0f); - AssertEqual(-0.50f, MathF.Atan2Pi(float.NegativeInfinity, -1.0f), 0.0f); - AssertEqual( 0.75f, MathF.Atan2Pi(float.PositiveInfinity, float.NegativeInfinity), 0.0f); - AssertEqual(-0.75f, MathF.Atan2Pi(float.NegativeInfinity, float.NegativeInfinity), 0.0f); - AssertEqual( 0.25f, MathF.Atan2Pi(float.PositiveInfinity, float.PositiveInfinity), 0.0f); - AssertEqual(-0.25f, MathF.Atan2Pi(float.NegativeInfinity, float.PositiveInfinity), 0.0f); + [Theory] + [InlineData( 0.0, -0.0, 1.0)] + [InlineData(-0.0, -0.0, -1.0)] + [InlineData( 0.0, -1.0, 1.0)] + [InlineData(-0.0, -1.0, -1.0)] + [InlineData(-1.0, 0.0, -0.5)] + [InlineData(-1.0, -0.0, -0.5)] + [InlineData( 1.0, 0.0, 0.5)] + [InlineData( 1.0, -0.0, 0.5)] + [InlineData( 1.0, double.NegativeInfinity, 1.0)] + [InlineData(-1.0, double.NegativeInfinity, -1.0)] + [InlineData(double.PositiveInfinity, 1.0, 0.5)] + [InlineData(double.PositiveInfinity, -1.0, 0.5)] + [InlineData(double.NegativeInfinity, 1.0, -0.5)] + [InlineData(double.NegativeInfinity, -1.0, -0.5)] + [InlineData(double.PositiveInfinity, double.NegativeInfinity, 0.75)] + [InlineData(double.NegativeInfinity, double.NegativeInfinity, -0.75)] + [InlineData(double.PositiveInfinity, double.PositiveInfinity, 0.25)] + [InlineData(double.NegativeInfinity, double.PositiveInfinity, -0.25)] + public static void Atan2Pi_Double_Precision(double y, double x, double expected) + { + AssertEqual(expected, Math.Atan2Pi(y, x), 0.0); + } + + [Theory] + [InlineData( 0.0f, -0.0f, 1.0f)] + [InlineData(-0.0f, -0.0f, -1.0f)] + [InlineData( 0.0f, -1.0f, 1.0f)] + [InlineData(-0.0f, -1.0f, -1.0f)] + [InlineData(-1.0f, 0.0f, -0.5f)] + [InlineData(-1.0f, -0.0f, -0.5f)] + [InlineData( 1.0f, 0.0f, 0.5f)] + [InlineData( 1.0f, -0.0f, 0.5f)] + [InlineData( 1.0f, float.NegativeInfinity, 1.0f)] + [InlineData(-1.0f, float.NegativeInfinity, -1.0f)] + [InlineData(float.PositiveInfinity, 1.0f, 0.5f)] + [InlineData(float.PositiveInfinity, -1.0f, 0.5f)] + [InlineData(float.NegativeInfinity, 1.0f, -0.5f)] + [InlineData(float.NegativeInfinity, -1.0f, -0.5f)] + [InlineData(float.PositiveInfinity, float.NegativeInfinity, 0.75f)] + [InlineData(float.NegativeInfinity, float.NegativeInfinity, -0.75f)] + [InlineData(float.PositiveInfinity, float.PositiveInfinity, 0.25f)] + [InlineData(float.NegativeInfinity, float.PositiveInfinity, -0.25f)] + public static void Atan2Pi_Float_Precision(float y, float x, float expected) + { + AssertEqual(expected, MathF.Atan2Pi(y, x), 0.0f); } } } From 63ab183b49087b53aeb21372356b2486ac67076f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 5 Jun 2021 16:20:42 +0800 Subject: [PATCH 12/15] Special case AcosPi and AtanPi --- .../System.Private.CoreLib/src/System/Math.cs | 4 +- .../src/System/MathF.cs | 84 ++++++++++++------- 2 files changed, 55 insertions(+), 33 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index 942aa1fcba82f0..3dc95c6bec2de4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -1621,8 +1621,8 @@ public static double TanPi(double x) return invert ? -tan : tan; } - // We don't need to special case inverse-trigs as long as getting precise PI/2 and PI. - // Guarded in unit test. + // Double inverse-trigs pass all special value tests on all platforms. + // Keep them fast. /// /// Returns the angle measured in half-revolutions whose sine is the specified number. diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index 1306ee5ab0e19a..86a2d91304ad7c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -419,45 +419,45 @@ public static unsafe float Round(float x, int digits, MidpointRounding mode) // Rounds to the nearest value; if the number falls midway, // it is rounded to the nearest value with an even least significant digit case MidpointRounding.ToEven: - { - x = Round(x); - break; - } + { + x = Round(x); + break; + } // Rounds to the nearest value; if the number falls midway, // it is rounded to the nearest value above (for positive numbers) or below (for negative numbers) case MidpointRounding.AwayFromZero: - { - float fraction = ModF(x, &x); - - if (Abs(fraction) >= 0.5f) { - x += Sign(fraction); - } + float fraction = ModF(x, &x); - break; - } + if (Abs(fraction) >= 0.5f) + { + x += Sign(fraction); + } + + break; + } // Directed rounding: Round to the nearest value, toward to zero case MidpointRounding.ToZero: - { - x = Truncate(x); - break; - } + { + x = Truncate(x); + break; + } // Directed Rounding: Round down to the next value, toward negative infinity case MidpointRounding.ToNegativeInfinity: - { - x = Floor(x); - break; - } + { + x = Floor(x); + break; + } // Directed rounding: Round up to the next value, toward positive infinity case MidpointRounding.ToPositiveInfinity: - { - x = Ceiling(x); - break; - } + { + x = Ceiling(x); + break; + } default: - { - throw new ArgumentException(SR.Format(SR.Argument_InvalidEnumValue, mode, nameof(MidpointRounding)), nameof(mode)); - } + { + throw new ArgumentException(SR.Format(SR.Argument_InvalidEnumValue, mode, nameof(MidpointRounding)), nameof(mode)); + } } x /= power10; @@ -692,8 +692,8 @@ public static float TanPi(float x) return invert ? -tan : tan; } - // We don't need to special case inverse-trigs as long as getting precise PI/2 and PI. - // Guarded in unit test. + // Double inverse-trigs fail special value tests on some platforms. + // Special case them. /// /// Returns the angle measured in half-revolutions whose sine is the specified number. @@ -715,7 +715,15 @@ public static float TanPi(float x) /// -or- /// if < -1 or > 1 /// or equals . - public static float AcosPi(float x) => Acos(x) / PI; + public static float AcosPi(float x) + { + if (x == 0) + { + return 0.5f; + } + + return x > 0 ? Acos(x) / PI : 1 - Acos(-x) / PI; + } /// /// Returns the angle measured in half-revolutions whose tangent is the specified number. @@ -726,7 +734,21 @@ public static float TanPi(float x) /// if equals , /// -0.5 if equals , /// or 0.5 if equals . - public static float AtanPi(float x) => Atan(x) / PI; + public static float AtanPi(float x) + { + if (float.IsPositiveInfinity(x)) + { + return 0.5f; + } + else if (float.IsNegativeInfinity(x)) + { + return -0.5f; + } + else + { + return Atan(x) / PI; + } + } /// /// Returns the angle whose measured in half-revolutions tangent is the quotient of two specified numbers. From 7b74167b309eabd193894e94e0e65ae21206887f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 5 Jun 2021 19:39:04 +0800 Subject: [PATCH 13/15] Special cases for Atan2Pi --- .../System.Private.CoreLib/src/System/MathF.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index 86a2d91304ad7c..8e8ee99b7318f2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -775,6 +775,20 @@ public static float AtanPi(float x) /// If x or y is , or if x and y are either /// or , /// the method returns . - public static float Atan2Pi(float y, float x) => Atan2(y, x) / PI; + public static float Atan2Pi(float y, float x) + { + // There are many special values specified by IEEE754:2019 + // involving +0/-0 which are not easy to handle + // To simplify, only special case failures in tests + if (float.IsPositiveInfinity(y) && float.IsFinite(x)) + { + return 0.5f; + } + if (float.IsNegativeInfinity(y) && float.IsFinite(x)) + { + return -0.5f; + } + return Atan2(y, x) / PI; + } } } From 4b809baf12fd57cc73b53b4d9a4cd94f11685224 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 5 Jun 2021 23:25:51 +0800 Subject: [PATCH 14/15] Special case SinPi and change Atan2Pi --- .../src/System/MathF.cs | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index 8e8ee99b7318f2..9ed06bdc5aa912 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -704,7 +704,19 @@ public static float TanPi(float x) /// -or- /// if < -1 or > 1 /// or equals . - public static float AsinPi(float x) => Asin(x) / PI; + public static float AsinPi(float x) + { + if (x == 1) + { + return 0.5f; + } + if (x == -1) + { + return -0.5f; + } + + return Asin(x) / PI; + } /// /// Returns the angle measured in half-revolutions whose cosine is the specified number. @@ -777,18 +789,16 @@ public static float AtanPi(float x) /// the method returns . public static float Atan2Pi(float y, float x) { - // There are many special values specified by IEEE754:2019 - // involving +0/-0 which are not easy to handle - // To simplify, only special case failures in tests - if (float.IsPositiveInfinity(y) && float.IsFinite(x)) - { - return 0.5f; - } - if (float.IsNegativeInfinity(y) && float.IsFinite(x)) + float atan = Atan2(y, x) / PI; + + // if x or y is 0 or inf, it's a special value required by IEEE754:2019 + // rounding to nearist quarter-integer (keeps +0/-0) + if (x == 0 || y == 0 || float.IsInfinity(x) || float.IsInfinity(y)) { - return -0.5f; + return Round(atan * 4) / 4; } - return Atan2(y, x) / PI; + + return atan; } } } From 24a44446d45a6f90664b67222b63e4c7c4ac459c Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 6 Jun 2021 02:21:05 +0800 Subject: [PATCH 15/15] Fix unwanted indent change and comment --- .../src/System/MathF.cs | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index 9ed06bdc5aa912..26ab3c680d4ad3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -419,45 +419,45 @@ public static unsafe float Round(float x, int digits, MidpointRounding mode) // Rounds to the nearest value; if the number falls midway, // it is rounded to the nearest value with an even least significant digit case MidpointRounding.ToEven: - { - x = Round(x); - break; - } + { + x = Round(x); + break; + } // Rounds to the nearest value; if the number falls midway, // it is rounded to the nearest value above (for positive numbers) or below (for negative numbers) case MidpointRounding.AwayFromZero: - { - float fraction = ModF(x, &x); - - if (Abs(fraction) >= 0.5f) - { - x += Sign(fraction); - } + { + float fraction = ModF(x, &x); - break; + if (Abs(fraction) >= 0.5f) + { + x += Sign(fraction); } + + break; + } // Directed rounding: Round to the nearest value, toward to zero case MidpointRounding.ToZero: - { - x = Truncate(x); - break; - } + { + x = Truncate(x); + break; + } // Directed Rounding: Round down to the next value, toward negative infinity case MidpointRounding.ToNegativeInfinity: - { - x = Floor(x); - break; - } + { + x = Floor(x); + break; + } // Directed rounding: Round up to the next value, toward positive infinity case MidpointRounding.ToPositiveInfinity: - { - x = Ceiling(x); - break; - } + { + x = Ceiling(x); + break; + } default: - { - throw new ArgumentException(SR.Format(SR.Argument_InvalidEnumValue, mode, nameof(MidpointRounding)), nameof(mode)); - } + { + throw new ArgumentException(SR.Format(SR.Argument_InvalidEnumValue, mode, nameof(MidpointRounding)), nameof(mode)); + } } x /= power10; @@ -692,7 +692,7 @@ public static float TanPi(float x) return invert ? -tan : tan; } - // Double inverse-trigs fail special value tests on some platforms. + // Float inverse-trigs fail special value tests on some platforms. // Special case them. ///