Açıları karşılaştırma ve farkı çözme


27

Açıları karşılaştırmak ve aralarındaki mesafe hakkında bir fikir edinmek istiyorum. Bu uygulama için derecelerde çalışıyorum, ancak radyan ve derecelerde de işe yarayacak. Açılarla ilgili problem, modüler aritmetik, yani 0-360 dereceye bağlı olmalarıdır.

Diyelim ki bir açı 15 derece ve bir 45 derecedir. Fark 30 derecedir ve 45 derece açı 15 derece olandan büyüktür.

Ancak, bu, örneğin 345 derece ve 30 dereceye sahip olduğunuzda bozuluyor. Her ne kadar doğru bir şekilde karşılaştırsalar da, aralarındaki fark doğru 45 derece yerine 315 derecedir.

Bunu Nasıl Çözebilirim? Algoritmik kod yazabilirim:

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

Ama ben karşılaştırmak / dallar önlemek ve tamamen aritmetik dayanan bir çözüm tercih ediyorum.


Bu problemde, verilen açıların [0,360] veya (-sonsuz, + sonsuz) aralığında olduğunu varsayabilir miyiz? Örneğin, algoritma -130 dereceyi 450 ile karşılaştırmaya da çalışmalı mı?
egarcia

Açıların bu aralığa normalize olduğunu varsayın.
Thomas O

Yanıtlar:


29

İşte benim basitleştirilmiş, dalsız, kıyaslama gerektirmeyen, minimum / maks versiyonum:

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

