圆周上两个角度标记之间的最短距离是多少?

12

我正在寻找一种公式,用于在圆周上找到两个度数标记之间的最短距离:例如,30度和170度之间的最短距离是140度。

这两个度数标记可以是任意实数,不一定在0到360之间(可以为负数,或者比360大得多,例如-528.2和740(等于171.8度))。然而,距离应始终为<= 180度且>= 0度。

听起来很简单。但是,我一直在尝试寻找一个好的解决方案,并尝试了许多不同的代码,但迄今为止我尝试过的所有情况都没有奏效。我使用的编程语言是C ++。有人有什么想法吗?


abs(deg1 - deg2)会给出差异,然后进行一些简单的模数学运算使其始终小于180即可。 - Marc B
听起来你会对两个数取绝对值,然后根据你的例子从最大值中减去最小值。 - Robert Harvey
2
@RobertHarvey:那实际上行不通。想象一下起始点是-90度,涵盖4个点即-90(又称270度)、0度、90度和180度。如果你有一个点在-90度,另一个点在135度,那么它们之间的最小距离实际上是135度。 - NotMe
@ChrisLively:是的,我意识到你需要进行模数计算。 - Robert Harvey
10个回答

22
  • 步骤1:获取“原始”差异。例如,给定 -528.2740.0,得到的结果是 1268.2

    • 一种方法:raw_diff = first > second ? first - second : second - first
    • 另一种方法:raw_diff = std::fabs(first - second)
  • 步骤2:减去 360.0 的倍数,以获得介于 0.0(包括)和 360.0(不包括)之间的值。

    • mod_diff = std::fmod(raw_diff, 360.0)
  • 步骤3:如果该值大于 180.0,则从 360.0 中减去它。

    • 一种方法:dist = mod_diff > 180.0 ? 360.0 - mod_diff : mod_diff
    • 另一种方法:dist = 180.0 - std::fabs(mod_diff - 180.0)

最好将其作为一系列语句阅读:

double raw_diff = first > second ? first - second : second - first;
double mod_diff = std::fmod(raw_diff, 360.0);
double dist = mod_diff > 180.0 ? 360.0 - mod_diff : mod_diff;

但是如果需要的话,把这一切放进一个表达式中也不难:

180.0 - std::fabs(std::fmod(std::fabs(first - second), 360.0) - 180.0)

这个很好用。它能被修改以便在第一个点到第二个点的最短距离是逆时针方向时返回负数距离,而在顺时针方向时返回正数距离吗? - snowfrogdev
@neoflash:这是可以做到的,但它太大了,无法放在评论中。 - ruakh

3
我一直在寻找这样的微控制器解决方案,以便用于寻找动画娃娃的齿轮箱,而我没有足够的能力正确计算三角函数。@ruakh的答案是一个很好的基础,但我发现在某些条件下符号被错误地翻转了。以下是对我有效的解决方案。此解决方案适用于圆中的度数标记,但更改MAX_VALUE可以使其适用于任意最大范围,在测量齿轮编码器脉冲时非常有用。在Arduino上进行了测试。
#define MAX_VALUE 360

float shortestSignedDistanceBetweenCircularValues(float origin, float target){

  float signedDiff = 0.0;
  float raw_diff = origin > target ? origin - target : target - origin;
  float mod_diff = fmod(raw_diff, MAX_VALUE); //equates rollover values. E.g 0 == 360 degrees in circle

  if(mod_diff > (MAX_VALUE/2) ){
    //There is a shorter path in opposite direction
    signedDiff = (MAX_VALUE - mod_diff);
    if(target>origin) signedDiff = signedDiff * -1;
  } else {
    signedDiff = mod_diff;
    if(origin>target) signedDiff = signedDiff * -1;
  }

  return signedDiff;

}

2

您也可以使用向量数学和三角函数;这里的角度应该是弧度制。

float angle(float angle1, float angle2)
{
  float x1=cos(angle1);
  float y1=sin(angle1);
  float x2=cos(angle2);
  float y2=sin(angle2);

  float dot_product = x1*x2 + y1*y2;
  return acos(dot_product);
}

