[Python-ideas] Fwd: Trigonometry in degrees

Tim Peters tim.peters at gmail.com
Wed Jun 13 15:37:05 EDT 2018

[Richard Damon]

> My first comment is that special casing values like this can lead to
> some very undesirable properties when you use the function for numerical
> analysis. Suddenly your sind is no longer continuous (sind(x) is no
> longer the limit of sind(x+d) as d goes to 0).
> As I stated in my initial comment on this, if you are going to create a
> sind function with the idea that you want 'nice' angles to return
> 'exact' results, then what you need to do is have the degree based trig
> routines do the angle reduction in degrees, and only when you have a
> small enough angle, either use the radians version on the small angle or
> directly include an expansion in degrees.
> ...

Either way, it's necessary to get the effect of working in greater than
output precision, if it's desired that the best possible result be returned
for cases well beyond just the handful of "nice integer inputs" people
happen to be focused on today.

So I'll say again that the easiest way to do that is to use `mpmath` to get
extra precision directly.

The following does that for sindg, cosdg, and tandg.

- There are no special cases.  Although tandg(90 + i*180) dies with
ZeroDivisionError inside mpmath, and that could/should be fiddled to return
an infinity instead.

- Apart from that, all functions appear to give the best possible
double-precision result for all representable-as-a-double integer degree
inputs (sindg(30), cosdg(-100000), doesn't matter).

- And for all representable inputs of the form `integer + j/32` for j in

- But not for all of the form `integer + j/64` for j in range(1, 64, 2).  A
few of those suffer greater than 1/2 ULP error.  Setting EXTRAPREC to 16 is
enough to repair those - but why bother? ;-)

- Consider the largest representable double less than 90:

>>> x
>>> x.hex()

The code below gives the best possible tangent:

>>> tandg(x)

Native precision is waaaaay off:

>>> math.tan(math.radians(x))

It's not really the extra precision that saves the code below, but allowing
argument reduction to reduce to the range [-pi/4, pi/4] radians, followed
by exploiting trigonometric identities.  In this case, exploiting that
tan(pi/2 + z) = -1/tan(z).  Then even native precision is good enough:

>>> -1 / math.tan(math.radians(x - 90))

Here's the code:

    import mpmath
    from math import fmod
    # Return (n, x) such that:
    # 1. d degrees is equivalent to x + n*(pi/2) radians.
    # 2. x is an mpmath float in [-pi/4, pi/4].
    # 3. n is an integer in range(4).
    # There is one potential rounding error, when mpmath.radians() is
    # used to convert a number of degrees between -45 and 45.  This is
    # done using the current mpmath precision.
    def treduce(d):
        d = fmod(d, 360.0)
        n = round(d / 90.0)
        assert -4 <= n <= 4
        d -= n * 90.0
        assert -45.0 <= d <= 45.0
        return n & 3, mpmath.radians(d)

    EXTRAPREC = 14
    def sindg(d):
        with mpmath.extraprec(EXTRAPREC):
            n, x = treduce(d)
            if n & 1:
                x = mpmath.cos(x)
                x = mpmath.sin(x)
            if n >= 2:
                x = -x
            return float(x)

    def cosdg(d):
        with mpmath.extraprec(EXTRAPREC):
            n, x = treduce(d)
            if n & 1:
                x = mpmath.sin(x)
                x = mpmath.cos(x)
            if 1 <= n <= 2:
                x = -x
            return float(x)

    def tandg(d):
        with mpmath.extraprec(EXTRAPREC):
            n, x = treduce(d)
            x = mpmath.tan(x)
            if n & 1:
                x = -1.0 / x
            return float(x)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180613/50637bef/attachment.html>

More information about the Python-ideas mailing list