Perbedaan terkecil antara 2 Sudut

139

Diketahui 2 sudut dalam rentang -PI -> PI di sekitar koordinat, berapakah nilai terkecil dari 2 sudut di antara keduanya?

Memperhatikan bahwa perbedaan antara PI dan -PI bukanlah 2 PI melainkan nol.

Contoh:

Bayangkan sebuah lingkaran, dengan 2 garis keluar dari tengah, ada 2 sudut di antara garis-garis itu, sudut yang dibuat di dalam alias sudut yang lebih kecil , dan sudut yang dibuat di luar alias sudut yang lebih besar. Kedua sudut jika dijumlahkan membuat lingkaran penuh. Mengingat bahwa setiap sudut bisa muat dalam kisaran tertentu, berapa nilai sudut yang lebih kecil, dengan mempertimbangkan rollover

Tom J Nowell
sumber
2
Saya membaca 3 kali sebelum saya mengerti apa yang Anda maksud. Silakan tambahkan contoh, atau jelaskan lebih baik ...
Kobi
Bayangkan sebuah lingkaran, dengan 2 garis keluar dari tengah, ada 2 sudut di antara garis-garis itu, sudut yang dibuat di dalam alias sudut yang lebih kecil, dan sudut yang dibuat di luar alias sudut yang lebih besar. Kedua sudut jika dijumlahkan membuat lingkaran penuh. Mengingat bahwa setiap sudut bisa muat dalam kisaran tertentu, berapa nilai sudut yang lebih kecil, dengan mempertimbangkan rollover
Tom J Nowell
2
@Tokopedia ini bukan pertanyaan yang sama, dalam pertanyaan ini sudut P1 yang digunakan dalam pertanyaan lain adalah jawaban yang salah, akan menjadi sudut lain yang lebih kecil. Juga, tidak ada jaminan bahwa sudutnya dengan sumbu horizontal
Tom J Nowell

Jawaban:

197

Ini memberikan sudut yang ditandatangani untuk setiap sudut:

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

Hati-hati dalam banyak bahasa, modulooperasi mengembalikan nilai dengan tanda yang sama dengan dividen (seperti C, C ++, C #, JavaScript, daftar lengkap di sini ). Ini membutuhkan modfungsi khusus seperti:

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

Atau lebih:

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

Jika sudut berada dalam [-180, 180] ini juga berfungsi:

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

Dengan cara yang lebih bertele-tele:

a = targetA - sourceA
a -= 360 if a > 180
a += 360 if a < -180
bennedich
sumber
Lebih sederhana dan lebih masuk akal membaca dengan lantang, meskipun secara efektif hal yang sama, pertama bti menghitung sudutnya, bagian kedua memastikan selalu lebih kecil dari 2 sudut yang mungkin
Tom J Nowell
1
meskipun seseorang mungkin ingin melakukan% 360, misalnya jika saya memiliki sudut 0 dan sudut target 721, jawaban yang benar adalah 1, jawaban yang diberikan di atas adalah 361
Tom J Nowell
1
Pernyataan yang lebih ringkas, meskipun berpotensi lebih mahal, setara dengan pernyataan kedua pendekatan terakhir, adalah a -= 360*sgn(a)*(abs(a) > 180). (Kalau dipikir-pikir, jika Anda memiliki implementasi tanpa cabang dari sgndan abs, maka karakteristik itu mungkin benar-benar mulai mengimbangi kebutuhan dua perkalian.)
mmirate
1
Contoh "Sudut bertanda untuk setiap sudut" tampaknya berfungsi di sebagian besar skenario, dengan satu pengecualian. Dalam skenario double targetA = 2; double sourceA = 359;'a' akan sama dengan -357.0, bukan 3.0
Stevoisiak
3
Di C ++ Anda dapat menggunakan std :: fmod (a, 360), atau fmod (a, 360) untuk menggunakan modulo floating point.
Joeppie
147

x adalah sudut target. y adalah sumber atau sudut awal:

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

Ini mengembalikan sudut delta yang ditandatangani. Perhatikan bahwa bergantung pada API Anda, urutan parameter untuk fungsi atan2 () mungkin berbeda.

Peter B
sumber
14
x-ymemberi Anda perbedaan sudut, tetapi mungkin di luar batas yang diinginkan. Pikirkan sudut ini yang menentukan titik pada lingkaran satuan. Koordinat titik itu adalah (cos(x-y), sin(x-y)). atan2mengembalikan sudut untuk titik itu (yang setara dengan x-y) kecuali rentangnya adalah [-PI, PI].
Max
3
Ini melewati rangkaian pengujian gist.github.com/bradphelan/7fe21ad8ebfcb43696b8
bradgonesurfing
3
solusi sederhana satu baris dan dipecahkan untuk saya (bukan jawaban yang dipilih;)). tetapi tan inverse adalah proses yang mahal.
Mohan Kumar
2
Bagi saya, solusi paling elegan. Malu mungkin mahal secara komputasi.
focs
Bagi saya solusi paling elegan juga! Memecahkan masalah saya dengan sempurna (ingin memiliki rumus yang memberi saya sudut belok yang ditandatangani yang lebih kecil dari dua kemungkinan arah / sudut belokan).
Jürgen Brauer
42

