Skip to content

Commit

Permalink
Expose the DegreesToRadians and RadiansToDegrees APIs (#88866)
Browse files Browse the repository at this point in the history
* Expose the DegreesToRadians and RadiansToDegrees APIs

* Adding some comments elaborating on the Degrees/Radians conversion APIs

* Update src/libraries/System.Private.CoreLib/src/System/Numerics/ITrigonometricFunctions.cs

Co-authored-by: Joe4evr <[email protected]>

* Fix a copy/paste error for the Half degree to radians tests

---------

Co-authored-by: Joe4evr <[email protected]>
  • Loading branch information
tannergooding and Joe4evr authored Jul 14, 2023
1 parent 1177698 commit 40480e8
Show file tree
Hide file tree
Showing 11 changed files with 459 additions and 0 deletions.
18 changes: 18 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/Double.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1887,6 +1887,24 @@ public static double CosPi(double x)
return result;
}

/// <inheritdoc cref="ITrigonometricFunctions{TSelf}.DegreesToRadians(TSelf)" />
public static double DegreesToRadians(double degrees)
{
// NOTE: Don't change the algorithm without consulting the DIM
// which elaborates on why this implementation was chosen

return (degrees * Pi) / 180.0;
}

/// <inheritdoc cref="ITrigonometricFunctions{TSelf}.RadiansToDegrees(TSelf)" />
public static double RadiansToDegrees(double radians)
{
// NOTE: Don't change the algorithm without consulting the DIM
// which elaborates on why this implementation was chosen

return (radians * 180.0) / Pi;
}

/// <inheritdoc cref="ITrigonometricFunctions{TSelf}.Sin(TSelf)" />
[Intrinsic]
public static double Sin(double x) => Math.Sin(x);
Expand Down
18 changes: 18 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/Half.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2162,6 +2162,24 @@ private static bool TryConvertTo<TOther>(Half value, [MaybeNullWhen(false)] out
/// <inheritdoc cref="ITrigonometricFunctions{TSelf}.CosPi(TSelf)" />
public static Half CosPi(Half x) => (Half)float.CosPi((float)x);

/// <inheritdoc cref="ITrigonometricFunctions{TSelf}.DegreesToRadians(TSelf)" />
public static Half DegreesToRadians(Half degrees)
{
// NOTE: Don't change the algorithm without consulting the DIM
// which elaborates on why this implementation was chosen

return (Half)float.DegreesToRadians((float)degrees);
}

/// <inheritdoc cref="ITrigonometricFunctions{TSelf}.RadiansToDegrees(TSelf)" />
public static Half RadiansToDegrees(Half radians)
{
// NOTE: Don't change the algorithm without consulting the DIM
// which elaborates on why this implementation was chosen

return (Half)float.RadiansToDegrees((float)radians);
}

/// <inheritdoc cref="ITrigonometricFunctions{TSelf}.Sin(TSelf)" />
public static Half Sin(Half x) => (Half)MathF.Sin((float)x);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,73 @@ public interface ITrigonometricFunctions<TSelf>
/// <remarks>This computes <c>cos(x * PI)</c>.</remarks>
static abstract TSelf CosPi(TSelf x);

/// <summary>Converts a given value from degrees to radians.</summary>
/// <param name="degrees">The value to convert to radians.</param>
/// <returns>The value of <paramref name="degrees" /> converted to radians.</returns>
static virtual TSelf DegreesToRadians(TSelf degrees)
{
// We don't want to simplify this to: degrees * (Pi / 180)
//
// Doing so will change the result and in many cases will
// cause a loss of precision. The main exception to this
// is when the initial multiplication causes overflow, but
// if we decide that should be handled in the future it needs
// to be more explicit around how its done.
//
// Floating-point operations are naturally imprecise due to
// rounding required to fit the "infinitely-precise result"
// into the limits of the underlying representation. Because
// of this, every operation can introduce some amount of rounding
// error.
//
// For integers, the IEEE 754 binary floating-point types can
// exactly represent them up to the 2^n, where n is the number
// of bits in the significand. This is 10 for Half, 23 for Single,
// and 52 for Double. As you approach this limit, the number of
// digits available to represent the fractional portion decreases.
//
// For Half, you get around 3.311 total decimal digits of precision.
// For Single, this is around 7.225 and around 15.955 for Double.
//
// The actual number of digits can be slightly more or less, depending.
//
// This means that values such as `Pi` are not exactly `Pi`, instead:
// * Half: 3.14 0625
// * Single: 3.14 1592 741012573 2421875
// * Double: 3.14 1592 653589793 115997963468544185161590576171875
// * Actual: 3.14 1592 653589793 2384626433832795028841971693993751058209749445923...
//
// If we were to simplify this to simply multiply by (Pi / 180), we get:
// * Half: 0.01745 6054 6875
// * Single: 0.01745 3292 384743690 49072265625
// * Double: 0.01745 3292 519943295 4743716805978692718781530857086181640625
// * Actual: 0.01745 3292 519943295 7692369076848861271344287188854172545609719144...
//
// Neither of these end up "perfect". There will be some cases where they will trade
// in terms of closeness to the "infinitely precise result". Over the entire domain
// however, doing the separate multiplications tends to produce overall more accurate
// results. It helps ensure the implementation can be trivial for the DIM case, and
// covers the vast majority of typical inputs more efficiently largely only pessimizing
// the case where the first multiplication results in overflow.
//
// This is particularly true for `RadiansToDegrees` where 180 is exactly representable
// and so allows an exactly representable intermediate value to be computed when overflow
// doesn't occur.

return (degrees * TSelf.Pi) / TSelf.CreateChecked(180);
}

/// <summary>Converts a given value from radians to degrees.</summary>
/// <param name="radians">The value to convert to degrees.</param>
/// <returns>The value of <paramref name="radians" /> converted to degrees.</returns>
static virtual TSelf RadiansToDegrees(TSelf radians)
{
// We don't want to simplify this to: radians * (180 / Pi)
// See DegreesToRadians for a longer explanation as to why

return (radians * TSelf.CreateChecked(180)) / TSelf.Pi;
}

/// <summary>Computes the sine of a value.</summary>
/// <param name="x">The value, in radians, whose sine is to be computed.</param>
/// <returns>The sine of <paramref name="x" />.</returns>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1851,6 +1851,24 @@ private static bool TryConvertTo<TOther>(NFloat value, [MaybeNullWhen(false)] ou
/// <inheritdoc cref="ITrigonometricFunctions{TSelf}.CosPi(TSelf)" />
public static NFloat CosPi(NFloat x) => new NFloat(NativeType.CosPi(x._value));

/// <inheritdoc cref="ITrigonometricFunctions{TSelf}.DegreesToRadians(TSelf)" />
public static NFloat DegreesToRadians(NFloat degrees)
{
// NOTE: Don't change the algorithm without consulting the DIM
// which elaborates on why this implementation was chosen

return new NFloat(NativeType.DegreesToRadians(degrees._value));
}

/// <inheritdoc cref="ITrigonometricFunctions{TSelf}.RadiansToDegrees(TSelf)" />
public static NFloat RadiansToDegrees(NFloat radians)
{
// NOTE: Don't change the algorithm without consulting the DIM
// which elaborates on why this implementation was chosen

return new NFloat(NativeType.RadiansToDegrees(radians._value));
}

/// <inheritdoc cref="ITrigonometricFunctions{TSelf}.Sin(TSelf)" />
public static NFloat Sin(NFloat x) => new NFloat(NativeType.Sin(x._value));

Expand Down
18 changes: 18 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/Single.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1768,6 +1768,24 @@ public static float CosPi(float x)
return result;
}

