// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace System.Numerics { /// <summary>Defines support for trigonometric functions.</summary> /// <typeparam name="TSelf">The type that implements this interface.</typeparam> public interface ITrigonometricFunctions<TSelf> : IFloatingPointConstants<TSelf> where TSelf : ITrigonometricFunctions<TSelf>? { /// <summary>Computes the arc-cosine of a value.</summary> /// <param name="x">The value whose arc-cosine is to be computed.</param> /// <returns>The arc-cosine of <paramref name="x" />.</returns> /// <remarks>This computes <c>arccos(x)</c> in the interval <c>[+0, +PI]</c> radians.</remarks> static abstract TSelf Acos(TSelf x); /// <summary>Computes the arc-cosine of a value and divides the result by <c>pi</c>.</summary> /// <param name="x">The value whose arc-cosine is to be computed.</param> /// <returns>The arc-cosine of <paramref name="x" />, divided by <c>pi</c>.</returns> /// <remarks>This computes <c>arccos(x) / PI</c> in the interval <c>[-0.5, +0.5]</c>.</remarks> static abstract TSelf AcosPi(TSelf x); /// <summary>Computes the arc-sine of a value.</summary> /// <param name="x">The value whose arc-sine is to be computed.</param> /// <returns>The arc-sine of <paramref name="x" />.</returns> /// <remarks>This computes <c>arcsin(x)</c> in the interval <c>[-PI / 2, +PI / 2]</c> radians.</remarks> static abstract TSelf Asin(TSelf x); /// <summary>Computes the arc-sine of a value and divides the result by <c>pi</c>.</summary> /// <param name="x">The value whose arc-sine is to be computed.</param> /// <returns>The arc-sine of <paramref name="x" />, divided by <c>pi</c>.</returns> /// <remarks>This computes <c>arcsin(x) / PI</c> in the interval <c>[-0.5, +0.5]</c>.</remarks> static abstract TSelf AsinPi(TSelf x); /// <summary>Computes the arc-tangent of a value.</summary> /// <param name="x">The value whose arc-tangent is to be computed.</param> /// <returns>The arc-tangent of <paramref name="x" />.</returns> /// <remarks>This computes <c>arctan(x)</c> in the interval <c>[-PI / 2, +PI / 2]</c> radians.</remarks> static abstract TSelf Atan(TSelf x); /// <summary>Computes the arc-tangent of a value and divides the result by pi.</summary> /// <param name="x">The value whose arc-tangent is to be computed.</param> /// <returns>The arc-tangent of <paramref name="x" />, divided by <c>pi</c>.</returns> /// <remarks>This computes <c>arctan(x) / PI</c> in the interval <c>[-0.5, +0.5]</c>.</remarks> static abstract TSelf AtanPi(TSelf x); /// <summary>Computes the cosine of a value.</summary> /// <param name="x">The value, in radians, whose cosine is to be computed.</param> /// <returns>The cosine of <paramref name="x" />.</returns> /// <remarks>This computes <c>cos(x)</c>.</remarks> static abstract TSelf Cos(TSelf x); /// <summary>Computes the cosine of a value that has been multipled by <c>pi</c>.</summary> /// <param name="x">The value, in half-revolutions, whose cosine is to be computed.</param> /// <returns>The cosine of <paramref name="x" /> multiplied-by <c>pi</c>.</returns> /// <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. // // Which of the two forms wins is decided by where the nearest representable `pi / 180` // falls relative to the infinitely precise value, which lands more or less uniformly for // each number of significand bits. Folding therefore ranges from far worse to far better // depending on the type, while this form stays between 55% and 75% correctly rounded // everywhere. A DIM cannot pick the better half of that, since it can only reach // `TSelf.Pi` and so must round twice to get there. // // A concrete type has neither constraint. It can name the constant directly and knows // what its hardware and any wider type give it, so it can be correctly rounded over the // whole domain, which every type here does and any other implementation is free to do. 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> /// <remarks>This computes <c>sin(x)</c>.</remarks> static abstract TSelf Sin(TSelf x); /// <summary>Computes the sine and cosine of a value.</summary> /// <param name="x">The value, in radians, whose sine and cosine are to be computed.</param> /// <returns>The sine and cosine of <paramref name="x" />.</returns> /// <remarks>This computes <c>(sin(x), cos(x))</c>.</remarks> static abstract (TSelf Sin, TSelf Cos) SinCos(TSelf x); /// <summary>Computes the sine and cosine of a value that has been multiplied by <c>pi</c>.</summary> /// <param name="x">The value, in half-revolutions, that is multipled by <c>pi</c> before computing its sine and cosine.</param> /// <returns>The sine and cosine of<paramref name="x" /> multiplied-by <c>pi</c>.</returns> /// <remarks>This computes <c>(sin(x * PI), cos(x * PI))</c>.</remarks> static abstract (TSelf SinPi, TSelf CosPi) SinCosPi(TSelf x); /// <summary>Computes the sine of a value that has been multiplied by <c>pi</c>.</summary> /// <param name="x">The value, in half-revolutions, that is multipled by <c>pi</c> before computing its sine.</param> /// <returns>The sine of <paramref name="x" /> multiplied-by <c>pi</c>.</returns> /// <remarks>This computes <c>sin(x * PI)</c>.</remarks> static abstract TSelf SinPi(TSelf x); /// <summary>Computes the tangent of a value.</summary> /// <param name="x">The value, in radians, whose tangent is to be computed.</param> /// <returns>The tangent of <paramref name="x" />.</returns> /// <remarks>This computes <c>tan(x)</c>.</remarks> static abstract TSelf Tan(TSelf x); /// <summary>Computes the tangent of a value that has been multipled by <c>pi</c>.</summary> /// <param name="x">The value, in half-revolutions, that is multipled by <c>pi</c> before computing its tangent.</param> /// <returns>The tangent of <paramref name="x" /> multiplied-by <c>pi</c>.</returns> /// <remarks>This computes <c>tan(x * PI)</c>.</remarks> static abstract TSelf TanPi(TSelf x); } }