การเปรียบเทียบมุมและหาความแตกต่าง


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

สมมติว่ามุมถูกทำให้เป็นมาตรฐานในช่วงนั้น
โทมัส O

คำตอบ:


29

นี่คือเวอร์ชันเรียบง่ายไม่มีสาขาเปรียบเทียบฟรีไม่มีรุ่นต่ำสุด / สูงสุด:

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

นำโมดูโล่ออกเนื่องจากอินพุตมีข้อ จำกัด เพียงพอ (ขอบคุณมาร์ตินสำหรับการชี้ให้เห็น)

สอง abs, สามลบ


คุณไม่ต้องการโมดูโลค่าอินพุตจะถูก จำกัด ช่วง [0,360] (ดูความคิดเห็นของโทมัสต่อการส่งต้นฉบับ) สวยเนี๊ยบ
Martin Sojka

อ่าใช่คุณพูดถูก ฉันมีการป้อนข้อมูลที่เข้มงวดน้อยลงเมื่อฉันลอง
JasonD

แต่ถ้าคุณต้องการรักษาสัญลักษณ์ของความแตกต่างเพื่อให้คุณสามารถบอกได้ว่าใครอยู่ทางซ้าย
Jacob Phillips

9

แม้ว่าจะเปรียบเทียบอย่างเหมาะสม แต่ความแตกต่างระหว่างพวกเขาคือ 315 องศาแทนที่จะเป็น 45 องศาที่ถูกต้อง

อะไรทำให้คุณคิดว่า 315 ไม่ถูกต้อง ในทิศทางหนึ่งคือ 315 องศาในอีกทางหนึ่งคือ 45 คุณต้องการเลือกมุมใดก็ได้ที่เล็กที่สุดของ 2 มุมที่เป็นไปได้และสิ่งนี้ดูเหมือนจะต้องการเงื่อนไข คุณไม่สามารถแก้มันด้วยการคำนวณแบบล้อมรอบ (เช่นผ่านตัวดำเนินการโมดูลัส) เพราะเมื่อคุณค่อยๆเพิ่มมุมหนึ่งมุมมุมนั้นจะเพิ่มขึ้นเรื่อย ๆ จนกว่ามันจะถึง 180 และจะเริ่มลดลง

ฉันคิดว่าคุณต้องตรวจสอบทั้งสองมุมและตัดสินใจว่าคุณต้องการวัดทิศทางใดหรือคำนวณทั้งสองทิศทางและตัดสินใจว่าต้องการผลลัพธ์ใด


ขอโทษฉันควรชี้แจง ถ้าคุณทำแบบย้อนกลับ 30 - 345 คือ -315 และมุมลบไม่สมเหตุสมผล ฉันเดาว่าฉันกำลังมองหามุมที่เล็กที่สุดระหว่างสองมุม เช่น 45 องศาเล็กกว่า 315
โทมัส O

2
แต่ไม่มี 'การย้อนกลับ' - คุณมี 2 มุมและการหมุน 2 ประเภทคุณสามารถเล่นเพื่อให้เข้าคู่กัน มุมลบทำให้รู้สึกดี - เป็นเพียงการวัดการหมุนจากแกนที่กำหนดเอง
Kylotan

ถ้าคุณต้องการมุมที่เล็กที่สุดแล้ว abs (a1% 180 - a2% 180) จะให้มุมนั้นกับคุณ มันจะไม่บอกทิศทาง การถอด abs จะทำให้คุณได้มุมที่เล็กที่สุด "ไปจาก" a1 "ถึง" a2
Chewy Gumball

2
@ ยอดเยี่ยมใช่มั้ย ความแตกต่างระหว่าง 180 และ 0 ไม่ใช่ 0 และความแตกต่างระหว่าง 181 และ 0 ไม่ใช่ 1 ...
dash-tom-bang

1
@ dash-tom-bang คุณค่อนข้างถูกต้อง ฉันไม่รู้ว่าฉันคิดอะไรอยู่ แต่มันก็ไม่ถูกต้องเลยตอนนี้ที่ฉันดูอีกครั้ง โปรดไม่สนใจความคิดเห็นก่อนหน้าของฉัน
Chewy Gumball

4

มีเคล็ดลับในการทำทั้งสองสาขาเสมอและให้ผลการเปรียบเทียบเลือกหนึ่ง:

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

ฉันไม่รู้วิธีที่จะทำโดยไม่มีการเปรียบเทียบแต่โดยปกติแล้วสาขาคือสิ่งที่ทำให้โค้ดช้าและยาวไม่ใช่การเปรียบเทียบ อย่างน้อยในความคิดของฉันนี้สามารถอ่านได้มากกว่าคำตอบของมาร์ติน (โปรแกรมเมอร์ C ที่ดีคนใดจะจำได้ว่ามันเทียบเท่ากับไร้สาขาและดูว่ามันกำลังทำอะไรอยู่) แต่ก็มีประสิทธิภาพน้อยกว่าด้วย

แต่อย่างที่ฉันพูดในความคิดเห็นของฉันอัลกอริธึมไร้สาขาเป็นสิ่งที่ดีในโปรเซสเซอร์ที่มีท่อลึกและการคาดการณ์ที่ไม่ดี - ไมโครคอนโทรลเลอร์มักจะมีท่อเล็ก ๆ และพีซีตั้งโต๊ะมักจะมีการคาดการณ์ที่ดี เป็นเส้นทางที่ดีที่สุดหากลดจำนวนคำสั่ง

