Most languages seem to only have sin
cos
and tan
. While the other 3 are just 1/cos
1/sin
and 1/tan
, is there any way that supporting the other 3 directly could be faster than calculating one and then performing a division? Are there any other reasons beyond the triviality of deriving the other csc
sec
and cot
from the existing sin
cos
and tan
to exclude them?
5 Answers
This does just roll the barrel slightly lower on the hill (or whatever the saying is), but:
The IEEE 754 floating point standard includes the following trig-related operations as "recommended"
- sin(x)
- cos(x)
- tan(x)
- arcsin(x)
- arccos(x)
- arctan(x)
- atan2(y, x)
Since the 2019 revision, these are also recommended (though I've never seen these):
- sinPi(x) = sin(pi * x)
- cosPi(x)
- tanPi(x)
- arcsinPi(x) = arcsin(x) / pi
- arccosPi(x)
- arctanPi(x)
- atan2Pi(y, x) = atan2(y, x) / pi
Now, why does IEEE 754 not recommend (arc)sec/csc/cot I have no idea.
-
4$\begingroup$ Just as a side note I really like the
*Pi
functions because a) They're more precise and b) I don't need to manually multiply by pi $\endgroup$– CPlusCommented Jun 29, 2023 at 18:24 -
1$\begingroup$ Do you have a link to the IEEE document that recommends these functions? $\endgroup$ Commented Jun 29, 2023 at 18:38
-
1$\begingroup$ @mousetail Sadly, the IEEE-754 standard costs money. $\endgroup$ Commented Jun 30, 2023 at 1:09
-
4$\begingroup$ I am surprised to see you say that you've never seen
sinpi
and friends! Off the top of my head, they are implemented in MATLAB, Julia, in vendor libraries from Intel and Apple, and as of 2023, even the C standard library. $\endgroup$ Commented Jun 30, 2023 at 4:38 -
1$\begingroup$ Or just have your compiler recognise the pattern and replace it with an optimised version. It'd be far more user friendly. $\endgroup$– SK-logicCommented Jun 30, 2023 at 8:48
One possible reason is that it is quite difficult to implement these functions with high accuracy.
For + - * / the IEEE standard requires that the error is at most 1/2 of the least significant bit (0.5 ulp). For sin, cos, tan and others, that limit is quite difficult but not impossible to achieve, while a careful implementation can achieve an error of up to say 0.6ulp without major problems. I think Apple's implementation in C and C++ achieves that for inputs less than 10^15, and I'd hope other implementations do the same.
If you want to calculate 1 / sin x, and do this in the naive way by calculating z = sin x and dividing 1 / z, then your rounding error will be much larger. And 1 / sin x is not a polynomial but close to 1 / x, so if you try to calculate it directly you will also have difficulties keeping the rounding error small (close to 0.5ulp, and definitly less than 1.0ulp).
PS. Just noticed in another post "1 - cos b" was mentioned. Since cos b ≈ 1 - b^2/2 for small b, 1 - cos b could be calculated with much higher precision for small b without going through the cos function.
-
$\begingroup$ Good point, and users who do want high precision often only need it in a certain range of inputs, which might be different to the range of inputs that other users need precision on. I think the same reason is why many standard libraries offer
log (1 + x)
as a built-in function ─ it allows higher precision for smallx
. $\endgroup$– kaya3Commented Jun 30, 2023 at 13:51 -
2$\begingroup$ "For sin, cos, tan and others, that limit is quite difficult but not impossible to achieve" is the classic table-maker dilemma. With high precision floating point, it is certainly infeasible to fully test. AFAIK, Getting a ULP of 0.500...0001 or less is reasonable. ULP <= 0.5 is very challenging. $\endgroup$– chuxCommented Jun 30, 2023 at 15:33
-
4$\begingroup$ Isn't this a good reason why the language should include these functions, because it's hard for the application to implement them itself without losing accuracy? $\endgroup$– BarmarCommented Jun 30, 2023 at 21:51
Older processors had instructions to compute the sine, cosine and tangent of floating-point numbers, but not the secant, cosecant or cotangent. So the reason could just be that especially for older languages, the standard library's mathematical functions were just thin wrappers around the processor's capabilities; and newer languages might have just been copying from those languages ever since.
On the other hand, I can think of a concrete performance reason why it's better not to provide these functions in the standard library. In almost all use-cases, you don't want just the secant, cosecant or cotangent, you want it multiplied by some value; these trigonometric functions are defined as ratios of side lengths in triangles, so you can find an unknown side of a triangle by multiplying a known side by the appropriate trigonometric function of a known angle.
For example, you could write code like this:
let hypotenuse = adjacent * sec(theta);
But if sec(theta)
is a function which returns 1.0 / cos(theta)
then (without compiler optimisations) this code will do an unnecessary multiplication by 1 and also an extra function call. The alternative way of writing it is:
let hypotenuse = adjacent / cos(theta);
For the compiler to optimise the former into the latter, it must inline the sec
function, rewrite the expression a * (1.0 / c)
as (a * 1.0) / c
, and then eliminate the unnecessary multiplication. In reality, even modern compilers don't do this optimisation, because it can change the result slightly, and the compiler can't know that the user doesn't mind the slight difference (or that the latter version is slightly more accurate according to the user's intent).
But even though the version with cos
is more efficient and slightly more accurate, many programmers might presume that if sec
exists in the standard library then it must be better to use it. Therefore, excluding it from the standard library causes users to write the better version with cos
. Exactly the same reasoning applies to cosec
and cot
.
-
2$\begingroup$ The original APL implementation only had the three basic ones from the outset, even though the target processors didn't have any trig instructions at all. $\endgroup$– AdámCommented Jun 29, 2023 at 21:53
-
2$\begingroup$ Modern processors generally don't have instructions to calculate any trig functions, and those that do (e.g. x87) advise you very strongly not to use them. $\endgroup$ Commented Jun 30, 2023 at 1:13
-
1$\begingroup$ @Pseudonym: The x87 advise to not use is because they're bugged and can't be unbugged due to massive compatibility breakage. Too bad really. (They do the wrong thing where the trig functions wrap around.) $\endgroup$– JoshuaCommented Aug 17, 2023 at 23:43
Frequency of use
The main three are vastly more commonly used than the other three.
-
5$\begingroup$ Counterpoint: sinh and cosh are commonly included but used even more rarely can csc and sec $\endgroup$ Commented Jun 29, 2023 at 18:07
-
1$\begingroup$ @mousetail But they are not easiliy defined in terms of the basic 3. $\endgroup$– AdámCommented Jun 29, 2023 at 20:23
-
2$\begingroup$ And perhaps more to the point, calculating csc and sec from cos and sin doesn't involve any significance-losing calculations, since it's just one reciprocal. Calculating sinh from exp can cause problems with cancellation around x=0. $\endgroup$ Commented Jun 30, 2023 at 1:13
-
2$\begingroup$ The frequency of use depends heavily on what you work with, I would claim that sinh and cosh are more commonly used than csc and sec. $\endgroup$ Commented Jun 30, 2023 at 9:50
A very huge system library with functions for trivial, easy to derive features is more difficult to remember. The code using them may easily be less readable. Imagine you see:
a = haversine(b)
Some will know, but for many, Google will be they friend, losing the productive time on that. And when the developer will need to write it next time, one may get in doubt, maybe it was hav
or hsin
or haversed_sine
instead and need to google again. Maybe again. The only benefit, this would boost the page rank of web resources focused on this uncommon function. At the same time,
a = (1 - cos(b) )/2
(the same) would be understood by any developer in a fraction of second. haversine
actually reduces readability, unless needed very often.
P.S: As correctly pointed in the comment to this answer, some rarely used functions make sense if there are code readers that have the fundamental knowledge about them, bringing up lots of useful associations. Maybe haversine is actually not a so good example if used for the task where it is commonly used (not if the expression matched just by chance). In any case the answer argues that smaller API is easier to remember and used without excessive and repeated googling.
P.S: Some "easy" functions may be present in the library if the naive formula may give overflow on intermediate result (like hypot(x, y) = sqrt(x*x + y*y)
) or significant rounding errors. The actual way of computing in this case is actually different from naive.
It is important to keep the balance.
-
6$\begingroup$ I actually disagree. If I read
(1-cos(b))/2
in code I might be mystified and have to wonder why they used that particular formula. If I readhaversine
in code I immediately know that they're using the haversine function, whose exact trigonometric formula I might have forgotten over the years, but whose usefulness in latitude/longitude I remember very well. So in this case, the name of the function is more important than the formula, and having a named function makes the code much easier to understand at a glance. $\endgroup$– StefCommented Jun 30, 2023 at 10:24 -
2$\begingroup$ This is because you know in depth about the function, and when you see the name this raises for you lots of the useful context. This is a useful observation. $\endgroup$ Commented Jun 30, 2023 at 11:18
-
2$\begingroup$ I have no familiarity with haversines or the use thereof, but I've sometimes thought a function to compute
1-cos(x)
would make sense since computing that value by first computing the cosine and then subtracting from 1 would yield poor precision whenx
is small. When using double precision, for example, the computation1-cos(x)
would yield zero for allx
allx
in the range +/-1.0536712127723508e-8
--i.e. the output would have no correct bits for anyx
value near zero, other than zero itself. $\endgroup$– supercatCommented Nov 13, 2023 at 21:41 -
x
is very small, you may look for a way of estimatingcscx)
without computingsin(x)
first. $\endgroup$sin,cos,tan
that are the 3 most need missing ones. sind(x) = sin(x*2*pi/360) too often returns unsatisfactory results when degree angle is a large multiple of 15 degrees. $\endgroup$