C语言 C:如何将浮点数包裹到区间 [-pi, pi)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4633177/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C: How to wrap a float to the interval [-pi, pi)
提问by P i
I'm looking for some nice C code that will accomplish effectively:
我正在寻找一些可以有效完成的不错的 C 代码:
while (deltaPhase >= M_PI) deltaPhase -= M_TWOPI;
while (deltaPhase < -M_PI) deltaPhase += M_TWOPI;
What are my options?
我有哪些选择?
采纳答案by Lior Kogan
Edit Apr 19, 2013:
2013 年 4 月 19 日编辑:
Modulo function updated to handle boundary cases as noted by aka.nice and arr_sea:
模函数已更新以处理 aka.nice 和 arr_sea 指出的边界情况:
static const double _PI= 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348;
static const double _TWO_PI= 6.2831853071795864769252867665590057683943387987502116419498891846156328125724179972560696;
// Floating-point modulo
// The result (the remainder) has same sign as the divisor.
// Similar to matlab's mod(); Not similar to fmod() - Mod(-3,4)= 1 fmod(-3,4)= -3
template<typename T>
T Mod(T x, T y)
{
static_assert(!std::numeric_limits<T>::is_exact , "Mod: floating-point type expected");
if (0. == y)
return x;
double m= x - y * floor(x/y);
// handle boundary cases resulted from floating-point cut off:
if (y > 0) // modulo range: [0..y)
{
if (m>=y) // Mod(-1e-16 , 360. ): m= 360.
return 0;
if (m<0 )
{
if (y+m == y)
return 0 ; // just in case...
else
return y+m; // Mod(106.81415022205296 , _TWO_PI ): m= -1.421e-14
}
}
else // modulo range: (y..0]
{
if (m<=y) // Mod(1e-16 , -360. ): m= -360.
return 0;
if (m>0 )
{
if (y+m == y)
return 0 ; // just in case...
else
return y+m; // Mod(-106.81415022205296, -_TWO_PI): m= 1.421e-14
}
}
return m;
}
// wrap [rad] angle to [-PI..PI)
inline double WrapPosNegPI(double fAng)
{
return Mod(fAng + _PI, _TWO_PI) - _PI;
}
// wrap [rad] angle to [0..TWO_PI)
inline double WrapTwoPI(double fAng)
{
return Mod(fAng, _TWO_PI);
}
// wrap [deg] angle to [-180..180)
inline double WrapPosNeg180(double fAng)
{
return Mod(fAng + 180., 360.) - 180.;
}
// wrap [deg] angle to [0..360)
inline double Wrap360(double fAng)
{
return Mod(fAng ,360.);
}
回答by Tim ?as
One-liner constant-time solution:
单线性恒定时间解决方案:
Okay, it's a two-liner if you count the second function for [min,max)form, but close enough — you could merge them together anyways.
好吧,如果你把第二个函数算作[min,max)表单,它是一个双线,但足够接近——你可以将它们合并在一起。
/* change to `float/fmodf` or `long double/fmodl` or `int/%` as appropriate */
/* wrap x -> [0,max) */
double wrapMax(double x, double max)
{
/* integer math: `(max + x % max) % max` */
return fmod(max + fmod(x, max), max);
}
/* wrap x -> [min,max) */
double wrapMinMax(double x, double min, double max)
{
return min + wrapMax(x - min, max - min);
}
Then you can simply use deltaPhase = wrapMinMax(deltaPhase, -M_PI, +M_PI).
然后你可以简单地使用deltaPhase = wrapMinMax(deltaPhase, -M_PI, +M_PI).
The solutions is constant-time, meaning that the time it takes does not depend on how far your value is from [-PI,+PI)— for better or for worse.
解决方案是固定时间的,这意味着它所花费的时间不取决于您的价值有多远[-PI,+PI)——无论好坏。
Verification:
确认:
Now, I don't expect you to take my word for it, so here are some examples, including boundary conditions. I'm using integers for clarity, but it works much the same with fmod()and floats:
现在,我不希望你相信我的话,所以这里有一些例子,包括边界条件。为清楚起见,我使用整数,但它fmod()与浮点数的工作原理大致相同:
- Positive
x:wrapMax(3, 5) == 3:(5 + 3 % 5) % 5 == (5 + 3) % 5 == 8 % 5 == 3wrapMax(6, 5) == 1:(5 + 6 % 5) % 5 == (5 + 1) % 5 == 6 % 5 == 1
- Negative
x:- Note:These assume that integer modulo copies left-hand sign; if not, you get the above ("Positive") case.
wrapMax(-3, 5) == 2:(5 + (-3) % 5) % 5 == (5 - 3) % 5 == 2 % 5 == 2wrapMax(-6, 5) == 4:(5 + (-6) % 5) % 5 == (5 - 1) % 5 == 4 % 5 == 4
- Boundaries:
wrapMax(0, 5) == 0:(5 + 0 % 5) % 5 == (5 + 0) % 5 == 5 % 5 == 0wrapMax(5, 5) == 0:(5 + 5 % 5) % 5 == (5 + 0) % 5== 5 % 5 == 0wrapMax(-5, 5) == 0:(5 + (-5) % 5) % 5 == (5 + 0) % 5 == 5 % 5 == 0- Note:Possibly
-0instead of+0for floating-point.
- Note:Possibly
- 正面
x:wrapMax(3, 5) == 3:(5 + 3 % 5) % 5 == (5 + 3) % 5 == 8 % 5 == 3wrapMax(6, 5) == 1:(5 + 6 % 5) % 5 == (5 + 1) % 5 == 6 % 5 == 1
- 阴性
x:- 注意:这些假设整数模复制左手符号;如果没有,您会得到上述(“正面”)案例。
wrapMax(-3, 5) == 2:(5 + (-3) % 5) % 5 == (5 - 3) % 5 == 2 % 5 == 2wrapMax(-6, 5) == 4:(5 + (-6) % 5) % 5 == (5 - 1) % 5 == 4 % 5 == 4
- 边界:
wrapMax(0, 5) == 0:(5 + 0 % 5) % 5 == (5 + 0) % 5 == 5 % 5 == 0wrapMax(5, 5) == 0:(5 + 5 % 5) % 5 == (5 + 0) % 5== 5 % 5 == 0wrapMax(-5, 5) == 0:(5 + (-5) % 5) % 5 == (5 + 0) % 5 == 5 % 5 == 0- 注意:可能
-0代替+0浮点数。
- 注意:可能
The wrapMinMaxfunction works much the same: wrapping xto [min,max)is the same as wrapping x - minto [0,max-min), and then (re-)adding minto the result.
该wrapMinMax功能的工作原理大致相同:包装x,以[min,max)相同包装x - min到[0,max-min),然后(重新)加入min到结果。
I don't know what would happen with a negative max, but feel free to check that yourself!
我不知道负最大值会发生什么,但请自行检查!
回答by aka.nice
If ever your input angle can reach arbitrarily high values, and if continuity matters, you can also try
如果您的输入角度可以达到任意高的值,并且连续性很重要,您也可以尝试
atan2(sin(x),cos(x))
This will preserve continuity of sin(x) and cos(x) better than modulo for high values of x, especially in single precision (float).
对于高 x 值,这将比模数更好地保持 sin(x) 和 cos(x) 的连续性,尤其是在单精度 (float) 中。
Indeed, exact_value_of_pi - double_precision_approximation ~= 1.22e-16
确实,exact_value_of_pi - double_precision_approximation ~= 1.22e-16
On the other hand, most library/hardware use a high precision approximation of PI for applying the modulo when evaluating trigonometric functions (though x86 family is known to use a rather poor one).
另一方面,大多数库/硬件在评估三角函数时使用 PI 的高精度近似值来应用模数(尽管已知 x86 系列使用相当差的函数)。
Result might be in [-pi,pi], you'll have to check the exact bounds.
结果可能在 [-pi,pi] 中,您必须检查确切的界限。
Personaly, I would prevent any angle to reach several revolutions by wrapping systematically and stick to a fmod solution like the one of boost.
就我个人而言,我会通过系统地包裹来防止任何角度达到几圈,并坚持使用像 boost 这样的 fmod 解决方案。
回答by jdehaan
There is also fmodfunction in math.hbut the sign causes trouble so that a subsequent operation is needed to make the result fir in the proper range (like you already do with the while's). For big values of deltaPhasethis is probably faster than substracting/adding `M_TWOPI' hundreds of times.
也有fmod函数 inmath.h但符号会引起麻烦,因此需要进行后续操作以使结果 fir 在适当的范围内(就像您已经对 while 所做的那样)。对于大值,deltaPhase这可能比减去/添加“M_TWOPI”数百倍快。
deltaPhase = fmod(deltaPhase, M_TWOPI);
EDIT:I didn't try it intensively but I think you can use fmodthis way by handling positive and negative values differently:
编辑:我没有深入尝试,但我认为您可以fmod通过不同地处理正值和负值来使用这种方式:
if (deltaPhase>0)
deltaPhase = fmod(deltaPhase+M_PI, 2.0*M_PI)-M_PI;
else
deltaPhase = fmod(deltaPhase-M_PI, 2.0*M_PI)+M_PI;
The computational time is constant (unlike the while solution which gets slower as the absolute value of deltaPhase increases)
计算时间是恒定的(与随着 deltaPhase 的绝对值增加而变慢的 while 解决方案不同)
回答by Craig McGillivary
I would do this:
我会这样做:
double wrap(double x) {
return x-2*M_PI*floor(x/(2*M_PI)+0.5);
}
There will be significant numerical errors. The best solution to the numerical errors is to store your phase scaled by 1/PI or by 1/(2*PI) and depending on what you are doing store them as fixed point.
会有很大的数值误差。数值误差的最佳解决方案是存储按 1/PI 或 1/(2*PI) 缩放的相位,并根据您的操作将它们存储为定点。
回答by Pete Kirkham
Instead of working in radians, use angles scaled by 1/(2π)and use modf, floor etc. Convert back to radians to use library functions.
不要使用弧度,而是使用按1/(2π)缩放的角度 并使用 modf、floor 等。转换回弧度以使用库函数。
This also has the effect that rotating ten thousand and a half revolutions is the same as rotating half then ten thousand revolutions, which is not guaranteed if your angles are in radians, as you have an exact representation in the floating point value rather than summing approximate representations:
这也具有旋转一万半圈与旋转一万圈和一万圈相同的效果,如果您的角度以弧度为单位,则无法保证这一点,因为您在浮点值中有准确的表示,而不是求和近似值陈述:
#include <iostream>
#include <cmath>
float wrap_rads ( float r )
{
while ( r > M_PI ) {
r -= 2 * M_PI;
}
while ( r <= -M_PI ) {
r += 2 * M_PI;
}
return r;
}
float wrap_grads ( float r )
{
float i;
r = modff ( r, &i );
if ( r > 0.5 ) r -= 1;
if ( r <= -0.5 ) r += 1;
return r;
}
int main ()
{
for (int rotations = 1; rotations < 100000; rotations *= 10 ) {
{
float pi = ( float ) M_PI;
float two_pi = 2 * pi;
float a = pi;
a += rotations * two_pi;
std::cout << rotations << " and a half rotations in radians " << a << " => " << wrap_rads ( a ) / two_pi << '\n' ;
}
{
float pi = ( float ) 0.5;
float two_pi = 2 * pi;
float a = pi;
a += rotations * two_pi;
std::cout << rotations << " and a half rotations in grads " << a << " => " << wrap_grads ( a ) / two_pi << '\n' ;
}
std::cout << '\n';
}}
回答by Andrew Hundt
Here is a version for other people finding this question that can use C++ with Boost:
这是其他人发现此问题的版本,可以将 C++ 与 Boost 结合使用:
#include <boost/math/constants/constants.hpp>
#include <boost/math/special_functions/sign.hpp>
template<typename T>
inline T normalizeRadiansPiToMinusPi(T rad)
{
// copy the sign of the value in radians to the value of pi
T signedPI = boost::math::copysign(boost::math::constants::pi<T>(),rad);
// set the value of rad to the appropriate signed value between pi and -pi
rad = fmod(rad+signedPI,(2*boost::math::constants::pi<T>())) - signedPI;
return rad;
}
C++11 version, no Boost dependency:
C++11 版本,无 Boost 依赖:
#include <cmath>
// Bring the 'difference' between two angles into [-pi; pi].
template <typename T>
T normalizeRadiansPiToMinusPi(T rad) {
// Copy the sign of the value in radians to the value of pi.
T signed_pi = std::copysign(M_PI,rad);
// Set the value of difference to the appropriate signed value between pi and -pi.
rad = std::fmod(rad + signed_pi,(2 * M_PI)) - signed_pi;
return rad;
}
回答by M2tM
I encountered this question when searching for how to wrap a floating point value (or a double) between two arbitrary numbers. It didn't answer specifically for my case, so I worked out my own solution which can be seen here. This will take a given value and wrap it between lowerBound and upperBound where upperBound perfectly meets lowerBound such that they are equivalent (ie: 360 degrees == 0 degrees so 360 would wrap to 0)
我在搜索如何在两个任意数字之间包装浮点值(或双精度值)时遇到了这个问题。它没有专门针对我的情况回答,所以我制定了自己的解决方案,可以在这里看到。这将取一个给定的值并将其包装在lowerBound和upperBound之间,其中upperBound完美地满足lowerBound,这样它们是等效的(即:360度== 0度,所以360将包装为0)
Hopefully this answer is helpful to others stumbling across this question looking for a more generic bounding solution.
希望这个答案对在这个问题上绊倒寻找更通用的边界解决方案的其他人有所帮助。
double boundBetween(double val, double lowerBound, double upperBound){
if(lowerBound > upperBound){std::swap(lowerBound, upperBound);}
val-=lowerBound; //adjust to 0
double rangeSize = upperBound - lowerBound;
if(rangeSize == 0){return upperBound;} //avoid dividing by 0
return val - (rangeSize * std::floor(val/rangeSize)) + lowerBound;
}
A related question for integers is available here: Clean, efficient algorithm for wrapping integers in C++
整数的相关问题可以在这里找到: Clean, Effective algorithm for wrapping integers in C++
回答by Glenn
In the case where fmod() is implemented through truncated division and has the same sign as the dividend, it can be taken advantage of to solve the general problem thusly:
在 fmod() 通过截除法实现并且与被除数具有相同符号的情况下,可以利用它来解决一般问题:
For the case of (-PI, PI]:
对于 (-PI, PI] 的情况:
if (x > 0) x = x - 2PI * ceil(x/2PI) #Shift to the negative regime
return fmod(x - PI, 2PI) + PI
And for the case of [-PI, PI):
对于 [-PI, PI) 的情况:
if (x < 0) x = x - 2PI * floor(x/2PI) #Shift to the positive regime
return fmod(x + PI, 2PI) - PI
[Note that this is pseudocode; my original was written in Tcl, and I didn't want to torture everyone with that. I needed the first case, so had to figure this out.]
[注意这是伪代码;我的原作是用 Tcl 写的,我不想用它来折磨每个人。我需要第一个案例,所以必须弄清楚这一点。]
回答by mojuba
A two-liner, non-iterative, tested solution for normalizing arbitrary angles to [-π, π):
用于将任意角度归一化为 [-π, π) 的两行、非迭代、经过测试的解决方案:
double normalizeAngle(double angle)
{
double a = fmod(angle + M_PI, 2 * M_PI);
return a >= 0 ? (a - M_PI) : (a + M_PI);
}
Similarly, for [0, 2π):
类似地,对于 [0, 2π):
double normalizeAngle(double angle)
{
double a = fmod(angle, 2 * M_PI);
return a >= 0 ? a : (a + 2 * M_PI);
}

