So sánh các góc và tìm ra sự khác biệt


27

Tôi muốn so sánh các góc và có được một ý tưởng về khoảng cách giữa chúng. Đối với ứng dụng này, tôi đang làm việc theo độ, nhưng nó cũng sẽ hoạt động cho radian và grads. Vấn đề với các góc là chúng phụ thuộc vào số học mô-đun, tức là 0-360 độ.

Nói một góc là 15 độ và một góc là 45. Chênh lệch là 30 độ, và góc 45 độ lớn hơn góc 15 độ.

Nhưng, điều này bị phá vỡ khi bạn có, nói, 345 độ và 30 độ. Mặc dù họ so sánh đúng, sự khác biệt giữa chúng là 315 độ thay vì 45 độ chính xác.

Làm sao tôi có thể giải quyết việc này? Tôi có thể viết mã thuật toán:

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

Nhưng tôi thích một giải pháp tránh so sánh / các nhánh và hoàn toàn dựa vào số học.


Về vấn đề này, chúng ta có thể giả sử rằng các góc đã cho nằm trong phạm vi [0,360] hoặc (-infinite, + vô hạn) không? Chẳng hạn, thuật toán cũng nên hoạt động khi so sánh -130 độ với 450?
egarcia

Giả sử các góc được chuẩn hóa theo phạm vi đó.
Thomas O

Câu trả lời:


29

Đây là phiên bản đơn giản, không phân nhánh, không so sánh, không có min / max của tôi:

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

Đã xóa modulo, vì các đầu vào bị hạn chế đủ (nhờ Martin đã chỉ ra điều đó).

Hai abs, ba trừ.


Bạn không cần modulo, các giá trị đầu vào được giới hạn trong phạm vi [0,360] (xem nhận xét của Thomas về nội dung gửi ban đầu). Khá gọn gàng.
Martin Sojka

À, vâng, bạn đúng. Tôi đã có đầu vào ít nghiêm ngặt hơn khi tôi thử nó.
JasonD

Nhưng nếu bạn muốn giữ dấu hiệu của sự khác biệt để bạn có thể biết cái nào ở bên trái thì sao?
Jacob Phillips

9

Mặc dù họ so sánh đúng, sự khác biệt giữa chúng là 315 độ thay vì 45 độ chính xác.

Điều gì khiến bạn nghĩ rằng 315 là không chính xác? Theo một hướng, nó là 315 độ, theo hướng khác, đó là 45. Bạn muốn chọn bất kỳ góc nào là nhỏ nhất trong 2 góc có thể và về bản chất điều này đòi hỏi phải có điều kiện. Bạn không thể giải quyết nó bằng số học bao quanh (tức là thông qua toán tử mô đun) bởi vì khi bạn tăng dần một góc, góc giữa chúng sẽ tăng lên cho đến khi nó chạm 180 và sau đó bắt đầu giảm dần.

Tôi nghĩ bạn phải kiểm tra cả hai góc và quyết định hướng nào bạn muốn đo, hoặc tính cả hai hướng và quyết định kết quả nào bạn muốn.


Xin lỗi tôi nên làm rõ. Nếu bạn làm ngược lại, 30 - 345 là -315 và góc âm sẽ không có ý nghĩa nhiều. Tôi đoán tôi đang tìm góc nhỏ nhất giữa hai người. tức là 45 độ nhỏ hơn 315.
Thomas O

2
Nhưng không có "đảo ngược" - bạn có 2 góc và 2 loại xoay bạn có thể thực hiện để làm cho một góc khớp với nhau. Một góc tiêu cực có ý nghĩa hoàn hảo - xét cho cùng, đó chỉ là một phép đo xoay từ một trục tùy ý.
Kylotan

Nếu bạn muốn góc nhỏ nhất thì abs (a1% 180 - a2% 180) sẽ cung cấp cho bạn góc đó. Nó sẽ không cho bạn biết hướng đi, tuy nhiên. Xóa cơ bụng sẽ cho bạn góc nhỏ nhất "đi từ" a1 "đến" a2
Chewy Gumball

2
@Chewy hả? Sự khác biệt giữa 180 và 0 không phải là 0 và sự khác biệt giữa 181 và 0 không phải là 1 ...
dash-tom-bang