/// <inheritdoc cref="ITrigonometricFunctions{TSelf}.DegreesToRadians(TSelf)" />
public static float DegreesToRadians(float degrees)
{
// NOTE: Don't change the algorithm without consulting the DIM
// which elaborates on why this implementation was chosen

return (degrees * Pi) / 180.0f;
}

/// <inheritdoc cref="ITrigonometricFunctions{TSelf}.RadiansToDegrees(TSelf)" />
public static float RadiansToDegrees(float radians)
{
// NOTE: Don't change the algorithm without consulting the DIM
// which elaborates on why this implementation was chosen

return (radians * 180.0f) / Pi;
}

/// <inheritdoc cref="ITrigonometricFunctions{TSelf}.Sin(TSelf)" />
[Intrinsic]
public static float Sin(float x) => MathF.Sin(x);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1331,6 +1331,7 @@ public static void Free(void* ptr) { }
public static System.Runtime.InteropServices.NFloat CreateChecked<TOther>(TOther value) where TOther : System.Numerics.INumberBase<TOther> { throw null; }
public static System.Runtime.InteropServices.NFloat CreateSaturating<TOther>(TOther value) where TOther : System.Numerics.INumberBase<TOther> { throw null; }
public static System.Runtime.InteropServices.NFloat CreateTruncating<TOther>(TOther value) where TOther : System.Numerics.INumberBase<TOther> { throw null; }
public static System.Runtime.InteropServices.NFloat DegreesToRadians(System.Runtime.InteropServices.NFloat degrees) { throw null; }
public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; }
public bool Equals(System.Runtime.InteropServices.NFloat other) { throw null; }
public static System.Runtime.InteropServices.NFloat Exp(System.Runtime.InteropServices.NFloat x) { throw null; }
Expand Down Expand Up @@ -1464,6 +1465,7 @@ public static void Free(void* ptr) { }
public static System.Runtime.InteropServices.NFloat Parse(string s, System.Globalization.NumberStyles style, System.IFormatProvider? provider) { throw null; }
public static System.Runtime.InteropServices.NFloat Parse(string s, System.IFormatProvider? provider) { throw null; }
public static System.Runtime.InteropServices.NFloat Pow(System.Runtime.InteropServices.NFloat x, System.Runtime.InteropServices.NFloat y) { throw null; }
public static System.Runtime.InteropServices.NFloat RadiansToDegrees(System.Runtime.InteropServices.NFloat radians) { throw null; }
public static System.Runtime.InteropServices.NFloat ReciprocalEstimate(System.Runtime.InteropServices.NFloat x) { throw null; }
public static System.Runtime.InteropServices.NFloat ReciprocalSqrtEstimate(System.Runtime.InteropServices.NFloat x) { throw null; }
public static System.Runtime.InteropServices.NFloat RootN(System.Runtime.InteropServices.NFloat x, int n) { throw null; }
Expand Down
Loading

0 comments on commit 40480e8

Please sign in to comment.