Reputation: 129
What is the best way to constrain any value from -pi to pi ?
I currently have:
if (fAngle > XM_PI) {
fAngle = fAngle - XM_2PI;
}
else if (fAngle < -XM_PI) {
fAngle = fAngle - -XM_2PI;
}
However, I fear those if's should instead be while's
For reference, under the Exploit Symmetrical Functions section:
Extra bit of precision!
Upvotes: 0
Views: 750
Reputation: 129
STATIC_INLINE_PURE float const __vectorcall constrain(float const fAngle)
{
static constexpr double const
dPI(std::numbers::pi),
d2PI(2.0 * std::numbers::pi),
dResidue(-1.74845553146951715461909770965576171875e-07); // abs difference between d2PI(double precision) and XM_2PI(float precision)
double dAngle(fAngle);
dAngle = std::remainder(dAngle, d2PI);
if (dAngle > dPI) {
dAngle = dAngle - d2PI - dResidue;
}
else if (dAngle < -dPI) {
dAngle = dAngle + d2PI + dResidue;
}
return((float)dAngle);
}
Upvotes: 0
Reputation: 4431
The simplest coding way is to use the math library function remainder, as in
fAngle = remainder( fangle, XM_2PI);
Upvotes: 1
Reputation: 223693
Adding or subtracting XM_2PI
cannot restore any accuracy that has been lost. In fact, it adds noise, generally losing more accuracy, because XM_2PI
is necessarily only an approximation of 2π. It has some error itself, so adding or subtracting it adds or subtracts the error in the approximation.
What it can do is keep you from losing more accuracy by ensuring that future results remain low in magnitude, thus remaining in a region where the floating-point format has more precision than if the number grew beyond 4, 8, 16, or other points where the exponent changes and the absolute precision becomes worse.
If you already have some value x
outside [−π, π] and want its sine or cosine, you should get the best result by using sin(x)
or cos(x)
directly. Good implementations of sin
and cos
will reduce the argument using a high-precision value for 2π, so you will get a better result than using sin(x-XM_PI)
or cos(x-XM_PI)
(unless, by chance, the various errors in these happen to cancel).
So your task with trigonometric functions is not to reduce values you already have but to design your algorithms to keep values from growing. Adding or subtracting 2π is a reasonable way to do this. However, when you do it, add or subtract an extended-precision version of 2π, not just XM_2PI
. You can do this by representing 2π as XM_2PI
(which should be the value representable in floating-point that is closest to 2π) plus some residue r
. r
should be the value representable in floating-point that is closest to 2π−XM_2PI
. You can calculate that with extended-precision software such as GMP or Maple and can likely find it online. (I do not have it handy or I would paste it here; anybody else is welcome to edit it in.) Then you would update your angle with fAngle = fAngle - XM_2PI - r;
or fAngle = fAngle + XM_2PI + r;
.
An exception is if you have the angle measured in some unit that you can represent or reduce exactly, such as in degrees (which you can reduce by 360º with no error as long as the number of degrees itself is represented with no error) or in time (such as number of seconds for some function with a period of a day or other rational number of seconds, so you can again reduce with no error). In that case, you can let the angle grow as long as you can represent it exactly, and you would reduce it modulo the period prior to converting it to radians.
Upvotes: 2