1
@ dash-tom-bang Bạn hoàn toàn đúng. Tôi không biết mình đang nghĩ gì, nhưng giờ nó không còn đúng nữa khi tôi nhìn lại. Xin vui lòng bỏ qua bình luận trước đây của tôi.
Chewy Gumball

4

Luôn có mẹo làm cả hai nhánh và để kết quả so sánh chọn một:

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

Tôi không biết cách nào để làm điều đó mà không so sánh , nhưng thường là chi nhánh là thứ làm cho mã chậm và dài, không phải là so sánh. Ít nhất theo ý kiến ​​của tôi, điều này dễ đọc hơn câu trả lời của Martin (bất kỳ lập trình viên giỏi C nào cũng sẽ nhận ra nó là tương đương không phân nhánh và xem những gì nó đang làm), nhưng cũng kém hiệu quả hơn.

Nhưng như tôi đã nói trong nhận xét của mình, thuật toán không phân nhánh rất tốt trên các bộ xử lý có đường ống sâu và dự đoán xấu - một vi điều khiển thường có một đường ống nhỏ và máy tính để bàn thường có dự đoán tốt, vì vậy trừ khi bạn nhắm mục tiêu vào máy chơi game, phiên bản phân nhánh có khả năng tuyến đường tốt nhất nếu nó làm giảm số lượng hướng dẫn.

Như mọi khi, hồ sơ - có thể đơn giản như đếm ngược cho hệ thống của bạn - sẽ cho bạn câu trả lời thực sự.


2

Giả sử đánh giá đúng thành -1 và đánh giá sai thành 0 và '~', '&' và '|' là Bitwise không , hoặc khai thác tương ứng, và chúng tôi đang làm việc với two's-bổ sung cho số học:

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 vì nó thông minh, nhưng trên vi điều khiển, điều này có thể hoạt động kém hơn nhiều so với phiên bản rẽ nhánh.

Phụ thuộc một chút vào vi điều khiển, nhưng, vâng, nó thường không có giá trị; một bước nhảy có điều kiện (ngắn) thường đủ nhanh. Ngoài ra, dòng thứ ba và thứ năm có thể được viết lại để nhanh hơn một chút bằng cách sử dụng thao tác xor (^) như thế này, nhưng tôi đã để chúng ở dạng hiện tại cho rõ ràng: temp1: = temp2 ^ ((temp2 ^ -temp2) & ~ temp1), temp2: = temp1 ^ ((temp1 ^ (360 - temp1)) & ~ temp2)
Martin Sojka

1

Cái này thì sao?

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

Việc bổ sung 360 là có để tránh sự khác biệt âm, bởi vì một modulo của số âm trả về kết quả âm. Sau đó, bạn nhận được kết quả nhỏ hơn trong hai kết quả có thể.

Vẫn còn một quyết định ngầm, nhưng tôi không biết làm thế nào để tránh nó. Về cơ bản, bạn so sánh hai góc bằng cách tính toán sự khác biệt theo chiều kim đồng hồ hoặc ngược chiều kim đồng hồ, và dường như bạn rõ ràng muốn nhỏ hơn trong hai sự khác biệt này. Tôi không biết làm thế nào để có được kết quả đó mà không so sánh chúng. Đó là, không sử dụng "abs", "min", "max" hoặc một số toán tử tương tự.


Có một số cách để tính toán min, max và abs của int mà không có hướng dẫn chi nhánh, mặc dù vì đây là vi điều khiển nên nhánh có lẽ là cách nhanh nhất. Graphics.stanford.edu/~seander/bithacks.html#IntegerAbs

1

Trong khi câu hỏi của bạn không liên quan đến chúng, tôi sẽ làm việc với giả định rằng câu hỏi tính toán góc của bạn bắt nguồn từ việc muốn biết góc tối thiểu giữa hai vectơ .

Tính toán đó thật dễ dàng. Giả sử A và B là các vectơ của bạn:

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

Nếu bạn không có vectơ và muốn sử dụng phương pháp này, bạn có thể xây dựng vectơ độ dài đơn vị cho các góc của mình bằng cách thực hiện new Vector2( cos( angle ), sin ( angle ) ).


1
Bộ xử lý tôi đang làm việc là một vi điều khiển nhỏ. Không có nghĩa gì khi sử dụng các hàm lượng giác để tạo ra một vectơ chỉ để có được sự khác biệt giữa các góc, mỗi chu kỳ đều quý giá.
Thomas O

