How could I define trigonometric functions that take arguments in degrees instead of the usual radians, and compute correctly rounded results for these arguments?
Multiplying the argument by M_PI/180.0
before passing it to the corresponding function in radians does not work, because M_PI/180.0
is not π/180. Section 5.5 of the Handbook of Floating-Point Arithmetic offers a method to compute the correctly rounded product of the argument by π/180, but some arguments will still be such that this product is close to the midpoint between two consecutive representable floats, and then applying even a correctly rounded function in radians can produce the wrong final result.
Two strategies that may work alone or in combination are using higher precision and using the sinpi
,cospi
, tanpi
trigonometric functions from CRlibm, that compute respectively sin(πx)
, cos(πx)
and tan(πx)
.
For the latter strategy, there remains the problem of the division by 180, which is not exact for many arguments.
Regarding the higher-precision strategy (multiplying the argument by an extended-precision representation of π/180, then applying the extended-precision function in radians), there may remain a problem with “exact” cases. The theorem that states that the only rational results of sin
, cos
and tan
for a rational argument are obtained in 0
only applies to the radian versions. It obviously does not apply to the degree versions, and if for some floating-point input x, sindeg(x) is exactly the midpoint between two consecutive representable floating-point numbers, then no amount of intermediate precision is enough to guarantee that the final result is correctly rounded.
The only rationals q
for which cosdeg(360q)
is rational have 1, 2, 3, 4, or 6 as the denominator. This paper by Joerg Jahnel contains a short and beautiful proof using field theory in section 6. (Indeed, the author characterises the degree of the algebraic number cosdeg(360q)
using Euler's totient function.) So there is no floating-point q
such that cosdeg(360q)
is halfway between two adjacent floating-point numbers.
So I guess the answer is "about the same way you implement sin
and friends for radians," though @gnasher729 makes the excellent point that argument reduction for degrees is much, much nicer.
It's difficult. On the positive side, you can reduce the argument to +/- 45 degrees exactly. So you need correctly rounded results between +/- 45 degrees. For very small x, sin (x) is about x * (pi / 180) which is hard enough to get rounded exactly.
To get mostly correctly rounded results for the sine function for example, take -45 <= x <= 45. Split x into xhi = round (512 x) / 512 and xlo = x - xhi. Let sin (x degrees) ≈ ax - bx^3. Round a and b so that s (x) a*xhi - b * (xhi^3) is calculated exactly. Calculate the remainder sin (x degrees) - s (x) carefully; the rounding error should be quite small because the result is small. Add to s (x), this will most of the time give the correctly rounded result.
Well, this is a difficult question. Let me clarify some points:
- What precision is required for the the output? Is it IEEE 754 single or double precision or non standard? Moreover, I assume, that input, i.e. the one represented in degrees, should represented in the same precision as outputs, as this is the case for the normal radian inputs.
- What is your performance metrics? CRlibm is optimized to produce correctly rounded double precision results. On the other side, MPFR is used for arbitrary precision but it is much slower than CRlibm when you need only double precision output.
- What is your working range? i.e. [min argument, max argmunet]? This matters for CRlibm as it works for double precision ranges. However, it won't really matter for MPFR.
I basically recommend to use MPFR if you are obliged to use inputs only in degrees. Let me remind you that any argument in degrees, when it is multiplied by (Pi/180), it produces a transcendental number. However, What is passed to the trigonometric function is the floating point representation rounded, preferably rounded to nearest integer, to the working precision.
I recommend you to do as follows:
- Use MPFR, use the C library whenever possible as it offers much better performance than its bindings.
- Set the MPFR precision much higher than your target precision. For example (target precision + 300). By doing this, you avoid any loss of accuracy for the operation ((Argument*Pi)/180). This can be done easily in MPFR C library by mpfr_set_default_prec().
- Do the operation: X_n=(Argument*Pi)/180, and then execute Sin(X_n) or whatever function you want. There is a constant Pi in MPFR, which is represented within your working precision
- Round your result to the target precision.
"Elementary functions", by Muller shows statistically that most, NOT ALL, of the hard cases are correctly rounded if the working precision is slightly larger than twice the target precision. But in your case, as the input is theoretically transcendental, to be safe, at the expense of the performance, make the working precision much higher than the target. Actually 10x is totally sufficient for almost 100% of cases, if you require up to double precision final result.
If you need a low precision, i.e. single precision or lower, it is feasible to do exhaustive test to decide on the lowest working precision which produces all cases correctly rounded.
You first need to detect the exact cases, and this has already been answered. Now, for the other cases, there's the well-known problem of the table maker's dilemma. If your arithmetic has a fixed (and small) precision and you want a certified bound on the intermediate precision that might be needed, there are two known solutions:
- Obtain a bound based on Nesterenko and Waldschmidt's theorem, as described in Section 4.3 of my PhD thesis (BTW, I think this would also give you the form of the exact cases). But you will get very large bounds on the precision (at least several millions bits?).
- Find the hardest-to-round case. It suffices to do the search in [0,180], since any larger argument will reduce to a value in [0,180] with the same fractional part (since the period is an integer).
来源:https://stackoverflow.com/questions/25104904/how-to-compute-correctly-rounded-trigonometric-functions-in-degrees