Subject FixedMath
Email pOiSOn
Irc irc.ada.net.tr #scene.tr #manowar #atheist
ICQ 6795916
Last Update 1 Ocak 2001 [ off bea.. amma zaman geçti..]
Note fpu sucks..

ne ki bu fixed..?

fixed math normal float işlemleri ufak bi trick ile integer sayılarla yapma işidir.. neden denilirse matematik işlemci ile yapılan işlemler integer ile yapılanlardan çok fazla cpu zamanı alir.. eger float sayılarda belli bir sınırınız var ise yani belli bir sayının altına veya üzerıne çıkmayacaksanız fixed math kullanmak işlemleri çok büyük oranda hızlandıracaktır. verilen kodları copy/paste için uğraşmayın en altta gerekli dosyalar var..

nası oluyo?..

bir kaç yolu var en çok kullanılan yöntem 32bitlik bir sayıyı 16'şar bitlik integer ve noktadan sonraki sayi için kullanmak.. eğer daha yüksek sayılar gerekli ise iki tane 32bitlik sayı kullanabilirsiniz.. kendinize göre yöntemlerde geliştirebilirsiniz tabiki.. örneğin 24.8 (25 bit + 8 bit gibi vs..

başlayalım..

önce fixed'i kullanmak için bir sayı tipi yaratalim..

typedef long Fixed32;

artık Fixed32 tipinde bir sayı tipimiz var.. bizim kullanacağımız 16.16'lik fixed olduğuna göre normal sayılardan fixed tipe çevrim için gerekli makroları düzenleyelim..

#define INT_TO_FIXED(x) ((x) << 16)
#define DOUBLE_TO_FIXED(x) ((long)(x * 65536.0 + 0.5))
#define FIXED_TO_INT(x) ((x) >> 16)
#define FIXED_TO_DOUBLE(x) (((double)(x)) / 65536.0)
#define ROUND_FIXED_TO_INT(x) (((x) + 0x8000) >> 16)

#define DTF DOUBLE_TO_FIXED
#define FTD FIXED_TO_DOUBLE
#define ITF INT_TO_FIXED
#define FTI FIXED_TO_INT
#define RTI ROUND_FIXED_TO_INT

#define ONE INT_TO_FIXED(1)
#define FIXED_PI 205887L
#define FIXED_2PI 411775L
#define FIXED_E 178144L
#define FIXED_ROOT2 74804L
#define FIXED_ROOT3 113512L
#define FIXED_GOLDEN 106039L

kodları incelediğinizde olayın büyük bir kısmını kapmış olacaksınız.. örneğin int tipinden fixed'e çevrim için sayı 16 bit sola kaydırılıyor böylece integer sayi'yi high 16 bitlere getirmiş oluyoruz, noktadan sonraki sayi ise 0 olacaktır yani float yazımında n.0 sayısını elde etmiş olduk..

aynı şekilde fixed sayıdan int'e çevrim için sayıyı 16bit sağa kaydırmamız yeterli.. double sayılar için neden 65536 ile çarpıp 0.5 ekleniyor derseniz 14.7 sayısı için işlemi yapın nedeni anlaşılacaktır..

ROUNT_TO_FIXED die bi makro var bu makro ile 14.7 sayısını 15 olarak integer'a çevirmeniz için kullanılabilir..

makro devamlarında uzun uzun makro adları yerine kısaltmaları eklenmiş.. böylece INT_TO_FIXED yerine ITF kullanabilirsiniz..

biraz alta da gerekebilecek sabit sayılar var.. lazım olur belki :)..

sayılar tamamda 4 işlemi nası yapçaz ?

dont panic! :).. 4 işlemin ikisi yani toplama ve çıkartma normal yolla yani integer'larla nasi yapıyorsaniz aynı şekilde yapabilirsiniz.. sadece bölme ve çarpma işlemleri icin iki tane makro (compiler watcom değilse asm fonks olarak yazmanız gerekecek..) kullanmanız gerekli..

Fixed32 FixedMul(Fixed32 num1, Fixed32 num2);
Fixed32 FixedDiv(Fixed32 numer, Fixed32 denom);

#pragma aux FixedMul = \
"imul edx" \
"add eax, 8000h" \
"adc edx, 0" \
"shrd eax, edx, 16" \
parm caller [eax] [edx] \
value [eax] \
modify [eax edx];

#pragma aux FixedDiv = \
"xor eax, eax" \
"shrd eax, edx, 16" \
"sar edx, 16" \
"idiv ebx" \
parm caller [edx] [ebx] \
value [eax] \
modify [eax ebx edx];

eger compiler olarak watcom kullanmıyorsanız (visual c gibi) makro tanımlarını nasıl fonks. olarak yazmanız gerektiği konusunda biraz bilgi vereyim.. boylece kolayca fonks. çevirebilirsiniz..
#pragma bir compiler direktifi yani makro ile aynı şey..
parm caller satırları fonks. tanımında yazan sırada parametrelerin hangi register'lara yerleştirileceğini belirliyor..
value satırları fonk. dönüş değerinin hangi register'da olduğunu belirliyor..
modify satırlari ile compiler'a işlemleri yaparken hangi register'ları değiştirdiğinizi söylüyorsunuz..
tüm olay bu kadar bunları bildikten sonra sanırım kolayca fonks. şekline çevirebiliriniz..(bu arada watcom iyi bir derleyicidir.. :))

diğer işler??!

