<div dir="ltr"><div class="gmail_quote"><div dir="ltr">[Richard Damon]</div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">My first comment is that special casing values like this can lead to<br>
some very undesirable properties when you use the function for numerical<br>
analysis. Suddenly your sind is no longer continuous (sind(x) is no<br>
longer the limit of sind(x+d) as d goes to 0).<br>
<br>
As I stated in my initial comment on this, if you are going to create a<br>
sind function with the idea that you want 'nice' angles to return<br>
'exact' results, then what you need to do is have the degree based trig<br>
routines do the angle reduction in degrees, and only when you have a<br>
small enough angle, either use the radians version on the small angle or<br>
directly include an expansion in degrees.<br>...<br></blockquote><div><br></div><div>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.<br><br>So I'll say again that the easiest way to do that is to use `mpmath` to get extra precision directly.<br><br>The following does that for sindg, cosdg, and tandg.<br><br>- 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.<br><br>- 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).<br><br>- And for all representable inputs of the form `integer + j/32` for j in range(32).<br><br>- 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? ;-)<br><br>- Consider the largest representable double less than 90:<br><br><div>>>> x</div><div>89.99999999999999</div><div>>>> x.hex()</div><div>'0x1.67fffffffffffp+6'</div><div><br>The code below gives the best possible tangent:<br><br><div>>>> tandg(x)</div><div>4031832051015932.0</div></div></div><div><br>Native precision is waaaaay off:<br><br><div>>>> math.tan(math.radians(x))</div><div>3530114321217157.5</div></div><div><br>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:<br><br><div>>>> -1 / math.tan(math.radians(x - 90))</div><div>4031832051015932.0<br><br>Here's the code:<br><br><div>    import mpmath</div><div>    from math import fmod</div><div>    # Return (n, x) such that:</div><div>    # 1. d degrees is equivalent to x + n*(pi/2) radians.</div><div>    # 2. x is an mpmath float in [-pi/4, pi/4].</div><div>    # 3. n is an integer in range(4).</div><div>    # There is one potential rounding error, when mpmath.radians() is</div><div>    # used to convert a number of degrees between -45 and 45.  This is</div><div>    # done using the current mpmath precision.</div><div>    def treduce(d):</div><div>        d = fmod(d, 360.0)</div><div>        n = round(d / 90.0)</div><div>        assert -4 <= n <= 4</div><div>        d -= n * 90.0</div><div>        assert -45.0 <= d <= 45.0</div><div>        return n & 3, mpmath.radians(d)</div><div><br></div><div>    EXTRAPREC = 14</div><div>    def sindg(d):</div><div>        with mpmath.extraprec(EXTRAPREC):</div><div>            n, x = treduce(d)</div><div>            if n & 1:</div><div>                x = mpmath.cos(x)</div><div>            else:</div><div>                x = mpmath.sin(x)</div><div>            if n >= 2:</div><div>                x = -x</div><div>            return float(x)</div><div><br></div><div>    def cosdg(d):</div><div>        with mpmath.extraprec(EXTRAPREC):</div><div>            n, x = treduce(d)</div><div>            if n & 1:</div><div>                x = mpmath.sin(x)</div><div>            else:</div><div>                x = mpmath.cos(x)</div><div>            if 1 <= n <= 2:</div><div>                x = -x</div><div>            return float(x)</div><div><br></div><div>    def tandg(d):</div><div>        with mpmath.extraprec(EXTRAPREC):</div><div>            n, x = treduce(d)</div><div>            x = mpmath.tan(x)</div><div>            if n & 1:</div><div>                x = -1.0 / x</div><div>            return float(x)</div></div></div><div><br></div></div></div>