20
$\begingroup$

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?

$\endgroup$
3
  • 1
    $\begingroup$ "Is there any way that supporting the other 3 directly could be faster than calculating one and performing the division?" - definitely could be. The major thing you'd worry about is accuracy, probably. e.g. if x is very small, you may look for a way of estimating cscx) without computing sin(x) first. $\endgroup$ Commented Jun 30, 2023 at 9:52
  • 2
    $\begingroup$ "only 3 out of the 6 trigonometry" --> it is the degree versions of 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$
    – chux
    Commented Jun 30, 2023 at 15:39
  • 1
    $\begingroup$ See also johndcook.com/blog/2009/09/25/how-many-trig-functions $\endgroup$
    – bishop
    Commented Jul 2, 2023 at 5:19

5 Answers 5

21
$\begingroup$

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.

$\endgroup$
9
  • 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$
    – CPlus
    Commented 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$
    – Pseudonym
    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-logic
    Commented Jun 30, 2023 at 8:48
12
$\begingroup$

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.

$\endgroup$
3
  • $\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 small x. $\endgroup$
    – kaya3
    Commented 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$
    – chux
    Commented 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$
    – Barmar
    Commented Jun 30, 2023 at 21:51
11
$\begingroup$

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.

$\endgroup$
3
  • 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ám
    Commented 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$
    – Pseudonym
    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$
    – Joshua
    Commented Aug 17, 2023 at 23:43
10
$\begingroup$

Frequency of use

The main three are vastly more commonly used than the other three.

$\endgroup$
4
  • 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ám
    Commented 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$
    – Pseudonym
    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
10
$\begingroup$

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.

$\endgroup$
4
  • 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 read haversine 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$
    – Stef
    Commented 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 when x is small. When using double precision, for example, the computation 1-cos(x) would yield zero for all x all x in the range +/- 1.0536712127723508e-8--i.e. the output would have no correct bits for any x value near zero, other than zero itself. $\endgroup$
    – supercat
    Commented Nov 13, 2023 at 21:41
  • $\begingroup$ Agree, extended $\endgroup$ Commented Nov 14, 2023 at 18:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .