# When should a trig function, with a degree argument, return -0.0?

In creating trig functions `my_sind(d)`, `my_cosd(d)`, `my_tand(d)`, that used a degree argument rather than a radian one and provided exact answers at multiples of 90, I noticed that the result was sometimes `-0.0` rather than `0.0`.

``````my_sind( 0.0) -->  0.0
my_sind(-0.0) --> -0.0

my_sind(180.0) --> -0.0
my_sind(360.0) -->  0.0
``````

`sin()` and `tan()` typically return the same sign zero result for a given sign zero input. It makes sense that `my_sin()` should match `sin()` for those inputs.

``````my_sind( 0.0) alike sin( 0.0) -->  0.0
my_sind(-0.0) alike sin(-0.0) --> -0.0
``````

The question is: for what whole number `non_zero_n` should/may the result ever return `-0.0` for `my_sind(180*non_zero_n)`, `my_cosd(180*n + 180)`, `my_tand(180*non_zero_n)`?

It is easy enough to code so only `f(-0.0)` produces `-0.0` and be done with it. Simple wondering if there is any reason to make other `f(x)` return `-0.0` for any other (non-zero) `x` and the importance of insuring that sign.

Note: This is not a question of why `0.0` vs. `-0.0` occurs. This is not why `cos(machine_pi/4)` does not return `0.0`. Neither is this a question of how to control the generation of `0.0` or `-0.0`. I see it best as a design question.

The design principle of "least surprise" suggests that we look to previously established functionality for guidance. In this case, the closest established functionality is provided by `sinpi` and `cospi` functions introduced in IEEE Std 754-2008 (the IEEE Standard for Floating-Point Arithmetic), section 9. These functions are not part of the current ISO C and ISO C++ standards, but have been incorporated into the math libraries of various programming platforms, for example CUDA.

These functions compute sin(πx) and cos(πx), where multiplication with π occurs implicitly inside the function. `tanpi` is not defined, but could, based on mathematical equivalency, be assumed to provide functionality according to `tanpi(x) = sinpi(x) / cospi(x)`.

We can now define `sind(x) = sinpi(x/180)`, `cosd(x) = cospi(x/180)`, `tand(x) = tanpi(x/180)` in an intuitive manner. Section 9.1.2 of IEEE-754 spells out the handling of special arguments for `sinpi` and `cospi`. In particular:

sinPi(+n) is +0 and sinPi(−n) is −0 for positive integers n. This implies, under appropriate rounding modes, that sinPi(−x) and −sinPi(x) are the same number (or both NaN) for all x. cosPi(n + ½) is +0 for any integer n when n + ½ is representable.

The IEEE 754-2008 standard does not give a rationale for the quoted requirements, however, an early draft of the relevant section states:

If the value of the function is zero, the sign of this 0 is best determined by considering the continuous extension of the sign function of the mathematical function.

Perusal of the 754 Working Group Mail Archive may yield additional insights, I have not had the time to dig through it. Implementing `sind()`, `cosd()`, and `tand()` as described above, we then arrive at this table of example cases:

``````SIND
angle value
-540 -0
-360 -0
-180 -0
0  0
180  0
360  0
540  0

COSD
angle value
-630  0
-450  0
-270  0
-90  0
90  0
270  0
450  0

TAND
angle value
-540  0
-360 -0
-180  0
0  0
180 -0
360  0
540 -0
``````

sin() and tan() typically return the same sign zero result for a given sign zero input

It could be generally true since:

• Speed/accuracy. For small enough doubles, the best answer for `sin(x)` is `x`. That is, for numbers smaller than about `1.49e-8`, the closest double to the sine of x is actually x itself (see the glibc source code for sin()).

• Handling of special cases.

A few extraordinary arithmetic operations are affected by zero's sign; for example `"1/(+0) = +inf"` but `"1/(-0) = -inf"`. To retain its usefulness, the sign bit must propagate through certain arithmetic operations according to rules derived from continuity considerations.

Implementations of elementary transcendental functions like sin(z) and tan(z) and their inverses and hyperbolic analogs, though not specified by the IEEE standards, are expected to follow similar rules. The implementation of `sin(z)` is expected to reproduce the sign of `z` as well as its value at `z = ±O`.

