比较角度并找出差异


27

我想比较角度并了解它们之间的距离。对于此应用程序,我正在以度数工作,但它也适用于弧度和梯度。角度的问题在于它们取决于模运算,即0-360度。

假设一个角度为15度,一个角度为45度。相差30度,并且45度角度大于15度的角度。

但是,当您有345度和30度时,这种情况就会分解。尽管它们可以正确比较,但是它们之间的差异是315度,而不是正确的45度。

我该如何解决?我可以编写算法代码:

if(angle1 > angle2) delta_theta = 360 - angle2 - angle1;
else delta_theta = angle2 - angle1;

但是我更喜欢一个避免比较/分支的解决方案,并且完全依靠算术。


关于这个问题,我们可以假设给定的角度在[0,360]或(-infinite,+ infinite)范围内吗?例如,该算法是否还可以将-130度与450度进行比较?
egarcia

假设角度被归一化到该范围。
Thomas O

Answers:


29

这是我的简化,无分支,无比较,没有最小/最大版本:

angle = 180 - abs(abs(a1 - a2) - 180); 

由于输入受到足够的约束,因此删除了模数(感谢Martin指出了这一点)。

两腹肌,三减。


您不需要模,输入值被限制在[0,360]范围内(请参见Thomas对原始提交的评论)。挺整洁的。
马丁·索卡

啊,是的,你是对的。我尝试时输入的内容不太严格。
JasonD

但是,如果您想保留差异的符号,以便可以分辨出哪一个在左侧,该怎么办?
Jacob Phillips

9

尽管它们可以正确比较,但是它们之间的差异是315度,而不是正确的45度。

是什么让您认为315不正确?在一个方向上为315度,在另一个方向上为45度。您想选择2个可能的角度中最小的一个,这似乎本质上是有条件的。您无法使用环绕算法(即通过模数运算符)来解决该问题,因为随着您逐渐增加一个角度,它们之间的角度会逐渐增大,直到达到180,然后开始下降。

我认为您要么必须检查两个角度并确定要测量的方向,要么要计算两个方向并确定要得到的结果。


对不起,我应该澄清一下。如果反向进行,则30-345为-315,并且负角没有太大意义。我想我正在寻找两者之间的最小角度。即45度小于315
托马斯ö

2
但是没有“反向”-您可以执行2个角度和2种旋转类型以使一个匹配。负角是完美的意义-毕竟,这只是从任意轴旋转的量度。
Kylotan

如果您想要最小的角度,则abs(a1%180-a2%180)会给您该角度。但是,它不会告诉您方向。去除腹肌将给您最小的角度,从“ a1”到“ a2”
耐嚼口香糖

2
@耐嚼吧?180和0之间的差异不是0,181和0之间的差异不是1 ...
dash-tom-bang 2010年

1
@ dash-tom-bang你说的很对。我不知道我在想什么,但是现在我再看一次,这是完全不对的。请无视我先前的评论。
耐嚼口香糖

4

总是有两个技巧,让比较结果选择一个:

delta_theta = (angle1 > angle2) * (360 - angle2 - angle1)
              + (angle2 > angle1) * (angle2 - angle1);

我不知道没有比较的方式,但是通常分支是使代码变慢和变长的原因,而不是比较。至少在我看来,这比Martin的答案更具可读性(任何优秀的C程序员都会将其视为无分支等效项,并查看其作用),但效率也较低。

但是就像我在评论中说的那样,无分支算法在具有深层流水线和不良预测的处理器上非常有用-微控制器通常具有很小的流水线,而台式机通常具有良好的预测,因此除非您以游戏机为目标,否则分支版本如果它减少指令数量,则可能是最佳途径。

与往常一样,配置文件-可能像对系统进行操作计数一样简单-将为您提供真正的答案。


2

假设true的值为-1,false的值为0,以及'〜','&'和'|' 分别是notandor运算符,我们正在使用二进制补码算法:

temp1 := angle1 > angle2
/* most processors can do this without a jump; for example, under the x86 family,
   it's the result of CMP; SETLE; SUB .., 1 instructions */
temp2 := angle1 - angle2
temp1 := (temp1 & temp2) | (~temp1 & -temp2)
/* in x86 again: only SUB, AND, OR, NOT and NEG are used, no jumps
   at this point, we have the positive difference between the angles in temp1;
   we can now do the same trick again */
temp2 := temp1 > 180
temp2 := (temp2 & temp1) | (~temp2 & (360 - temp1))
/* the result is in temp2 now */

+1是因为它很聪明,但是在微控制器上,它的性能可能比分支版本差很多。

取决于微控制器,但是,是的,这通常不值得。(短)条件跳转通常足够快。同样,可以通过使用xor(^)操作将第三和第五行重写为更快一点,但是为了清楚起见,我将它们保留为当前形式:temp1:= temp2 ^((temp2 ^ -temp2)& 〜temp1),temp2:= temp1 ^((temp1 ^(360-temp1))&〜temp2)
Martin Sojka 2010年

