Skip to content

Implement ITrigonometricFunctions<PreciseNumber>, plus Atan2 #82

Description

@matt-edmondson

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:

public static PreciseNumber Atan2(PreciseNumber y, PreciseNumber x);
public static PreciseNumber Atan2(PreciseNumber y, PreciseNumber x, int significantDigits);

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
  • sin²x + cos²x = 1 to 49 digits across a sweep
  • Reduction at large arguments: Sin(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 #79
  • SinCos agrees with separate Sin and Cos calls exactly
  • SinPi(1e20) is correct — the test that catches a naive Sin(x * Pi) implementation
  • Atan2 in all four quadrants and on both axes, including Atan2(0, 0)
  • Agreement with Math.Sin / Math.Cos / Math.Atan2 to 15 digits, as the cheap regression net
  • Benchmarks across the Digits axis (8 / 30 / 200) with allocation reported

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions