- CORDIC, FPU ya da büyük lookup tabloları olmadan
sin, cos, tan gibi trigonometrik fonksiyonları hesaplamak için karmaşık işlemleri toplama ve bit kaydırma ağırlıklı hale getiren bir algoritmadır
- Bu yaklaşım, yüksek performanslı sistemlerden çok gömülü ortamlarda, özellikle düşük performanslı mikrodenetleyiciler ve FPGA'lerde kullanışlıdır; değerini yalnızca hızla değerlendirmek zordur
- Kayan nokta yerine sabit nokta kullanılırsa
int32_t içindeki üst 16 bit tam sayı kısmı, alt 16 bit kesir kısmı olarak ayrılabilir ve yaklaşık -32768.99997 ile 32767.99997 arası temsil edilebilir
- Vektörü hedef açıya doğru giderek küçülen açılarla döndürüp
atan(2**-i) tablosundaki 16 değeri ve başlangıç değeri olarak x=39796 kullanırsanız, her yinelemede çarpma işlemini bit kaydırma ile değiştirebilirsiniz
- Örnek açı
0.9152 için 16 yineleme yapıldığında sin(0.9152) mutlak hatası 0.00000956, cos(0.9152) mutlak hatası ise 0.0000434 seviyesine iner
CORDIC'in uygun olduğu hesaplama ortamı
- CORDIC,
sin, cos, tan gibi trigonometrik fonksiyonları düşük güç tüketimli donanımlarda hesaplamak için kullanılan bir algoritmadır
- FPU, yani kayan nokta işlem birimi olmayan ya da büyük lookup tabloları kullanmanın zor olduğu ortamlarda da çalışır
- Gerçek hesaplama, basit toplama ve bit kaydırma işlemleri etrafında şekillenir
- Vektör matematiği, trigonometri, yakınsama ve bilgisayar bilimi fikirlerini birleştirerek karmaşık fonksiyonları basit işlemlerle yaklaşıklar
- Yüksek performanslı donanımlarda bu tekniğe mutlaka ihtiyaç olmayabilir
- Başlıca kullanım alanı gömülü ortamlardır
- Özellikle düşük performanslı mikrodenetleyiciler ve FPGA'ler için uygundur
- Daha hızlı donanımlar veya çevre birimleri bulunabilir, ancak kullanışlılığın tek ölçütü hız değildir
Kayan noktadan kaçınan sabit nokta gösterimi
sin(x) gibi -1.0 ile 1.0 arasında değer üreten bir fonksiyonun mutlaka kayan nokta ile gösterilmesi gerekmez
- Sabit nokta, ondalık noktanın yerini tamsayı tipi içinde sabitleyerek rasyonel sayıları ifade eder
- Örnekte
int32_t, üst 16 bit tam sayı kısmı ve alt 16 bit kesir kısmı olacak şekilde bölünür
- Bu durumda aralık yaklaşık
-32768.99997 ile 32767.99997 olur
- Ondalık noktanın yeri değiştikçe tam sayı aralığı ile kesir hassasiyeti arasında takas yapılabilir
- Değerin kendisi yine
int32_t olarak kalır; ek anlamı bit düzenine programcı yükler
Sabit noktaya dönüştürme ve temel işlemler
- Kesir hassasiyeti 16 bitse
42.01 gibi bir float değeri (1 << 16) ile çarparak sabit nokta değerine dönüştürebilirsiniz
42.01 * (1 << 16) değeri int32_t'ye cast edildiğinde 2753167 olur
- Tekrar float'a çevirmek için
2753167 / (1 << 16) hesaplanır ve yaklaşık 42.0099945 elde edilir
- Hiç kayan nokta kullanmadan
1.5 gibi bir değeri doğrudan encode etmek de mümkündür
- Tam sayı kısmı
1, (1 << 16) ile yukarı taşınır
- Kesir kısmının yarısı,
0x0000 ile 0xffff arasındaki orta değer olan 0x7fff olarak alınabilir
- Bu yöntemin sonucu ondalık olarak
98303 olur
- Aynı ölçekleme katsayısını kullanan değerler arasında toplama ve çıkarma doğrudan çalışır
- Çarpma için iki sabit nokta değeri çarpılır, ardından sonuç ölçekleme katsayısı kadar sağa kaydırılır
- Bölme için bölünen, önce ölçekleme katsayısı kadar sola kaydırılır; ardından bölenle bölünerek ek hassasiyet kazanılır
Vektör döndürerek trigonometrik fonksiyonları yaklaşık hesaplamak
- CORDIC, “co-ordinate rotation digital computer” ifadesinin kısaltmasıdır ve 1950'lerin ortasında geliştirilmiştir
- Temel fikir, birim çember üzerindeki bir vektörü giderek küçülen açılarla döndürerek hedef açıya ulaşıldığında vektör bileşenlerinin sinüs ve kosinüs değerlerine karşılık gelmesini sağlamaktır
- Bu süreç ikili aramaya benzer biçimde ilerler
- Hedef açı yönünde büyük bir açıyla hareket edilir
- Hedefin aşılıp aşılmadığı kontrol edilir
- Ardından saat yönünde veya saat yönünün tersine, daha küçük açılarla döndürme tekrarlanır
- Örneğin
sin(0.7) hesaplanırken başlangıç vektörü (1, 0) ve hedef açı 0.7 radyandır
- İlk olarak
0.7853 radyan, yani 45˚, saat yönünün tersine döndürülür
- Kalan hedef
0.7 - 0.7853 = -0.0853 olur
- Değer negatif olduğu için sonraki adımda
0.3926 radyan, yani 22.5˚, saat yönünde döndürülür
- Sonrasında kalan hedefin işaretine göre
0.1963 radyan gibi daha küçük açılarla yön değiştirilerek döndürme sürer
- 16 yineleme sonunda vektör neredeyse tam hedef açıya hizalanır;
y, sin(a) için, x ise cos(a) için bir yaklaşım olur
Dönüş matrisindeki pahalı işlemleri azaltmak
- Normal bir vektör dönüşü, sinüs ve kosinüs içeren matris çarpımını kullanır
- CORDIC, trigonometrik özdeşliklerden yararlanarak dönüş matrisini
tan(a) merkezli hale getirir
- Başlangıçta
45˚, 22.5˚, 11.25˚ gibi sabit dönüş açıları kullanıldığı için tan(a) değerleri önceden hesaplanmış bir tabloda tutulabilir
- Bu tablo yalnızca 16 adet
uint32_t gerektirir; yani 64 bayttır
- Karşılaştırma için,
-1 ile 1 arasındaki 4096 değeri tutan optimize edilmemiş bir sin(x) tablosu 16KiB gerektirir ve hassasiyeti de daha düşüktür
- Her dönüşte önde gelen
cos(a) terimi her yinelemede oluşur, ancak hepsinin çarpımı bir sabite yakınsar
45˚, 22.5˚, 11.25˚ gibi açılar kullanıldığında bu çarpım yaklaşık 0.6366 olur
- Bu sabiti tüm yinelemelerden sonra bir kez çarpmak yeterlidir
Yalnızca kaydırma ve toplama bırakacak açı seçimi
- Çarpmayı ortadan kaldırmak için
tan(a) sonucunun her zaman 2'nin negatif kuvveti olması sağlanacak açılar seçilir
- Bunun için her yineleme
i=0 ile 15 arasında atan(2**-i) değerlerini içeren 16 girişli bir tablo oluşturulur
- Gerçek dönüş açıları
45˚, 26.565˚, 14.036˚, 7.125˚ gibi olur
- Açılar tam olarak yarıya inmese de bu açıları kullanmak süreç için doğru sonuca yakınsamayı sağlar
tan(a) ile çarpma işlemi, yineleme numarası i kadar bit kaydırmaya dönüşür
cos(a) terimlerinin çarpımı da bu yeni açı seçimine göre yeniden hesaplanır
- Değer yaklaşık
0.60725'tir
- 16 bit sabit noktada bu
39796 değerine karşılık gelir
- Sonda çarpmak yerine başlangıç vektöründeki
x değerini 1 değil 39796 yapmak yeterlidir
Algoritma adımları
- Ön hesaplama aşamasında, her girdisi
atan(2**-i) olan bir tablo oluşturulur ve her değer sabit nokta biçimine çevrilir
- Dönüşüm formülü
atan(2**-i) * (1 << 16) şeklindedir
sin veya cos hesaplanırken giriş açısı da sabit nokta biçimine dönüştürülür
- Örnekte
0.9152, 0.9152 * (1 << 16) = 59978 olur
- Başlangıç durumu şöyledir
x = 39796
y = 0
z = 59978
z, vektörün bir parçası değil, kalan hedef açıyı izleyen değerdir
z işaretine göre dönüş yönü belirlenir
z >= 0 ise saat yönünün tersine döndürülür ve z -= table[i] uygulanır
z < 0 ise saat yönünde döndürülür ve z += table[i] uygulanır
- Her yineleme
x ve y üzerinde yalnızca toplama, çıkarma ve >> i kaydırması kullanır
if z >= 0:
x_next = x - (y >> i)
y_next = y + (x >> i)
z -= table[i]
else:
x_next = x + (y >> i)
y_next = y - (x >> i)
z += table[i]
x = x_next
y = y_next
Örnek yakınsama sonuçları ve kalan konular
0.9152 radyan örneğinde ilk yinelemede z pozitif olduğu için yaklaşık 0.785 radyan kadar saat yönünün tersine döndürülür
- İkinci yinelemede de
z pozitif kaldığından yaklaşık 0.436 radyan saat yönünün tersine döndürülür, ancak hedef aşılır
- Üçüncü yinelemede
z negatif olur ve yaklaşık 0.244 radyan saat yönünde döndürülür
- Dördüncü yinelemede de
z negatif kaldığı için yaklaşık 0.124 radyan saat yönünde döndürülür
- Açı değişimleri küçüldükçe vektör gerçek sonucun çevresinde ileri geri hareket ederek yakınsar
- 16 yineleme sonunda
y, sin(0.9152) için son derece yakın bir yaklaşım verir
- Sinüs için mutlak hata
0.00000956'dır
x için kosinüs mutlak hatası 0.0000434'tür
- Ele alınmayan konular da vardır
- İlgilenilen açı, birim çemberin 1. veya 4. çeyreği dışında olduğunda gereken özel işlemler
- CORDIC türevleriyle hesaplanabilen
tan, atan, asin, acos, sinh, cosh, tanh, sqrt, ln, e^x
- Logaritma ve üstel hesaplama için tasarlanmış ilişkili algoritma BKM
- İlgili konuların Low Byte Productions YouTube channel üzerinde daha ayrıntılı ele alınması planlanıyor
1 yorum
Hacker News yorumları
Yazar bunun çoğunlukla FPGA gibi yerlerde uygulandığını söylemiş olsa da oyun geliştirmede ya da dağıtık fizik simülasyonlarında da kullanılabilir.
Kayan nokta hesaplamalarda platformlar arası determinizm sağlamak zordur; çözümlerden biri kayan noktadan tamamen kaçınıp sabit noktalı bir fizik motoru uygulamaktır.
Trigonometrik fonksiyonları uygulamak için CORDIC gibi bir şey gerekir.
Birkaç yıl önce eğlence olsun diye böyle bir şey yapmaya başlamıştım ama bitiremedim; bir gün tekrar denemek isterim.
https://randomascii.wordpress.com/2013/07/16/floating-point-...
Özetle x87'nin tuhaf yanları vardı; yuvarlama modu ve sıfıra flush etme gibi ayarların tutarlı biçimde eşlenmesi gerekir; eski işlemcilerde FMA yoktur;
mmsqrtpsgibi yaklaşık komutların tutarlı bir belirtimi yoktur ve derleyici ifadeleri yeniden ilişkilendirebilir.Küçük rutinler ya da kendi yazdığınız bir kütüphane söz konusuysa, zahmetli olsa da bunlardan kaçınıldığını garanti etmek mümkündür.
IEEE-754 2008 belirtimi daha net hâle getirdi ve fiilen x87'nin ölümünü varsaydı; 2024'te x87'den kesin biçimde kaçınmak mümkün.
FMA da IEEE-754 2008 belirtiminin bir parçasıdır ve Intel Haswell sonrası dâhil modern işlemcilerde bulunur.
Yine de 8-wide AVX2 ile 4-wide NEON gibi mimari farklar sorun çıkarabilir; ancak assembly ya da intrinsic'ler kullanarak, ya da Compiler Explorer veya objdump ile denetlenen C yazarak çıktıya bakıp “bu tutarlı olur” diye karar verebilirsiniz.
“Aslında IEEE 754 bugünkü kadar popüler bir standart hâline gelmeden önce sabit nokta her zaman kullanılıyordu. 1980 ile yaklaşık 2000 arasında çalışmış bir oyun geliştiricisine sorarsanız ayrıntıları anlatacaktır.”
nphysics'in yeniden yazılmış hâli olan yeni kütüphane Rapier ise bunun yerine IEEE-754 2008'in garantilerine dayanarak platformlar arası determinizm sağlıyor.
Bu yüzden eski platformlarda çalışmıyor, ancak wasm dâhil modern platformlarda deterministik.
Elbette her platformun sunduğu sin, cos gibi transandantal fonksiyon rutinlerine güvenemezsiniz; her yerde aynı şekilde çalışacak biçimde kendiniz uygulamanız gerekir.
Ancak uyumsuz platformlarda çalıştırmazsanız uygulanabilir bir yaklaşım.
https://www.rustsim.org/blog/2020/06/01/this-month-in-rustsi...
https://rapier.rs/docs/user_guides/rust/determinism/
CORDIC, sinüs ve kosinüs hesaplama/üretmenin yanı sıra logaritma, üstel fonksiyon, karekök, vektör büyüklüğü, kutupsal-koordinat ile Kartezyen-koordinat dönüşümü, vektör döndürme gibi çeşitli işlemler için de kullanılabilir.
Yazar da sonuç bölümünde bu olasılığa işaret ediyor.
Mevcut ortonormal matrisler yerine kuaterniyon kullanılırsa CORDIC tabanlı işlemlerin daha verimli, yani daha az hesaplama döngüsü ve bellek kullanarak ve daha düşük hatayla yürütülebileceği hissine kapılıyorum.
https://core.ac.uk/works/8439118
Lisede analiz öncesi dersinde Taylor serilerini öğrenmiştim ve öğretmenim hesap makinelerindeki trigonometrik fonksiyonların gerçekten böyle uygulandığını söylemişti.
Araştırınca aslında CORDIC olduğunu gördüm ve TI Basic'te uygulayarak epey eğlendim.
CORDIC değildi ama algoritmasında benzer yanlar var.
http://files.righto.com/calculator/sinclair_scientific_simul...
Donanım uygulamalarıyla ilgili yazılar:
https://arxiv.org/pdf/2211.04053
https://hal.science/hal-01327460/document
https://archive.ll.mit.edu/HPEC/agendas/proc05/Day_1/Abstrac...
Farklı dönemlerdeki çeşitli donanımlarda, genel yazılım/donanım trigonometrik fonksiyon uygulamalarıyla nasıl karşılaştırıldığını görmek isterim
IoT ve makineler arası iletişim büyüyor; CORDIC uygulaması ve işlem verimliliği düşünüldüğünde kullanımı muhtemelen ciddi biçimde artacak, bu yüzden doğru ve optimize edilmiş uygulamalar için iyi referanslara ihtiyaç var
İstisna olarak Prof. Omondi ve Prof. Deschamps’ın kitapları var
https://www.worldscientific.com/worldscibooks/10.1142/p1054
http://www.arithmetic-circuits.org/guide2fpga/vhdl_codes.htm
sin ve cos vektör döndürmede sık kullanılır
Bu durumda CORDIC’in püf noktası, geleneksel sin/cos/çarpma hesabından kaçınmak ve döndürülecek vektörün kendisini CORDIC’in girdisi olarak vermektir
Böylece CORDIC, sin/cos hesaplamadan ya da karmaşık sayı çarpımı yapmadan döndürülmüş vektörü doğrudan üretir
CORDIC, gecikme süresinin çok kritik olmadığı durumlarda özellikle parlar
Hesabın her adımı pipeline’a alınırsa ciddi bir throughput elde edilebilir; bu da kablosuz sistemlerde dijital mixing için uygundur
2023 itibarıyla bazı modern MCU’lar düşük maliyetli olmasına rağmen FPU’ya sahip
STM32G4 iyi bir örnek; M0 MCU gibi durumlardan farklı olarak sabit nokta kullanmak istemiyorsanız
f32’yi serbestçe kullanabilirsinizBu tür çipler MCU başına yaklaşık 1–2 dolara bulunabiliyor
Ancak G4’te sabit nokta amaçları için bu algoritmayı uygulayan bir donanımsal CORDIC çevre birimi de var
Bunun esas olarak kayan nokta hassasiyet kaybından kaçınmak için mi kullanıldığını merak ediyorum
Register’lar üzerinden programlanıyor ama CORDIC’i doğrudan CPU üzerinde uygulamıyor; IC içindeki özel donanım işliyor
En ucuz STM32G4, STM32G441KBT6 ve yuvarlayınca 4 dolar ediyor https://www.digikey.com/en/products/detail/microchip-technol...
2 doların altında nereden bulunabildiğini merak ediyorum
Digi-Key’de Nuvoton çipi 500 adetlik miktarda kıl payı 2 doların altına iniyor
Hızlı ve 64 bit ara çarpımları işleyebiliyor; bu yüzden bölme ve trigonometrik fonksiyon hassasiyeti çoğu kullanım için yeterli
Gerekirse yazılımla hassasiyet daha da artırılabilir
CORDIC’i geç öğrendim; ondan önce performans ve determinizm için 8 bit/16 bit assembly dünyasında sabit noktayı çok kullanıyordum
Öğrenince şaşırmıştım
Hızlıydı ve işe yarar biçimde kullanmak için gereken matematik bilgisi de yalnızca temel düzeydeydi
Daha önce katkıda bulunduğum epey sevimli bir kod parçası aklıma geliyor
Birim çemberdeki bir yayın oluşturduğu açının açıortay koordinatlarını bulmak gerekiyordu ve iki kolun
(x,y)koordinatları zaten vardıMevcut uygulama,
(x,y)koordinatlarını kutupsal koordinatlara(r,θ)çeviren, hesaplananθ’nın doğru çeyrekte olup olmadığını kontrol eden, sonraθ’yı ikiye bölüp yeniden(x,y)’ye dönüştüren bir trigonometrik fonksiyon yığınıydıSonuçta çok sayıda trigonometrik ve ters fonksiyon çağrısı yapıyordu
Python’da karmaşık sayılar birinci sınıf değerler olarak kullanılabildiği için,
(x1,y1)’denz1,(x2,y2)’denz2adlı iki karmaşık sayı tanımlayıp çarpımın geometrik ortalamasını√(z1*z2)almak yeterliydiYeni kodda ne açık bir trigonometrik fonksiyon vardı ne de açık dönüştürme ve ters dönüştürme
https://fgiesen.wordpress.com/2010/10/21/finish-your-derivat...
“22,75˚ döndürmek, 45˚ döndürüp ardından -22,5˚ döndürmekle aynıdır; bu oldukça açık” denmiş, ama bu durumda 22,5° döndürme olmuyor mu?
Yazıdaki bir hata mı, yoksa ben mi yanlış anladım, merak ediyorum
Meagher’ın octree sistemi, tamsayı çarpma ve bölme olmadan yalnızca tamsayı aritmetiği kullanmasıyla bilinir.
“Boolean işlemleri (birleşim, kesişim, fark), geometrik işlemler (taşıma, ölçekleme, döndürme), N boyutlu çakışma tespiti ve uzayın rastgele bir noktasında gizli yüzey kaldırmayı içeren gösterim için verimli doğrusal zamanlı algoritmalar geliştirilmiştir. Bu algoritmalar kayan nokta işlemleri, tamsayı çarpma ve tamsayı bölme gerektirmez.”
https://doi.org/10.1016/0146-664X(82)90104-6
Bu sayede octree gösterimi için hızlı, özel amaçlı VLSI grafik hızlandırma donanımı yapmak kolaylaştı.
CORDIC’in küçük tablolar kullanan 3. derece interpolasyon ya da diğer polinom interpolasyonlarıyla karşılaştırıldığında nasıl bir performans verdiğini merak ediyorum.
Kaynakları sınırlı synthesizer’ların zaman zaman 3. derece interpolasyon kullandığını öğrenmiştim; muhtemelen CORDIC’in nispeten yeni olduğu bir dönemdi.
Kabaca bakınca CORDIC her yinelemede 1 bit hassasiyet kazandığı için hesaplama açısından daha pahalı, ama alan açısından polinomlardan daha az yer kullanacak gibi görünüyor.
Yine de alan açısından, yazıda
sin(x)için sunulan 4096 girişli arama tablosundan daha ucuza gelebileceğini vurgulamak gerekir.Simetri sayesinde tam çemberin yalnızca 1/4’ü gerekir.
Bayt boyutunda açılar kullanınca otomatik olarak döngüsel olması kullanışlıydı ve 2D oyunlarda döndürme için
2^8oldukça yeterliydi.Ancak akıcı hareket istiyorsanız 3D’de bununla çok fazla ilerleyemezsiniz.