将角度保持在-179至180度之间的简单方法

46

有没有一种简单的方法将角度(以度为单位)转换为介于-179和180之间?我确定可以使用mod(%)和一些if语句,但这变得很丑陋:


//Make angle between 0 and 360
angle%=360;

//Make angle between -179 and 180
if (angle>180) angle-=360;

似乎应该有一个简单的数学运算可以同时执行这两个语句。暂时可能需要为转换创建一个静态方法。


5
依我之见,那并不丑陋。相反,它非常整洁清晰。 - C. Ross
8
如果起始角度小于-179度怎么办? - Matthew Whited
在查阅文档后,我同意Matthew Whited的观点 - angle%360产生的值介于-359和+359之间,而不是0和+359。因此,你的解决方案无法将初始值小于-179归一化。 - Daniel Brückner
@Daniel:说得好。给出的例子不够好。请随意提出更好的建议。 - User1
16个回答

129
// reduce the angle  
angle =  angle % 360; 

// force it to be the positive remainder, so that 0 <= angle < 360  
angle = (angle + 360) % 360;  

// force into the minimum absolute value residue class, so that -180 < angle <= 180  
if (angle > 180)  
    angle -= 360;  

3
这是正确答案,且需要的计算最少。 - tzot
6
不是多余的。假设这个角度是-5000度? - President James K. Polk
8
我很震惊,更多的人接受了旧的双while循环方法而不是这个…… - Gurgadurgen
如果我的角度是浮点数而不是整数怎么办? - Charbel
9
将来任何人(比如我)如果在Java中遇到这个问题时,请注意%运算符是余数而不是模数,因此在像Python这样的语言中,第二行angle = (angle + 360) % 360并不需要使其适用于负数(尽管它不会有任何损害)。详情请见:https://dev59.com/f2435IYBdhLWcg3wfQJ9。 - Steve
显示剩余6条评论

27

试试这个替代方案!

atan2(sin(angle), cos(angle))

atan2的范围是[-π, π)。这利用了tan θ = sin θ / cos θ的事实,并且atan2足够聪明,知道θ处于哪个象限。

由于你想要度数,所以需要将角度从弧度转换并转回去:

atan2(sin(angle * PI/180.0), cos(angle * PI/180.0)) * 180.0/PI

更新 我的先前示例是完全合法的,但将范围限制在±90度内。 atan2 的范围是所需的-179度至180度。以下保留原文。


尝试这样做:

asin(sin(angle)))

sin函数的定义域是实数轴,值域为[-1, 1]。而asin函数的定义域为[-1, 1],值域为[-PI/2, PI/2]。由于asinsin的反函数,所以输入值(因为使用浮点数可能会有一些漂移)不会改变太多,您将会得到原始的输入值,并通过限制范围来得到期望的输出值。

如果您需要使用角度,则需要将角度值转换为弧度值,然后再转换回去。

asin(sin(angle * PI/180.0)) * 180.0/PI

(注意:即使在FPU中执行三角函数,它们的速度也比简单的除法和减法操作慢数十亿倍!)


2
利用有限范围函数进行归一化,这是最简洁且正确的答案,虽然会涉及一些额外开销。 - Platinum Azure
现在更好了,因为值在正确的范围内。但是,现在你要做三个三角函数而不是两个! - Seth
1
如果您使用http://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math3/complex/Complex.html,您可以执行Complex(0.0, angle).exp().log(),这可能更加简洁。请参见https://dev59.com/fmUo5IYBdhLWcg3wmASi#29237626以获取解释。 - Gus

14

我知道岁月已经过去,但是仍然。

此解决方案不包含循环、减法或模运算(允许归一化为弧度间隔)。适用于任何输入,包括负值、大值和边缘情况。

double normalizedAngle = angle - (ceil((angle + M_PI)/(2*M_PI))-1)*2*M_PI;  // (-Pi;Pi]:
double normalizedAngle = angle - (ceil((angle + 180)/360)-1)*360;           // (-180;180]:

double normalizedAngle = angle - (floor((angle + M_PI)/(2*M_PI)))*2*M_PI;  // [-Pi;Pi):
double normalizedAngle = angle - (floor((angle + 180)/360))*360;           // [-180;180):

工作良好,没有循环且可移植 :) - Richard
由于某种原因,每当我用Swift编写这个(非常优雅)版本时,编译器需要三分钟才能理解它。 - p10ben

12

这适用于负数和小数,并且不需要循环或三角函数:

angle -= Math.floor(angle / 360 + 0.5) * 360

结果在[-180,180)区间内。对于(-180,180]区间,您可以使用以下方法:

angle -= Math.ceil(angle / 360 - 0.5) * 360


