<div dir="ltr"><div class="gmail_quote"><div dir="ltr">[Steven D'Aprano]</div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">... </blockquote><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">The initial proposal is fine: a separate set of trig functions that take <br>
their arguments in degrees would have no unexpected surprises (only the <br>
expected ones). With a decent implementation i.e. not this one:<br>
<br>
    # don't do this<br>
    def sind(angle):<br>
        return math.sin(math.radians(angle))<br></blockquote><div><br>But that's good enough for almost all real purposes.  Indeed, except for argument reduction, it's essentially how scipy's sindg and cosdg _are_ implemented:<br><br><div><a href="https://github.com/scipy/scipy/blob/master/scipy/special/cephes/sindg.c">https://github.com/scipy/scipy/blob/master/scipy/special/cephes/sindg.c</a></div><div> <br></div></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
and equivalent for cos, we ought to be able to get correctly rounded <br>
values for nearly all the "interesting" angles of the circle, without <br>
those pesky rounding issues caused by π not being exactly representable <br>
as a float.<br><br></blockquote><div>If people are overly ;-) worried about tiny rounding errors, just compute things with some extra bits of precision to absorb them.  For example, install `mpmath` and use this:<br><br>     def sindg(d):</div><div>        import math, mpmath</div><div>        d = math.fmod(d, 360.0)</div><div>        if abs(d) == 180.0:</div><div>            return 0.0</div><div>        with mpmath.extraprec(12):</div><div>            return float(mpmath.sin(mpmath.radians(d)))</div><div><br>Then, e.g,<br><br><div>>>> for x in (0, 30, 90, 150, 180, 210, 270, 330, 360):</div><div>...     print(x, sindg(x))</div><div>0 0.0<br></div><div>30 0.5</div><div>90 1.0</div><div>150 0.5</div><div>180 0.0</div><div>210 -0.5</div><div>270 -1.0</div><div>330 -0.5</div><div>360 0.0</div></div><div><br>Notes:</div><div><br></div><div>1. Python's float "%" is unsuitable for argument reduction; e.g.,<br><br><div>>>> -1e-14 % 360.0</div><div>360.0<br><br>`math.fmod` is suitable, because it's exact:<br><br></div><div>>>> math.fmod(-1e-14, 360.0)</div><div>-1e-14</div></div><div><br>2. Using a dozen extra bits of precision make it very likely you'll get the correctly rounded 53-bit result; it will almost certainly (barring bugs in `mpmath`) always be good to less than 1 ULP.<br><br>3. Except for +-180.  No matter how many bits of float precision (including the number of bits used to approximate pi) are used, converting that to radians can never yield the mathematical `pi`; and sin(pi+x) is approximately equal to -x for tiny |x|; e.g., here with a thousand bits:<br><br>>>> mpmath.mp.prec = 1000<br><div>>>> float(mpmath.sin(mpmath.radians(180)))</div><div>1.2515440597544546e-301<br><br>So +-180 is special-cased.  For cosdg, +-{90. 270} would need to be special-cased for the same reason.<br><br></div></div></div></div>