Jika kedua sudut Anda adalah x dan y, maka salah satu sudut di antaranya adalah abs (x - y). Sudut lainnya adalah (2 * PI) - abs (x - y). Jadi nilai terkecil dari 2 sudut tersebut adalah:

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

Ini memberi Anda nilai absolut dari sudut, dan ini mengasumsikan input dinormalisasi (yaitu: dalam kisaran [0, 2π)).

Jika Anda ingin mempertahankan tanda (yaitu: arah) dari sudut dan juga menerima sudut di luar jangkauan, [0, 2π)Anda dapat menggeneralisasikan hal di atas. Berikut kode Python untuk versi umum:

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

Perhatikan bahwa %operator tidak berperilaku sama di semua bahasa, terutama ketika nilai negatif terlibat, jadi jika porting beberapa penyesuaian tanda mungkin diperlukan.

Laurence Gonsalves
sumber
1
@bradgonesurfing Itu benar, tetapi agar adil pengujian Anda memeriksa hal-hal yang tidak ditentukan dalam pertanyaan asli, khususnya masukan yang tidak dinormalisasi dan pelestarian tanda. Versi kedua dalam jawaban yang diedit harus lulus tes Anda.
Laurence Gonsalves
Versi kedua juga tidak berfungsi untuk saya. Coba 350 dan 0 misalnya. Ini harus mengembalikan -10 tetapi mengembalikan -350
kjyv
@kjyv Saya tidak bisa mereproduksi perilaku yang Anda gambarkan. Bisakah Anda memposting kode persisnya?
Laurence Gonsalves
Ah, maafkan aku. Saya telah menguji versi Anda dengan rad dan derajat dengan python lagi dan itu berfungsi dengan baik. Jadi pasti ada kesalahan dalam terjemahan saya ke C # (tidak memilikinya lagi).
kjyv
2
Perhatikan bahwa, pada Python 3, Anda sebenarnya dapat menggunakan tau secara asli! Tulis saja from math import tau.
mhartl
8

Saya menerima tantangan untuk memberikan jawaban yang ditandatangani:

def f(x,y):
  import math
  return min(y-x, y-x+2*math.pi, y-x-2*math.pi, key=abs)
David Jones
sumber
1
Ah ... jawabannya adalah fungsi Python. Maaf, saya menggunakan mode Python sebentar. Semoga tidak apa-apa.
David Jones
Saya akan memasukkan rumus baru ke dalam kode saya di lantai atas dan melihat apa jadinya! (terima kasih ^ _ ^)
Tom J Nowell
1
Saya cukup yakin jawaban PeterB juga benar. Dan bajingan jahat. :)
David Jones
4
Tapi yang ini tidak mengandung fungsi trigonometri :)
nornagon
Apa rumus ekuivalen untuk java? jika sudut dalam derajat.?
Soley
6

Untuk pengguna UnityEngine, cara termudah adalah dengan menggunakan Mathf.DeltaAngle .

Josh
sumber
1
Tidak memiliki keluaran yang ditandatangani tho
kjyv
4

Kode efisien dalam C ++ yang berfungsi untuk semua sudut dan di kedua: radian dan derajat adalah:

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);
}
Adriel Jr
sumber
0

Metode sederhana, yang saya gunakan di C ++ adalah:

double deltaOrientation = angle1 - angle2;
double delta =  remainder(deltaOrientation, 2*M_PI);
Eduard
sumber
-1

Tidak perlu menghitung fungsi trigonometri. Kode sederhana dalam bahasa C adalah:

#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);
}

misalkan dif = a - b, dalam radian

dif = difangrad(a,b);

biarkan dif = a - b, dalam derajat

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

Tanpa dosa, tanpa cos, tanpa tan, .... hanya geometri !!!!

Uli Gue
sumber
8
Bug! Karena Anda #menentukan PIV2 sebagai "M_PI + M_PI", bukan "(M_PI + M_PI)", garis arg = arg - PIV2;akan meluas ke arg = arg - M_PI + M_PI, dan begitu juga tidak.
canton7