1
Trên một vi điều khiển, tôi cho rằng không nên sử dụng một nhánh, nhưng câu trả lời của tôi không có nhiều vấn đề nếu bạn thực sự muốn tránh phân nhánh.
JasonD

Vâng, một nhánh là hai chu kỳ và một phép cộng / trừ / vv là một chu kỳ, nhưng việc phân nhánh cũng chiếm thêm bộ nhớ chương trình. Nó không quan trọng, nhưng nó sẽ tốt đẹp.
Thomas O

Tôi có cảm giác câu trả lời của bạn là đúng và của tôi là sai, nhưng tôi không thể hiểu được tại sao lại như vậy. :)
Kylotan

1

Về cơ bản giống như câu trả lời của JasonD, ngoại trừ sử dụng các phép toán bitwise thay vì hàm giá trị tuyệt đối.

Điều này là giả sử bạn có số nguyên ngắn 16 bit!

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

tôi nghĩ

delta = (a2 + Math.ceil( -a2 / 360 ) * 360) - (a1 + Math.ceil( -a1 / 360 ) * 360);

0

Vì bạn chỉ quan tâm đến việc loại bỏ các nhánh và các hoạt động "phức tạp" ngoài số học, tôi sẽ khuyên bạn nên điều này:

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

Bạn vẫn cần một abs trong đó mặc dù tất cả các góc độ là tích cực. Mặt khác, kết quả âm tính nhất sẽ luôn được chọn (và sẽ luôn có chính xác một câu trả lời phủ định cho dương và duy nhất a và b khi so sánh ab và ba).

Lưu ý: Điều này sẽ không bảo toàn hướng giữa angle1 và angle2. Đôi khi bạn cần điều đó cho mục đích AI.

Điều này tương tự như câu trả lời của CeeJay, nhưng loại bỏ tất cả các chế độ. Tôi không biết chi phí chu kỳ là bao nhiêu abs, nhưng tôi sẽ đoán là 1 hoặc 2. Khó có thể nói chi phí là bao nhiêumin . Có thể 3? Vì vậy, cùng với 1 chu kỳ cho mỗi phép trừ, dòng này sẽ có chi phí khoảng 4 đến 9.


0

Lấy góc tương đối nhỏ hơn ở dạng đã ký (+/-), từ góc độ hướng tới muốn :

  • nhất 180 độ | PI radian
  • Được chỉ định nếu ngược chiều kim đồng hồ
  • + ký nếu theo chiều kim đồng hồ

Độ

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

Xạ hương

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

Cơ sở lý luận

Tôi đã xem qua chủ đề này sau khi tôi đã tìm ra điều này, tìm kiếm một giải pháp tránh modulo; Cho đến nay tôi không tìm thấy . Giải pháp này là để bảo tồn dấu hiệu phối cảnh, vì @ jacob-phillips đã hỏi nhận xét này . Có những giải pháp rẻ hơn nếu bạn chỉ cần góc không dấu ngắn nhất.


0

Đó là một câu hỏi cũ nhưng tôi đã gặp phải trường hợp tương tự - phải nhận được sự khác biệt về góc cạnh và tốt nhất là không có các nhánh và toán nặng. Đây là những gì tôi đã kết thúc với:

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

Hạn chế là 'b' không nên có nhiều hơn 'N' xoay so với 'a'. Nếu bạn không thể đảm bảo nó và có thể cho phép các hoạt động bổ sung thì hãy sử dụng dòng này làm dòng đầu tiên:

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

Tôi đã có ý tưởng từ bình luận thứ 13 của bài đăng này: http://blog.lexique-du-net.com/index.php?post/Calculate-the-real-difference-b between-two-angles -keep-the- ký tên


-1

tôi đoán tôi có thể nói

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

Tất nhiên, xem xét các góc được đo bằng độ.


1
Tôi không tin rằng điều này giải quyết vấn đề trong câu hỏi. 345% 360 == 345 và abs (345-30) vẫn là 315.
Gregory Avery-Weir

@Gregory: okay!, Tôi xin lỗi vì sai lầm. Tôi đang chỉnh sửa trả lời, kiểm tra cái mới này. :)
Vishnu

1
Nhân tiện, angle1 = angle1% 360; angle2 = angle2% 360; var distance = Math.abs (angle1-angle2); giống như var distance = Math.abs (angle1-angle2)% 360 - chỉ chậm hơn.
Martin Sojka
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.