2
这在数学上是正确的,但计算量很大,并且会产生很多不必要的舍入误差。使用 fabs+fmod 方法可以运行得快约 20 倍,并且产生数量级更少的舍入误差。 - ruakh
此外,您可以使用三角恒等式:(sin(a)sin(b)+cos(a)cos(b) = sin(a-b) 来推导解决方案:acos(sin(angle1 - angle2))。 - Artium
sin(a)sin(b)+cos(a)cos(b) 应该是 cos(a-b),而不是 sin(a-b)。请参见此处。 - Merax

1
如果你在0-360度的圆上,我认为你可以避免使用fmod()和abs()函数。以下代码将给出在360度圆上最短的距离(以及方向),而无需包含库。180度和0度的方向取决于你认为哪个数字是第一个。我猜这对于非360度刻度的圆也适用(将360改为最大值,将180改为最大值的一半),但我还没有测试过。已经针对不同的值测试了下面的代码,似乎工作正常,但最好不要完全相信它——我不确定。
float find_shortest_angle(float first, float second)
{
   //first = 350., second = 10.; //example rollover 350 to 10 = 20 deg
   first = 10., second = 350.; //example rollover 10 to 350 = -20 deg

   float diff, sweep_angle;

   diff = first-second;
   if(diff<-180)
      sweep_angle= -(360+diff);
   else if(diff>180)
      sweep_angle= 360-diff;
   else
      sweep_angle= -diff;

   return sweep_angle;
}

可能可以翻转大于/小于符号,这将有助于消除一些负号,但我没有尝试过。 - user70891

0

我曾经遇到过一个类似的问题,需要找到圆中任意两点之间的最短距离。我的解决方案如下:

  • 如果N = 圆中的点数
                0 -> N-1
        j before n/2 after (n-j)

               1 -> N-1
   (j-1) before [(n/2)+1] after n-j+1

               2 -> N-1
   (j-2) before [(n/2)+2] after n-j+2

               and so on

其中j第二个点i第一个点

这里是解决方案的Python代码。

for i in range(0, n):
   for j in range(i,n):
          if j < n/2+i:
                 s_rt = j-i
          else :
                 s_rt = n-j+i

我认为可以通过对角度进行微调来找到解决方案。


0

当然,你需要导入math库,以使用fmod和fabs函数。

double a = -528.2;
double b = 740.0;
double diff = (a > b ? a - b : b - a);
double mod_diff = fmod(diff, 360);
double result = (mod_diff < 180 ? mod_diff : 360 - mod_diff);

这不太对。如果(比如说)a0,而b200,你的代码会给出20而不是160 - ruakh
谢谢您的回答,但我找到了反例。假设a = 10而b = 270。由于a小于b,因此答案应该是(270-10)mod 180。但是260 mod 180是80,但实际距离为100。 - Joe Lyga
我们走吧,我实际上(我真不好意思)测试了这段代码,现在它给我返回 171.8 的结果。 - Prashant Kumar
同时也适用于a=10,b=270,结果为100。并且在a和b交换时也能正确运行。 - Prashant Kumar

0

你可以将你在这里找到的公式(http://en.wikipedia.org/wiki/Arc_(geometry))应用于两个角度和两个方向。 因此,您可以找到两个互补的距离(如果将它们相加,您将获得周长(或者您可以通过将另一个弧的长度从周长中减去来获得一个弧的长度)。

您可以比较这两个长度以获取不同角度之间两点之间的最小距离。

在C++中,您有math.h库:http://www.cplusplus.com/reference/clibrary/cmath/


试一下。我建议你先在纸上尝试,然后再用C++实现它。 - user1054204

0

对于像我这样的初学者,其他答案虽然很可能给出一个好的结果,但是很难理解发生了什么。因此,这是我的方法来检查当前点(cp)和目标点(tp)之间哪个方向(顺时针或逆时针)最短。它还将最短距离值分配给shortestDistance变量。根据我特别需要此方法的内容,重要的是我返回一个布尔值,指示最短距离是顺时针还是逆时针方向。以下是该方法:

public boolean checkIfClockwiseIsShortest(int cp, int tp) {
    boolean clockwiseIsShortest = false;
    if (cp != tp) { // if current point and target point are not the same...
        if (tp > cp) { // if current point is less than target point AND..
            if ((tp - cp) <= ((360 - tp) + cp)) {
                clockwiseIsShortest = true;
                shortestDistance = (tp-cp);
                System.out.println("Case A: " + shortestDistance +" degrees clockwise");//[CAN REMOVE]

            } else if ((tp - cp) > ((360 - tp) + cp)) {
                clockwiseIsShortest = false;
                shortestDistance = ((360 - tp) + cp);
                System.out.println("Case B: " + shortestDistance+" degrees counter-clockwise");//[CAN REMOVE]
            }
        } else { // BUT if target point < current point
            if ((cp - tp) <= ((360 - cp) + tp)) {
                clockwiseIsShortest = false;
                shortestDistance = (cp-tp);
                System.out.println("Case C: " + shortestDistance+" degrees counter-clockwise");//[CAN REMOVE]
            } else if ((cp - tp) > ((360 - cp) + tp)) {
                clockwiseIsShortest = true;
                shortestDistance = ((360 - cp) + tp);
                System.out.println("Case D: " + shortestDistance+" degrees clockwise");//[CAN REMOVE]
            }
        }
    }
    return clockwiseIsShortest;
}

请注意,cp是整数度数的起点,tp是整数度数的目标点;类型可以更改为double以获得更高的精度。

它考虑了穿过0度标记的顺时针或逆时针方向。

它检查的3个if:

  1. 起始/当前点(cp)是否等于目标点(tp)?如果是,则不给出距离。
  2. 如果不相等,则目标点>当前点(以度数为单位)或目标点<当前点(以度数为单位)吗?
  3. 基于此,嵌套中的最后一个if检查顺时针旋转的度数是否小于逆时针旋转的度数(或反之亦然)。

同样,其他代码较短的帖子也可能完全正常。

编辑:顺便说一下,shortestDistance变量是类变量,在与此方法相同的类中初始化;如果您要使用此代码,请确保您放置它的类也具有基于与cp和tp变量相同的原始类型的shortestDistance变量(或将其强制转换)。


-1
你可以尝试通过将两个角度除以360后的余数之差取绝对值来解决问题。
#include <iostream>
#include <cmath>

using namespace std;
int degree_difference(int a, int b)
{
    return abs(a%360-b%360);
}

int main()
{
    int result = degree_difference(-235, 15);
    cout << "Difference: " << result << endl;
    return 0;
}

稍等,我认为这不是你想要的。 - enderskill

-1

我们必须假设一个圆只有360度,否则就会变得棘手起来。

因此,你首先要做的是使每个标记都在0到360之间。为了做到这一点,你可以将两个标记分别除以360取模运算。如果结果小于0,则加上360。

假设我们的点是520和-45。

mark1 = ((520 % 360) >= 0) ? (520 % 360) : 360 - (520 % 360);

mark2 = ((-45 % 360) >= 0) ? (-45 % 360) : 360 - (-45 % 360);

mark1 将为 160。mark2 将为 315。

现在,你只需取差的绝对值:

result = abs(mark1 - mark2) = 155

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