Reputation: 696
I am working with some programming problems which require the use of trigonometric functions. Unfortunately, the functions in the math.h
library seem to be inaccurate. Here is an example:
#include <stdio.h>
#include <math.h>
int main() {
double a, b, c, d;
a = sin(0.0);
b = sin(90.0);
c = cos(0.0);
d = cos(90.0);
printf("A = %lf\nB = %lf\nC = %lf\nD = %lf\n", a, b, c, d);
return (0);
}
OUTPUT:
A = 0.000000
B = 0.893997
C = 1.000000
D = -0.448074
So is there any way to make these functions accurate? Or do I have to make my own functions using series?
I googled and so far could not find any way to make the functions accurate but using series.
Upvotes: 0
Views: 7493
Reputation: 80276
Functions sin()
and cos()
expect radians. They are usually “faithful”, that is, they produce results within 1ULP of the mathematical result, at least for arguments up to a couple of thousands.
#include <stdio.h>
#include <math.h>
int main() {
double a, b, c, d;
a = sin(0.0);
b = sin(0.5 * 3.1415926535897932);
c = cos(0.0);
d = cos(0.5 * 3.1415926535897932);
printf("A = %lf\nB = %lf\nC = %lf\nD = %lf\n", a, b, c, d);
return (0);
}
A = 0.000000
B = 1.000000
C = 1.000000
D = 0.000000
EDITED TO ADD:
Camilo Martinez points out that “Unit in the Last Place” is a bit of a specialized notion. In simpler terms, there are a finite number of double
values, denser around zero:
++-+-+-+---+---+-------+---------------+-------------------------------+--
The exact trigonometric value you are computing almost always falls in-between two of these double
s (the only exceptions are sin(0.0) = 0.0
and cos(0.0) = 1.0
):
++-+-+-+---+---+-------+---------------+-------------------------------+--
| ^ |
lower | upper
double | double
|
exact (mathematical) result
Most libraries provide functions that will, in normal circumstances, give you the double
immediately above the exact result or immediately below the mathematical result.
This is pretty good considering the difficulty of the question.
Some libraries provide functions that will give you the double nearest to the exact result, for all inputs (even 1E21
). This is an astounding result. Until relatively recently this could only be obtained at the cost of expensive computations, but nowadays, you can even obtain this sort of result nearly as fast as with the less accurate functions of the past.
Finally, sometimes you are not even applying sin()
or cos()
to the number you would like to, but only to its nearest double
approximation. This is the case in your example once fixed: you would like to apply sin()
and cos()
to π/2, but you can't, because π/2 is not representable as a double
. You have to apply them to the nearest available double
instead (this is what the fixed program does).
Such inaccuracies can compound. The process by which they do have given floating-point computations a bad reputation, but actually, the way floating-point inaccuracies compound is very predictable and can be taken into account when writing programs that use floating-point.
Upvotes: 13
Reputation: 215211
If you really need to work in degrees and have correct results, the standard trigonometric functions are not sufficient. For instance, cos(90*M_PI/180)
will not yield 0.0
. The error is probably less than 1ulp except when the correct result is zero (or maybe very close to zero), so you might just decide to be happy with using them anyway. To get better results, you'd need to adapt the existing algorithms to work in degrees (in some ways this will be easier since argument-reduction, one of the hardest parts of implementing the trig functions, is trivial in degrees) and do some numerical analysis to ensure you're within the error bounds you want.
A quick fix-up for working in degrees would be to just special-case multiples of 30 (where all the exact results fall) and call cos(x*M_PI/180)
, etc. for all other values. This won't be perfect but at least it will avoid introducing messy inexactness at points that should be exact.
Upvotes: 2