Girişler yeterince kısıtlı olduğundan modulo kaldırıldı (bunu işaret ettiği için Martin'e teşekkürler).

İki abs, üç çıkar.


Moduloya ihtiyacınız yoktur, giriş değerleri [0,360] aralığıyla sınırlıdır (bkz. Thomas'ın orijinal gönderime yaptığı yorum). Oldukça temiz.
Martin Sojka

Ah, evet, haklısın. Denedim daha az katı girdi.
JasonD

peki ya hangisinin solda olduğunu söyleyebilmeniz için farkın işaretini korumak istiyorsanız?
Jacob Phillips

9

Her ne kadar doğru bir şekilde karşılaştırsalar da, aralarındaki fark doğru 45 derece yerine 315 derecedir.

315'in yanlış olduğunu düşündüren nedir? Bir yönde, 315 derece, diğer yönde ise 45'tir. Hangisinin 2 olası açının en küçüğü olduğunu seçmek istiyorsunuz ve bu kendinden koşullu olarak gerekli görünüyor. Etrafı çevreleyen aritmetik ile çözemezsiniz (örn. Modulus operatörü ile) çünkü bir açıyı kademeli olarak yükseltirken, aralarındaki açı 180 olana kadar büyür ve sonra düşmeye başlar.

Her iki açıyı da kontrol etmeniz ve hangi yöne ölçmek istediğinize karar vermeniz ya da her iki yöne hesaplamanız ve hangi sonucu istediğinize karar vermeniz gerektiğini düşünüyorum.


Üzgünüm netleştirmeliyim. Tersten yaptıysanız, 30 - 345 -315'tir ve negatif bir açı pek mantıklı gelmez. Sanırım ikisi arasındaki en küçük açıyı arıyorum. yani 45 derece 315'ten daha küçüktür.
Thomas O

2
Ancak 'tersi' yoktur - biri diğerini eşleştirmek için gerçekleştirebileceğiniz 2 açı ve 2 dönüş tipiniz vardır. Negatif bir açı mükemmel bir anlam ifade eder - sonuçta isteğe bağlı bir eksenden dönme ölçüsüdür.
Kylotan

En küçük açıyı istiyorsanız, o zaman abs (a1% 180 - a2% 180) bu açıyı verecektir. Bununla birlikte, size yön vermeyecektir. Absın kaldırılması size "a1" den "a2" ye kadar giden en küçük açıyı verecektir
Chewy Gumball

2
@Chewy, ha? 180 ile 0 arasındaki fark 0 değil ve 181 ile 0 arasındaki fark 1 değil ...
dash-tom-bang

1
@ dash-tom-bang Çok haklısın. Ne düşündüğümü bilmiyorum, ama şimdi tekrar bakmam doğru değildi. Lütfen önceki yorumumu dikkate almayın.
Chewy Gumball

4

Her iki dalı da yapmanın ve karşılaştırma sonucunun bir tanesini seçmesine izin vermenin hilesi vardır:

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

Karşılaştırma yapmadan yapmanın bir yolunu bilmiyorum , ama genellikle dal , karşılaştırmayı değil, kodu yavaş ve uzun yapan şeydir. En azından benim görüşüme göre, bu Martin'in cevabından daha okunaklı (herhangi bir iyi C programcısı bunu dalsız bir eşdeğer olarak tanıyacak ve ne yaptığını görecek), ama aynı zamanda daha az verimli.

Ancak benim yorumumda dediğim gibi, branşsız algoritmalar derin boru hatları ve kötü tahminleri olan işlemcilerde iyidir - bir mikroişlemci genellikle küçük bir boru hattına sahiptir ve bir masaüstü bilgisayarın genellikle iyi bir tahmini vardır, bu yüzden bir oyun konsolunu hedeflemediğiniz sürece, dallanma sürümü talimat sayısını azaltırsa muhtemelen en iyi yoldur.

Her zaman olduğu gibi, profilleme - sisteminiz için sayma kadar basit olabilir - size gerçek cevabı verecektir.


2

Varsayarsak doğru -1 değerlendirir ve yanlış '|' 0 değerlendirir ve '~', '&' ve bitdüzeyi olan değil , ve ve ya operatörler sırasıyla ve Birlikte çalıştığımız two's-tamamlayacak aritmetik:

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 zekice olduğu için, ancak bir mikroişlemci üzerinde bu muhtemelen dallanma sürümünden daha kötü performans gösteriyor.

Mikrodenetleyiciye biraz bağlı, ancak, evet, genellikle buna değmez; (kısa) koşullu bir sıçrama tipik olarak yeterince hızlıdır. Ayrıca, üçüncü ve beşinci satırlar, xor (^) işlemini bu şekilde kullanarak biraz daha hızlı olacak şekilde yeniden yazılabilir, ancak bunları açıklık için geçerli biçimde bıraktım: temp1: = temp2 ^ ((temp2 ^ -temp2) & ~ temp1), temp2: = temp1 ^ ((temp1 ^ (360 - temp1)) & ~ temp2)
Martin Sojka

1

Peki buna ne dersin?

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

Negatif farklılıklardan kaçınmak için 360 eklemesi vardır, çünkü negatif bir sayıdaki bir modülo negatif sonuç verir. Sonra iki olası sonuçtan daha küçük elde edersiniz.

Hala kesin bir karar var, ama nasıl önleneceğini bilmiyorum. Temel olarak, iki açıyı farkı saat yönünde veya saatin tersi yönünde hesaplayarak karşılaştırırsınız ve açıkça bu iki farklılığın daha küçük olmasını istersiniz. Bu sonucu, karşılaştırmadan nasıl elde edeceğimi bilmiyorum. Yani, "abs", "min", "max" veya benzer bir operatör kullanmadan.


Branşman talimatı olmadan minimum, maks ve girişlerin hesaplanması için birkaç yol vardır, ancak bu bir mikrodenetleyici olduğundan, dal muhtemelen en hızlı yoldur. graphics.stanford.edu/~seander/bithacks.html#IntegerAbs

1

Sorunuz onlara referans göstermediyse de, açı hesaplama sorunuzun iki vektör arasındaki minimum açıyı bilmek istemekten kaynaklandığı varsayımı üzerinde çalışacağım .

Bu hesaplama kolaydır. A ve B'nin sizin vektörleriniz olduğunu varsayalım:

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

Vektörleriniz yoksa ve bu yaklaşımı kullanmak istiyorsanız, açılarını vererek birim uzunluk vektörlerini oluşturabilirsiniz new Vector2( cos( angle ), sin ( angle ) ).


1
Üzerinde çalışmakta olduğum işlemci küçük bir mikrodenetleyicidir. Sadece açılar arasındaki farkı elde etmek için bir vektör üretmek için trig fonksiyonlarını kullanmak mantıklı değildir, her döngü değerlidir.
Thomas O

1
Bir mikrodenetleyicide, bir dal kullanmak daha iyi değil, ama gerçekten dallanmayı önlemek istiyorsanız cevabımda çok fazla aritmatik yok.
JasonD,

Bir dal iki döngüdür ve toplama / çıkarma / etc bir döngüdür, fakat dallanma da ek program hafızasını alır. Kritik değil, ama iyi olurdu.
Thomas O,

Cevabınızın doğru olduğu ve benimkilerin yanlış olduğu hissine kapılıyorum, ancak bunun neden böyle olduğunu kafamdan alamıyorum . :)
Kylotan

1

Temelde mutlak değer fonksiyonu yerine bitsel işlemleri kullanmak hariç, JasonD'nin cevabı ile aynı.

Bu, 16 bitlik kısa tamsayılara sahip olduğunuzu varsayıyor!

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

bence

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

0

Sadece aritmetik işlemlerin ötesinde dalları ve "karmaşık" işlemleri ortadan kaldırmaya önem verdiğiniz için şunu tavsiye ederim:

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

absTüm açıların olumlu olmasına rağmen hala orada bir yere ihtiyacınız var. Aksi takdirde, en olumsuz sonuç her zaman seçilecektir (ve ab ve ba'yı karşılaştırırken her zaman pozitif, benzersiz a ve b için tam bir negatif cevap olacaktır).

Not: Bu, angle1 ve angle2 arasındaki yönü korumaz. Bazen AI amaçları için buna ihtiyacınız vardır.

Bu, CeeJay'in cevabına benzer, ancak tüm modülleri ortadan kaldırır. Döngü maliyetinin ne olduğunu bilmiyorum abs, ancak maliyetin 1 veya 2 olduğunu tahmin edeceğim min. Belki 3? Bu yüzden, çıkarma başına 1 döngü ile birlikte, bu çizginin 4 ila 9 civarında bir yerde bir maliyeti olmalıdır.


0

Get küçük bağıl içinde açısını imzalanan perspektifinden, (+/-) biçiminde olması yönünde acze :

  • en fazla 180 derece | PI radyan
  • - saat yönünün tersine işaretlenmişse
  • + saat yönünde ise imzalandı

Dereceler

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

Radyandan

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

gerekçe

Modulo'dan kaçan bir çözüm ararken, bunu çözdükten sonra bu konuya rastladım; Şimdiye kadar hiçbiri bulamadım . @ Jacob-phillips bu yorumu istedi: Bu çözüm, perspektif işareti korumak içindir . Sadece en kısa imzasız açıya ihtiyacınız varsa daha ucuz çözümler vardır.


0

Bu eski bir soru ama aynı davaya girdim - açısal bir fark bulmalıydım, tercihen dallar ve ağır matematik olmadan. Ben de bununla bitirdim:

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

Sınırlama, 'b' nin 'a' ile karşılaştırıldığında 'N' rotasyonundan daha fazlasına sahip olmaması gerektiğidir. Bunu sağlayamıyorsanız ve ekstra işlemlere izin veriyorsanız, bunu ilk satır olarak kullanın:

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

Bu yazının 13. yorumundan fikir edindim : http://blog.lexique-du-net.com/index.php?post/Calculate-the-real-difference-between-two-angles-keeping-the- işaret


-1

sanırım söyleyebilirim

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

Tabii ki, açı göz önüne alındığında derece cinsinden ölçülür.


1
Bunun sorudaki sorunu çözdüğüne inanmıyorum. % 345 360 == 345 ve abs (345-30) hala 315'tir.
Gregory Avery-Weir

@Gregory: tamam! Hata için özür dilerim. Cevabı düzenliyorum, bu yeniyi kontrol edin :)
Vishnu

1
Bu arada, angle1 = angle1% 360; angle2 = angle2% 360; var mesafe = Math.abs (açı1-açı2); var distance ile aynıdır = Math.abs (angle1-angle2)% 360 - daha yavaş.
Martin Sojka
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.