2
请注意,对于这个问题,angle必须是一个float类型,否则整数运算将会启动。使用angle -= Math.ceil((double)angle / 360 - 0.5) * 360或类似的方法可以获得最佳安全性。我喜欢这个答案,应该排名第一。 - Cory-G
我也喜欢这种方法,如果角度已经在约束范围内,它不会导致浮点漂移。 - Khlorghaal
这个答案是简洁和数学简单的最佳结合。 - Robert Penner

10

不是很聪明,但没有条件。

angle = (angle + 179) % 360 - 179;

但是我不确定Java如何处理负数的模运算。这只有在-1模360等于359时才有效。

更新

刚刚查看了文档,a % b会产生一个值,在-(|b| - 1)+(|b| - 1)之间,因此代码是有问题的。为了应对模运算符返回的负值,必须使用以下方法。

angle = ((angle + 179) % 360 + 360) % 360 - 179;

但是...不行...永远不行...使用与您最初的解决方案类似的东西,但对小于-179的值进行修正。


应该写成 "angle = (angle + 179) % 360 - 179"。否则,180 度会被转换为 -180 度。 - Rômulo Ceccon
1
如果起始角度小于-179,则此操作将失败。 - Matthew Whited
我不确定含糊其辞是否可以被视为一个问题 - 对于常用的数学运算,你只需要它高效。当然,两个加法和一个模运算可能不如一个模运算、一个比较和有时候的加法更好。 - Cascabel
@Matthew Whited 因为-1模360的余数是-1? - Daniel Brückner
-1大于-179且小于180,所以没问题。但是试试-181(小于-179)。 - Matthew Whited
显示剩余3条评论

8

我知道我来晚了,但是...

大多数答案都不好,因为它们试图聪明而简洁,然后没有考虑到边缘情况。

这个方法稍微冗长一些,但如果你想让它工作,只需加入使其工作的逻辑。不要试图聪明。

int normalizeAngle(int angle)
{
    int newAngle = angle;
    while (newAngle <= -180) newAngle += 360;
    while (newAngle > 180) newAngle -= 360;
    return newAngle;
}

这个方法可行且相对简单,没有试图过于花哨。请注意,只有零或一个while循环会运行。


1
它可以处理非整数度数的角度! - Aniko
1
你能在开始时设置 newAngle = angle % 360 吗?这样你就不必担心 normalizeAngle(Integer.MAX_VALUE) 了,因为它可能会落入“不考虑边缘情况”的类别。 - Jimmy
51
这个答案对于非靠近0度的角度来说太慢了。这是供那些出于宗教原因反对除法的人使用的解决方案。 - President James K. Polk
10
对于一个大数来说,速度相当慢 :( - Peter Lawrey
17
将normalizeAngle(360000000000)翻译为:将角度标准化为0到359.999999度。 "go get a sandwich" angle是一个俚语,意思是非常大的角度,可能需要很长时间才能计算出来。 - Glenn Maynard
3
这个相比原来的更干净吗?毕竟,这就是所问的问题。 - MCMastery

3
处理负数的一种简单方法是:
double mod = x - Math.floor((x + 179.0) / 360) * 360;

品尝铸造。

顺便说一句:似乎角度在(180.0,181.0)之间是未定义的。范围不应该是(-180,180](排除,包含)吗?


如果角度为270度,则我会得到一个意外的结果为-90度。 - letsdev-cwack
3
@letsdev-cwack -90 是我预计会得到的结果。你预计会得到什么结果,为什么? - Peter Lawrey

3

以下是仅限整数的解决方案:

int normalize(int angle)
{
    angle %= 360;
    int fix = angle / 180; // Integer division!!
    return (fix) ? angle - (360 * (fix)) : angle;
}

有时候聪明反被聪明误,Platinum Azure。

3
也许不是很有用,但我一直喜欢使用非度角。
通过位运算可以将0到255的角度范围保持在范围内,或者对于单字节变量,可以简单地允许溢出。
对于从-128到127的角度范围,使用位运算不是那么容易,但是对于单字节变量,您可以让它溢出。
我认为这是多年前游戏中的一个好主意,因为您可能正在使用查找表来查找角度。如今,不太好 - 角度的使用方式不同,并且是浮点数。
尽管如此,也许值得一提。

+1 非常有趣的想法,如果需要更细的粒度,可以使用更多的位数 -- 例如两个字节可以给出 65536 个不同的角度。 - President James K. Polk

3

我已经制定了一个用于圆形值方向的公式

以保持角度在0到359之间的方法是:

angle + Math.ceil( -angle / 360 ) * 360

但是要保持在-179到180之间,可以使用以下公式:
angle + Math.ceil( (-angle-179) / 360 ) * 360

这将使其方向偏移约-179,保持实际角度不变。

转移角度方向的通用公式可以是:

angle + Math.ceil( (-angle+shift) / 360 ) * 360

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接