ความแตกต่างที่เล็กที่สุดระหว่าง 2 Angles


139

กำหนดให้ 2 มุมในช่วง -PI -> PI รอบ ๆ พิกัดค่าของมุมที่เล็กที่สุดของ 2 มุมระหว่างทั้งสองมุมคืออะไร?

คำนึงว่าความแตกต่างระหว่าง PI และ -PI ไม่ใช่ 2 PI แต่เป็นศูนย์

ตัวอย่าง:

ลองนึกภาพวงกลมที่มีเส้น 2 เส้นออกมาจากจุดศูนย์กลางมีมุม 2 มุมระหว่างเส้นเหล่านั้นมุมที่ทำจากด้านในหรือที่เรียกว่ามุมที่เล็กกว่าและมุมที่ทำด้านนอกหรือที่เรียกว่ามุมที่ใหญ่กว่า มุมทั้งสองเมื่อรวมกันแล้วจะทำให้เป็นวงกลมเต็ม เนื่องจากแต่ละมุมสามารถปรับให้พอดีกับช่วงที่กำหนดได้ค่ามุมที่น้อยกว่าคือเท่าใดโดยคำนึงถึงการโรลโอเวอร์


2
ฉันอ่าน 3 ครั้งก่อนที่จะเข้าใจว่าคุณหมายถึงอะไร กรุณาเพิ่มตัวอย่างหรืออธิบายให้ดีกว่านี้ ...
Kobi

ลองนึกภาพวงกลมที่มีเส้น 2 เส้นออกมาจากจุดศูนย์กลางมีมุม 2 มุมระหว่างเส้นเหล่านั้นมุมที่ทำจากด้านในหรือที่เรียกว่ามุมที่เล็กกว่าและมุมที่ทำด้านนอกหรือที่เรียกว่ามุมที่ใหญ่กว่า มุมทั้งสองเมื่อรวมกันแล้วจะทำให้เป็นวงกลมเต็ม ระบุว่าแต่ละมุมสามารถปรับให้พอดีกับช่วงที่กำหนดค่ามุมที่น้อยกว่าคือเท่าใดโดยคำนึงถึงโรลโอเวอร์
Tom J Nowell


2
@JimG. นี่ไม่ใช่คำถามเดียวกันในคำถามนี้มุม P1 ที่ใช้ในคำถามอื่นจะเป็นคำตอบที่ไม่ถูกต้องมันจะเป็นอีกมุมที่เล็กกว่า นอกจากนี้ยังไม่มีการรับประกันว่ามุมจะอยู่กับแกนนอน
Tom J Nowell

คำตอบ:


197

สิ่งนี้ให้มุมเซ็นสำหรับทุกมุม:

a = targetA - sourceA
a = (a + 180) % 360 - 180

