You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Part of #78. Depends on #79 (argument reduction cannot be more accurate than the π it reduces by), on #80 (Atan and Asin need Sqrt) and on #81 (shared series machinery).
Design
Argument reduction
Sin and Cos reduce modulo 2π, then into [-π/4, π/4] with an octant index, so one kernel serves both and the series stays short.
To get n correct digits from an argument of magnitude 10^d, π is needed to roughly d + n digits. That is the entire reason #79 blocks this issue, and the reduction should call PiTo(workingDigits + argumentDigits) rather than the Pi property so the requirement is explicit in the code.
Kernel
Taylor, with argument halving first — sin x = 2 sin(x/2) cos(x/2) applied m times — so the series argument is small and converges in a handful of terms. Every term truncated with ReduceSignificance(workingDigits).
The individual members
Tan is Sin / Cos with a single Divide at the end, not two independent series.
SinCos computes both from one reduction. It exists in the interface precisely so callers do not pay for the reduction twice, and the motivating workload (SGP4) calls it constantly — so implementing it as two separate calls would miss the point of the member.
Atan reduces with atan x = 2·atan( x / (1 + √(1 + x²)) ), applied until |x| is small, then Taylor. This is where the Sqrt dependency on #80 comes from.
Asin / Acos via Atan and Sqrt: asin x = atan( x / √(1 - x²) ), with asin(±1) = ±π/2 special-cased because the division is by zero exactly there.
The …Pi family (SinPi, CosPi, TanPi, AsinPi, AcosPi, AtanPi) takes its argument in half-turns, and must reduce modulo 2 on the argument before multiplying by π. Implementing them as Sin(x * Pi) throws away exactly the accuracy they exist to preserve — SinPi(1e20) is well-defined and Sin(1e20 * Pi) is not.
DegreesToRadians / RadiansToDegrees are straightforward, with one discipline borrowed from ktsu.Semantics: anything built on π is a correctly-rounded literal at full precision, never derived from another literal. Semantics carried an error from DegreeToRadians' 98th digit into every factor taken from it, and the fix was to stop deriving.
Atan2 is not in the interface
Atan2 lives on IFloatingPointIeee754<T>, which PreciseNumber should not implement — it is not IEEE-754 and has no NaN or infinity to give the interface's edge cases meaning. So add it as a bespoke static:
Quadrant dispatch on Atan(y / x), with x == 0 and y == 0 handled explicitly. It is not optional despite not being interface-driven: SGP4 needs it in several places, and so does every conversion from Cartesian to spherical coordinates.
Tests
sin, cos, tan of 0, π/6, π/4, π/3, π/2 and 1 against published 50-digit values
Part of #78. Depends on #79 (argument reduction cannot be more accurate than the π it reduces by), on #80 (
AtanandAsinneedSqrt) and on #81 (shared series machinery).Design
Argument reduction
SinandCosreduce modulo 2π, then into[-π/4, π/4]with an octant index, so one kernel serves both and the series stays short.To get n correct digits from an argument of magnitude 10^d, π is needed to roughly
d + ndigits. That is the entire reason #79 blocks this issue, and the reduction should callPiTo(workingDigits + argumentDigits)rather than thePiproperty so the requirement is explicit in the code.Kernel
Taylor, with argument halving first —
sin x = 2 sin(x/2) cos(x/2)applied m times — so the series argument is small and converges in a handful of terms. Every term truncated withReduceSignificance(workingDigits).The individual members
TanisSin / Coswith a singleDivideat the end, not two independent series.SinCoscomputes both from one reduction. It exists in the interface precisely so callers do not pay for the reduction twice, and the motivating workload (SGP4) calls it constantly — so implementing it as two separate calls would miss the point of the member.Atanreduces withatan x = 2·atan( x / (1 + √(1 + x²)) ), applied until|x|is small, then Taylor. This is where theSqrtdependency on #80 comes from.Asin/AcosviaAtanandSqrt:asin x = atan( x / √(1 - x²) ), withasin(±1) = ±π/2special-cased because the division is by zero exactly there.The
…Pifamily (SinPi,CosPi,TanPi,AsinPi,AcosPi,AtanPi) takes its argument in half-turns, and must reduce modulo 2 on the argument before multiplying by π. Implementing them asSin(x * Pi)throws away exactly the accuracy they exist to preserve —SinPi(1e20)is well-defined andSin(1e20 * Pi)is not.DegreesToRadians/RadiansToDegreesare straightforward, with one discipline borrowed fromktsu.Semantics: anything built on π is a correctly-rounded literal at full precision, never derived from another literal. Semantics carried an error fromDegreeToRadians' 98th digit into every factor taken from it, and the fix was to stop deriving.Atan2is not in the interfaceAtan2lives onIFloatingPointIeee754<T>, which PreciseNumber should not implement — it is not IEEE-754 and has no NaN or infinity to give the interface's edge cases meaning. So add it as a bespoke static:Quadrant dispatch on
Atan(y / x), withx == 0andy == 0handled explicitly. It is not optional despite not being interface-driven: SGP4 needs it in several places, and so does every conversion from Cartesian to spherical coordinates.Tests
sin²x + cos²x = 1to 49 digits across a sweepSin(10^6)against a 150-digit reference. This is the test that would fail today if the constants were left at 26 digits, so it is the one that ties this issue to Pi carries 26 significant digits and Tau 25, fewer than MinimumDivisionPrecision, and Pi is truncated rather than rounded #79SinCosagrees with separateSinandCoscalls exactlySinPi(1e20)is correct — the test that catches a naiveSin(x * Pi)implementationAtan2in all four quadrants and on both axes, includingAtan2(0, 0)Math.Sin/Math.Cos/Math.Atan2to 15 digits, as the cheap regression netDigitsaxis (8 / 30 / 200) with allocation reported