(Branch Cuts for Complex Elementary Functions or Much Ado About Nothing's Sign Bit by W. Kahan)

Negatively signed zero echoes the mathematical analysis concept of approaching 0 from below as a one-sided limit (consider `1 / sin(x)`: the sign of zero makes a huge difference).

EDIT

Considering the second point I'd write `my_sind` so that:

``````my_sind(-0.0) is -0.0
my_sind(0.0) is 0.0
``````

The latest C standard (F.10.1.6 `sin` and F.10.1.7 `tan`, implementations with a signed zero), specifies that if the argument is `±0`, it is returned unmodified.

EDIT 2

For the other values I think it's a matter of approximation. Given `M_PI` < π:

``````0 = sin(π) < sin(M_PI) ≈ 1.2246467991473532e-16 ≈ +0.0
0 = sin(-π) > sin(-M_PI) ≈ -1.2246467991473532e-16 ≈ -0.0
0 = sin(2*π) > sin(2*M_PI) ≈ -2.4492935982947064e-16
0 = sin(-2*π) < sin(-2*M_PI) ≈ 2.4492935982947064e-16
``````

So if `my_sind` provides exact answers at multiples of 180° it can return `+0.0` or `-0.0` (I don't see a clear reason to prefer one over the other).

If `my_sind` uses some approximation (e.g. a `degree * M_PI / 180.0` conversion formula), it should consider how it's approaching the critical values.

• What are your thoughts concerning `sind(180), sind(-180), sind(360), sind(-360),...`? Nov 3, 2014 at 12:33
• Thanks for the update. Maybe my post is not clear. The main question is should `my_trig(x)` ever return `-0.0` when `|x|` is not `0.0`? Nov 3, 2014 at 15:33
• Thanks for the "So if my_sind provides exact answers at multiples of 180° it can return +0.0 or -0.0 (I don't see a clear reason to prefer one over the other)." It is the closest discussion point so far. I'm thinking that "principle of least astonishment" encourages always returning `+0.0`, but looking to see if there are compelling reasons to return `-0.0` in some situations (other than `x == +/-0.0`). Nov 3, 2014 at 18:22
• @chux: I think for multiples of `180.0`, one really has to examine the values of relative machine precision given those values. That is, the smallest increment/decrement that gives a different representable value in that numerical format. Then, compare that value with the true value to see whether it would fall on the plus side or minus side. Jan 3, 2015 at 22:23
• @rwong Thanks for the idea. Multiples of 90.0 degrees, the exact `sind(double degrees)` and `cosd(double degrees)` value can be returned: `-1.0, +0.0, +1.0`. This post is about should `-0.0` ever be returned (aside from sind(-0.0)). Note: `sind()` does not use the simplistic `sin(x/360*M_PI)` approach. Jan 4, 2015 at 1:50

The library doesn't try to distinguish +0 from -0. IEEE 754 worries quite a bit about this distinction...I found the functions [in math.h] quite hard enough to write without fretting about the sign of nothing. -- P.J. Plauger, The Standard C Library, 1992, page 128.

Formally, trig functions should return the sign of zero in accord with the C standard...which leaves the behavior undefined.

In the face of undefined behavior, the principle of least astonishment suggests duplicating the behavior of the corresponding function from `math.h`. This smells justifiable, while diverging from the behavior of the corresponding function in `math.h` smells like a way to introduce bugs into exactly the code which depends on the sign of zero.

• The trig functions in `math.h` do not return 0.0 when given arguments like +/-pi/2 or +/-pi as these functions can only take representable values near +/-pi/2,etc. These "near" values return results near 0.0. Since the std library trig functions (`sin cos tan`) do not return 0.0 (or -0.0) for any input (except +/-0.0), but my_sind(), my_cosd(), my_tand() can return 0.0 (or -0.0) there is no 0.0 behavior to duplicate. Nov 3, 2014 at 15:28
• @chux The premise that `sin(-0.0)` should return `-0` is suspect. It treats an implementation detail of the IEEE standard as a trigonometric principle. Although there is a general mathematical principle of zero as the limit of two intervals embodied in the IEEE implementation, it occurs at that level of abstraction not within general trigonometry [hence the variability in what your trigonometric functions return]. The best that can happen is that you can define an arbitrary convention, but it will be divergent from `math.h`'s nonchalance over the sign of zero. Nov 3, 2014 at 16:35
• Note: I am not suggesting `sin(-0.0)` should return `-0.0`, but that `my_sind(x)` should match `sin(x)` when `x` is `+/-0.0`. IOW: follow previous practice. Further the question itself is more about what to do when `x != 0.0`, should `my_sind(x)` ever return `-0.0` as in `my_sind(180)`, etc? Maybe your answer / comment addresses that - but I have not seen that. Nov 3, 2014 at 16:50
• @chux If the behavior is undefined, then it's undefined. That's just the way C is. Plauger didn't worry about `+0` versus `-0` when he wrote `math.h` twenty years ago. It's not clear to me what problem your fretting about the difference is solving. Nov 3, 2014 at 17:50
• Hopefully you see that for a well implemented `sin(rad)` for any value `rad>0` and of any precision will never yield `0.0` as pi is irrational. [Ref](www.csee.umbc.edu/~phatak/645/supl/Ng-ArgReduction.pdf) However `my_sind(deg)` yields an exact `0.0` (either + or -) every multiple of `180.0` as the value 0.0 is the correct mathematical result. "Principle of least astonishment" suggests returning 0.0 in these cases. My question is should `-0.0` ever be returned in these cases? Nov 3, 2014 at 18:17