1

那这个呢?

min( (a1-a2+360)%360, (a2-a1+360)%360 )

为了避免负差,此处添加了360,因为负数的模将返回负结果。然后,您将获得两个可能结果中的较小者。

仍然存在一个隐含的决定,但我不知道如何避免。基本上,您可以通过计算顺时针或逆时针的差来比较两个角度,似乎您明确希望这两个差中的较小者。如果不进行比较,我不知道如何获得结果。也就是说,不使用“ abs”,“ min”,“ max”或某些类似的运算符。


没有分支指令,有几种计算最小,最大和绝对整数的方法,尽管由于这是微控制器,所以分支可能是最快的方法。 graphics.stanford.edu/~seander/bithacks.html#IntegerAbs

1

虽然您的问题没有提及它们,但我将假设您的角度计算问题源自想要知道两个向量之间的最小角度。

该计算很容易。假设A和B是您的向量:

angle_between = acos( Dot( A.normalized, B.normalized ) )

如果您没有矢量,并且想使用这种方法,则可以通过给定角度构造单位长度矢量new Vector2( cos( angle ), sin ( angle ) )


1
我正在处理的处理器是一个小型微控制器。仅使用trig函数生成矢量来获取角度之间的差是没有意义的,每个循环都是宝贵的。
Thomas O

1
在微控制器上,我有些惊讶,使用分支不是更好,但是如果您真的想避免分支,那么我的答案就没有太多算术了。
JasonD 2010年

好了,一个分支是两个周期,一个加/减/等是一个周期,但是分支也占用了额外的程序存储器。这并不关键,但是会很好。
Thomas O

我感觉到您的答案是正确的,而我的答案是错误的,但是我无法理解为什么会这样。:)
Kylotan 2010年

1

基本上与JasonD的答案相同,除了使用按位运算而不是绝对值函数。

假设您有16位短整数!

short angleBetween(short a,short b) {
    short x = a - b;
    short y = x >> 15;
    y = ((x + y) ^ y) - 180;
    return 180 - ((x + y) ^ y);
}


0

由于您只关心消除算术之外的分支和“复杂”操作,因此我建议这样做:

min(abs(angle1 - angle2), abs(angle2 - angle1))

abs尽管所有角度均为正,您仍需要在其中放置一个。否则,将始终选择最否定的结果(当比较ab和ba时,对于肯定的,唯一的a和b,总是存在一个完全正确的否定答案)。

注意:这将不会保留angle1和angle2之间的方向。有时您出于AI目的需要它。

这类似于CeeJay的答案,但消除了所有模数。我不知道周期成本是多少abs,但是我猜它是1或2。很难说成本也是多少min。也许3?因此,加上每个减法1个周期,这条线的成本大约为4到9。


0

have朝向want的角度,获得带符号(+/-)形式的较小相对角度:

  • 最高 180度| PI弧度
  • 逆时针签名
  • +顺时针签名

学位

PITAU = 360 + 180 # for readablility
signed_diff = ( want - have + PITAU ) % 360 - 180

弧度

PI = 3.14; TAU = 2*PI; PITAU = PI + TAU;
signed_diff = ( want - have + PITAU ) % TAU - PI

基本原理

我弄清楚这一点之后,便遇到了这个线程,寻找一种避免取模的解决方案。到目前为止,我还没有发现。此解决方案用于保留透视图符号,如@ jacob-phillips询问此评论。如果只需要最短的无符号角,则有更便宜的解决方案。


0

这是一个古老的问题,但我遇到了同样的情况-必须获得有符号的角度差,最好没有分支和繁重的数学运算。我最终得到的是:

int d = (a - b) + 180 + N * 360; // N = 1, 2 or more.
int r = (d / 360) * 360;
return (d - r) - 180;

限制是,与“ a”相比,“ b”的旋转次数不得超过“ N”。如果您不能确保并可以进行额外的操作,则可以将其用作第一行:

int d = ((a % 360) - (b % 360)) + 540;

我从这篇帖子的第13条评论中得到了这个主意:http : //blog.lexique-du-net.com/index.php? post/ Calculate- the- real-difference-between-two-angles-keeping-the-标志


-1

我想我可以说

angle1=angle1%360;
angle2=angle2%360;
var distance = Math.abs(angle1-angle2);
//edited
if(distance>180)
  distance=360-distance;

当然,考虑到角度以度为单位。


1
我不认为这可以解决问题。345%360 == 345,以及ABS(345-30)仍然是315
格雷戈里艾利-堰

@Gregory:好的,对不起,我很抱歉。我正在编辑回复,请选中此新回复。:)
Vishnu

1
顺便说一下,angle1 = angle1%360; angle2 = angle2%360; var distance = Math.abs(angle1-angle2); 与var distance = Math.abs(angle1-angle2)%360相同-慢一点。
Martin Sojka
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.