ระวังในหลายภาษาการmoduloดำเนินการจะส่งคืนค่าที่มีเครื่องหมายเดียวกับเงินปันผล (เช่น C, C ++, C #, JavaScript, รายการทั้งหมดที่นี่ ) สิ่งนี้ต้องการmodฟังก์ชันที่กำหนดเองดังนี้:

mod = (a, n) -> a - floor(a/n) * n

หรือไม่ก็:

mod = (a, n) -> (a % n + n) % n

หากมุมอยู่ในระยะ [-180, 180] สิ่งนี้ก็ใช้ได้เช่นกัน:

a = targetA - sourceA
a += (a>180) ? -360 : (a<-180) ? 360 : 0

ด้วยวิธีที่ละเอียดยิ่งขึ้น:

a = targetA - sourceA
a -= 360 if a > 180
a += 360 if a < -180

ง่ายกว่าและสมเหตุสมผลกว่าอ่านออกเสียงแม้ว่าจะมีประสิทธิภาพเหมือนกัน แต่ bti แรกจะหามุมส่วนที่สองทำให้แน่ใจว่ามุมที่เป็นไปได้จะมีขนาดเล็กลงเสมอ
Tom J Nowell

1
แม้ว่าใคร ๆ จะต้องการหา% 360 เช่นถ้าฉันมีมุม 0 และมุมเป้าหมาย 721 คำตอบที่ถูกต้องจะเป็น 1 คำตอบที่ได้จากข้างต้นจะเป็น 361
Tom J Nowell

1
กระชับมากขึ้น a -= 360*sgn(a)*(abs(a) > 180)แต่อาจมีราคาแพงกว่าเทียบเท่าของคำสั่งที่สองวิธีหลังเป็น (ลองคิดดูสิถ้าคุณใช้งานแบบไม่แตกกิ่งก้านสาขาsgnและabsจากนั้นลักษณะดังกล่าวอาจเริ่มชดเชยการต้องคูณสองครั้ง)
mmirate

1
ตัวอย่าง "มุมที่มีลายเซ็นสำหรับมุมใด ๆ " ดูเหมือนจะใช้ได้ในสถานการณ์ส่วนใหญ่โดยมีข้อยกเว้นอย่างหนึ่ง ในสถานการณ์double targetA = 2; double sourceA = 359;'a' จะเท่ากับ -357.0 แทนที่จะเป็น 3.0
Stevoisiak

3
ใน C ++ คุณสามารถใช้ std :: fmod (a, 360) หรือ fmod (a, 360) เพื่อใช้ Floating Point Modulo
Joeppie

147

x คือมุมเป้าหมาย y คือแหล่งที่มาหรือมุมเริ่มต้น:

atan2(sin(x-y), cos(x-y))

จะส่งคืนมุมเดลต้าที่ลงนาม โปรดทราบว่าขึ้นอยู่กับ API ของคุณลำดับของพารามิเตอร์สำหรับฟังก์ชัน atan2 () อาจแตกต่างกัน


14
x-yให้ความแตกต่างของมุม แต่อาจอยู่นอกขอบเขตที่ต้องการ คิดว่ามุมนี้กำหนดจุดบนวงกลมหน่วย พิกัดของจุดนั้นคือ(cos(x-y), sin(x-y)). atan2ส่งคืนมุมสำหรับจุดนั้น (ซึ่งเทียบเท่ากับx-y) ยกเว้นช่วงคือ [-PI, PI]
สูงสุด

3
สิ่งนี้ผ่านชุดทดสอบgist.github.com/bradphelan/7fe21ad8ebfcb43696b8
bradgonesurfing

3
วิธีแก้ปัญหาง่ายๆเพียงบรรทัดเดียวและแก้ไขให้ฉัน (ไม่ใช่คำตอบที่เลือก;)) แต่การผกผันสีแทนเป็นกระบวนการที่มีค่าใช้จ่ายสูง
Mohan Kumar

2
สำหรับฉันวิธีแก้ปัญหาที่หรูหราที่สุด น่าเสียดายที่อาจมีราคาแพงในการคำนวณ
focs

สำหรับฉันเป็นทางออกที่สง่างามที่สุดเช่นกัน! แก้ไขปัญหาของฉันได้อย่างสมบูรณ์แบบ (ต้องการมีสูตรที่ให้มุมเลี้ยวที่เซ็นชื่อซึ่งมีขนาดเล็กกว่าจากสองทิศทาง / มุมเลี้ยวที่เป็นไปได้)
Jürgen Brauer

42

ถ้ามุมทั้งสองของคุณคือ x และ y มุมใดมุมหนึ่งระหว่างมุมทั้งสองคือ abs (x - y) อีกมุมหนึ่งคือ (2 * PI) - abs (x - y) ดังนั้นค่าของ 2 มุมที่เล็กที่สุดคือ:

min((2 * PI) - abs(x - y), abs(x - y))

สิ่งนี้ให้ค่าสัมบูรณ์ของมุมและถือว่าอินพุตเป็นแบบมาตรฐาน (กล่าวคืออยู่ในช่วง[0, 2π))

หากคุณต้องการรักษาเครื่องหมาย (เช่นทิศทาง) ของมุมและยอมรับมุมนอกช่วง[0, 2π)คุณสามารถสรุปข้อมูลข้างต้นได้ นี่คือรหัส Python สำหรับเวอร์ชันทั่วไป:

PI = math.pi
TAU = 2*PI
def smallestSignedAngleBetween(x, y):
    a = (x - y) % TAU
    b = (y - x) % TAU
    return -a if a < b else b

โปรดทราบว่าตัว%ดำเนินการจะไม่ทำงานเหมือนกันในทุกภาษาโดยเฉพาะอย่างยิ่งเมื่อเกี่ยวข้องกับค่าลบดังนั้นหากจำเป็นต้องย้ายการปรับสัญญาณบางอย่าง


1
@bradgonesurfing นั่นคือ / เป็นความจริง แต่เพื่อความเป็นธรรมการทดสอบของคุณจะตรวจสอบสิ่งที่ไม่ได้ระบุไว้ในคำถามดั้งเดิมโดยเฉพาะอินพุตที่ไม่เป็นมาตรฐานและการเก็บรักษาเครื่องหมาย เวอร์ชันที่สองในคำตอบที่แก้ไขควรผ่านการทดสอบของคุณ
Laurence Gonsalves