การทำโปรไฟล์ - ซึ่งอาจจะง่ายเหมือนการนับระบบของคุณ - จะให้คำตอบที่แท้จริง


2

สมมติว่าค่าจริงเป็น -1 และเท็จประเมินเป็น 0 และ '~', '&' และ '|' เป็นค่าที่เหมาะสมไม่ได้ , และและหรือผู้ประกอบการตามลำดับและเรากำลังทำงานกับ two's-เสริมคณิตศาสตร์:

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

1

แล้วเรื่องนี้ล่ะ

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

การเพิ่ม 360 จะมีเพื่อหลีกเลี่ยงความแตกต่างเชิงลบเนื่องจากโมดูโลของจำนวนลบส่งกลับผลลัพธ์เป็นลบ จากนั้นคุณจะได้ผลลัพธ์ที่เป็นไปได้ที่น้อยลง

ยังคงมีการตัดสินใจโดยนัย แต่ฉันไม่รู้วิธีหลีกเลี่ยง โดยทั่วไปคุณเปรียบเทียบมุมทั้งสองด้วยการคำนวณความแตกต่างตามเข็มนาฬิกาหรือทวนเข็มนาฬิกาและดูเหมือนว่าคุณต้องการความแตกต่างสองอย่างนี้น้อยลงอย่างชัดเจน ฉันไม่รู้วิธีรับผลลัพธ์นั้นโดยไม่เปรียบเทียบ นั่นคือโดยไม่ใช้ "abs", "min", "max" หรือตัวดำเนินการที่คล้ายกัน


มีหลายวิธีในการคำนวณ min, max, และ abs ของ ints โดยไม่มีคำสั่งสาขา แต่เนื่องจากนี่เป็นไมโครคอนโทรลเลอร์สาขาอาจเป็นวิธีที่เร็วที่สุด 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
โปรเซสเซอร์ที่ฉันกำลังทำงานอยู่เป็นไมโครคอนโทรลเลอร์ขนาดเล็ก มันไม่สมเหตุสมผลเลยที่จะใช้ฟังก์ชันตรีโกณฯ เพื่อสร้างเวกเตอร์เพียงเพื่อให้ได้ความแตกต่างระหว่างมุมทุกรอบมีค่า
โทมัส O

1
ในไมโครคอนโทรลเลอร์ฉันรู้สึกแปลกใจที่ไม่ได้ใช้สาขา แต่ไม่ได้ตอบคำถามของฉันถ้าคุณต้องการหลีกเลี่ยงการแตกกิ่งก้านสาขา
JasonD

สาขามีสองรอบและเพิ่ม / ลบ / ฯลฯ เป็นหนึ่งรอบ แต่การแยกยังใช้หน่วยความจำโปรแกรมเพิ่มเติม มันไม่สำคัญ แต่มันจะดี
โทมัส O

ฉันรู้สึกว่าคำตอบของคุณถูกต้องและของฉันผิด แต่ฉันไม่สามารถเข้าใจได้ว่าทำไมถึงเป็นเช่นนั้น :)
Kylotan

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ในแม้จะมีมุมทั้งหมดเป็นบวก มิฉะนั้นผลลัพธ์เชิงลบส่วนใหญ่จะถูกเลือกเสมอ (และจะมีคำตอบเชิงลบเพียงคำตอบเดียวสำหรับบวก a และ b ที่เป็นเอกลักษณ์เมื่อเปรียบเทียบ ab และ ba)

หมายเหตุ: สิ่งนี้จะไม่รักษาทิศทางระหว่าง angle1 และ angle2 บางครั้งคุณจำเป็นต้องมีเพื่อ AI

นี่คล้ายกับคำตอบของ CeeJay แต่กำจัดโมดูโลทั้งหมด ฉันไม่ทราบว่าค่าใช้จ่ายในรอบนั้นเป็นabsเท่าไหร่ แต่ฉันจะเดาว่าเป็น 1 หรือ 2 ยากที่จะบอกว่าราคาminเท่าไหร่ อาจจะ 3 ดังนั้นพร้อมกับ 1 รอบต่อการลบบรรทัดนี้ควรมีราคาประมาณ 4 ถึง 9


0

รับญาติที่มีขนาดเล็กในมุมลงนามในแบบฟอร์ม (+/-) จากมุมมองของมีต่อความต้องการ :

  • ไม่เกิน 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;

ข้อ จำกัด คือ 'b' ไม่ควรมีการหมุนมากกว่า 'N' เมื่อเทียบกับ 'a' หากคุณไม่สามารถมั่นใจได้และสามารถอนุญาตให้มีการดำเนินการเพิ่มเติมให้ใช้สิ่งนี้เป็นบรรทัดแรก:

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;

ofcourse เมื่อพิจารณามุมจะวัดเป็นองศา


1
ฉันไม่เชื่อว่านี่จะแก้ปัญหาในคำถาม 345% 360 == 345 และ abs (345-30) ยังคงเป็น 315
Gregory Avery-Weir

@ เกรกอรี่: โอเค! ฉันขอโทษสำหรับความผิดพลาด ฉันกำลังแก้ไขคำตอบตรวจสอบอันใหม่นี้ :)
พระนารายณ์

1
โดยวิธีการที่ angle1 = angle1% 360; angle2 = angle2% 360; ระยะทาง var = Math.abs (angle1-angle2); เหมือนกับ var distance = Math.abs (angle1-angle2)% 360 - ช้ากว่า
Martin Sojka
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.