diğer işler için hazırlanmış bi kaç makro daha var.. sırasıyla kök alma, 1^n , ve karekök alma.. karekök almak için iki tane makro var biri düşük rezülasyonda diğeri yüksek.. yüksek olan tabiki biraz daha cpu time alıyor..

Fixed32 FixedSquare(Fixed32 n);
Fixed32 OneOver(Fixed32 n);
Fixed32 FixedSqrtLP(Fixed32 n); // Low Precision (8.8)
Fixed32 FixedSqrtHP(Fixed32 n); // High Precision (8.16)
// This is faster than using FixedMul for squares.
#pragma aux FixedSquare = \
"imul eax" \
"add eax, 8000h" \
"adc edx, 0" \
"shrd eax, edx, 16" \
parm caller [eax] \
value [eax] \
modify [eax edx];

// This is faster than using FixedDiv.
#pragma aux OneOver = \
"xor eax, eax" \
"mov edx, 1" \
"idiv ebx" \
parm caller [ebx] \
value [eax] \
modify [eax ebx edx];

#pragma aux FixedSqrtLP = \
" xor eax, eax" \
" mov ebx, 40000000h" \
"sqrtLP1: mov edx, ecx" \
" sub edx, ebx" \
" jl sqrtLP2" \
" sub edx, eax" \
" jl sqrtLP2" \
" mov ecx,edx" \
" shr eax, 1" \
" or eax, ebx" \
" shr ebx, 2" \
" jnz sqrtLP1" \
" shl eax, 8" \
" jmp sqrtLP3" \
"sqrtLP2: shr eax, 1" \
" shr ebx, 2" \
" jnz sqrtLP1" \
" shl eax, 8" \
"sqrtLP3: nop" \
parm caller [ecx] \
value [eax] \
modify [eax ebx ecx edx];

#pragma aux FixedSqrtHP = \
" xor eax, eax" \
" mov ebx, 40000000h" \
"sqrtHP1: mov edx, ecx" \
" sub edx, ebx" \
" jb sqrtHP2" \
" sub edx, eax" \
" jb sqrtHP2" \
" mov ecx,edx" \
" shr eax, 1" \
" or eax, ebx" \
" shr ebx, 2" \
" jnz sqrtHP1" \
" jz sqrtHP5" \
"sqrtHP2: shr eax, 1" \
" shr ebx, 2" \
" jnz sqrtHP1" \
"sqrtHP5: mov ebx, 00004000h" \
" shl eax, 16" \
" shl ecx, 16" \
"sqrtHP3: mov edx, ecx" \
" sub edx, ebx" \
" jb sqrtHP4" \
" sub edx, eax" \
" jb sqrtHP4" \
" mov ecx, edx" \
" shr eax, 1" \
" or eax, ebx" \
" shr ebx, 2" \
" jnz sqrtHP3" \
" jmp sqrtHP6" \
"sqrtHP4: shr eax, 1" \
" shr ebx, 2" \
" jnz sqrtHP3" \
"sqrtHP6: nop" \
parm caller [ecx] \
value [eax] \
modify [eax ebx ecx edx];

açılar???!

açılarda da fixed math kullanabilirsiniz..bunun için kendi açı tipimizi tanımlıyoruz..

typedef unsigned short Iangle; // Integer angle (0..1023)

bu açı tipi bildiğimizden biraz farklı normalde 360' lık bir açı alanı kullanırız.. fakat açıyı bulmak için bu sefer matematik işlemleri yerine bir lookup dizisi kullanıcaz.. bu halde de 360' lik bir lookup bazı işlemler için biraz kaba gelebilir bunun yerine 1024' lük bir sistem kullanıcaz.. buna göre 0' = 0 , 360' = 1024 oranında bir lookup kullanılıyor.. bir çok işte 30' gibi spesifik açılarla uraşmadığımızdan (yani açıyı sadece arttırıp 360'ı bulunca sıfırlamaktan başka bişey yapmıyoruz..) böylesi daha iyi üstelik.. açı 1024'ü geçtiğinde (sayı tipinden dolayı) kendiğilinden tekrar 0 olacaktır..

gerekli lookup tanımlamaları..

#define MAX_TRIG 1024

extern Fixed32 CosTab[MAX_TRIG];
extern Fixed32 SinTab[MAX_TRIG];

#include "sintab.hpp"
#include "costab.hpp"

sintab.hpp ve costab.hpp dosyalarını sadece bir lookup dizisi içerdiğinden buraya eklemedim, ekteki zip içerisinde hem bu dosyaları, hemde bu dosyaları yaratan ufak bi utility'i bulcaksınız.. source kod olduğu için gerektiğinde kendinize göre değiştirebilirsiniz..
ayrıca gerektiğinde (lookupdan direkt getirmek yerine) veya tanjant gibi açılara ihtiyaç duyduğunuzda aşağıdaki fonks. var..

void CosSin(Iangle theta, Fixed32 *Cos, Fixed32 *Sin)
{
theta &= (MAX_TRIG - 1);

*Sin = SinTab[theta];
*Cos = CosTab[theta];
}

Fixed32 Tan(Iangle theta)
{
// This shifting stuff is for better accuracy.
theta &= (MAX_TRIG - 1);
return (FixedDiv(SinTab[theta] << 16, CosTab[theta]) >> 16);
}

gördüğünüz gibi gerekli kodlar fazlasıyla optimize edilmiş halde ve çok hızlı çalışıyorlar..
ve burda da size gerekecek tüm source kod dosyaları..

iyi şans..