รุ่นที่สองยังใช้ไม่ได้สำหรับฉัน ลอง 350 และ 0 เช่น ควรส่งคืน -10 แต่ส่งกลับ -350
kjyv

@kjyv ฉันทำซ้ำพฤติกรรมที่คุณอธิบายไม่ได้ คุณสามารถโพสต์รหัสที่แน่นอน?
Laurence Gonsalves

อาฉันขอโทษ ฉันได้ทดสอบเวอร์ชันของคุณด้วย rad และองศาใน python อีกครั้งและใช้งานได้ดี ดังนั้นต้องเป็นความผิดพลาดในการแปล C # ของฉัน (อย่ามีอีกต่อไป)
kjyv

2
โปรดทราบว่าใน Python 3 คุณสามารถใช้ tau ได้จริง! from math import tauเขียนเพียง
mhartl

8

ฉันยอมรับความท้าทายในการให้คำตอบที่ลงชื่อ:

def f(x,y):
  import math
  return min(y-x, y-x+2*math.pi, y-x-2*math.pi, key=abs)

1
อ่า ... คำตอบคือฟังก์ชัน Python นั่นเอง ขออภัยฉันอยู่ในโหมด Python ชั่วขณะ หวังว่าคงไม่เป็นไร
David Jones

ฉันจะเสียบสูตรใหม่ลงในโค้ดของฉันที่ชั้นบนและดูว่ามันกลายเป็นอะไร! (ขอบคุณ ^ _ ^)
Tom J Nowell

1
ฉันค่อนข้างมั่นใจว่าคำตอบของ PeterB ก็ถูกต้องเช่นกัน และแฮ็คที่ชั่วร้าย :)
David Jones

4
แต่อันนี้ไม่มีฟังก์ชันตรีโกณ :)
nornagon

สูตรเทียบเท่าสำหรับ java คืออะไร? ถ้ามุมเป็นองศา?
Soley

6

สำหรับผู้ใช้ UnityEngine, วิธีที่ง่ายเป็นเพียงการใช้Mathf.DeltaAngle


1
ไม่มีเอาต์พุตที่ลงชื่อ
kjyv

5

วิธีการแก้ปัญหาทางคณิตศาสตร์ (ตรงข้ามกับอัลกอริทึม):

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

4
ชุดทดสอบนี้ล้มเหลวgist.github.com/bradphelan/7fe21ad8ebfcb43696b8
bradgonesurfing

ล้มเหลวหาก abs (a1-a2) >>> 360 ใช้สิ่งนี้แทน: stackoverflow.com/a/52432897/6050364
Adriel Jr

4

รหัสที่มีประสิทธิภาพใน C ++ ที่ใช้ได้กับทุกมุมและทั้งเรเดียนและองศาคือ:

inline double getAbsoluteDiff2Angles(const double x, const double y, const double c)
{
    // c can be PI (for radians) or 180.0 (for degrees);
    return c - fabs(fmod(fabs(x - y), 2*c) - c);
}


-1

ไม่จำเป็นต้องคำนวณฟังก์ชันตรีโกณมิติ รหัสง่ายๆในภาษา C คือ:

#include <math.h>
#define PIV2 M_PI+M_PI
#define C360 360.0000000000000000000
double difangrad(double x, double y)
{
double arg;

arg = fmod(y-x, PIV2);
if (arg < 0 )  arg  = arg + PIV2;
if (arg > M_PI) arg  = arg - PIV2;

return (-arg);
}
double difangdeg(double x, double y)
{
double arg;
arg = fmod(y-x, C360);
if (arg < 0 )  arg  = arg + C360;
if (arg > 180) arg  = arg - C360;
return (-arg);
}

ให้ dif = a - b เป็นเรเดียน

dif = difangrad(a,b);

ให้ dif = a - b เป็นองศา

dif = difangdeg(a,b);

difangdeg(180.000000 , -180.000000) = 0.000000
difangdeg(-180.000000 , 180.000000) = -0.000000
difangdeg(359.000000 , 1.000000) = -2.000000
difangdeg(1.000000 , 359.000000) = 2.000000

ไม่บาปไม่คอสไม่ตาล .... เรขาคณิตเท่านั้น !!!!


8
บัก! เนื่องจากคุณ # กำหนด PIV2 เป็น "M_PI + M_PI" ไม่ใช่ "(M_PI + M_PI)" บรรทัดarg = arg - PIV2;จะขยายเป็นarg = arg - M_PI + M_PIและไม่ทำอะไรเลย
แคน 7
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.