MKNM "Synergia"

Liczby zmienno i stałoprzecinkowe

Liczby zmienno i stałoprzecinkowe

Często programując mikrokontrolery zachodzi potrzeba wykonania skomplikowanych operacji matematycznych wymagających użycia liczb niecałkowitych. Najczęściej używa się wtedy zmiennych typu float, które nie gwarantują dużej szybkości obliczeń, co jest szczególnie niepożądane, gdy jakaś część kodu musi się wykonać jak najszybciej np. w przerwaniu. Niektóre mikrokontrolery posiadają FPU (Float Point Unit – jednostka zmiennoprzecinkowa) która przyśpiesza wykonywanie obliczeń, jednak jeśli sprzęt, z którego się korzysta, nie posiada odpowiedniego wsparcia sprzętowego,  z pomocą przychodzą liczby stałoprzecinkowe.

Na początku warto wyjaśnć jak tworzona jest liczba zmiennoprzecinkowa, czyli popularny float.

Rysunek 1. Kolejność bitów tworzących liczbę zmiennoprzecinkową [1]

Kolejność bitów w liczbie zmiennoprzecinkowej jest pokazana na rys.1. Spróbujmy zapisać liczbę 10,25 jako float. Najpierw zapiszemy liczbę 10 (część całkowitą) w postaci binarnej. Będzie to 1010. Teraz czas na liczbę 0.25 (część niecałkowita) – będzie to 01 w zapisie binarnym. Zapisując razem otrzymamy 1010.01 = 1.01001 ·  2^3(notacja naukowa). Mantyse otrzymujemy przez odrzucenie części całkowite otrzymując 01001.  Na pierwszy rzut oka wykładnik jest równy 3. Jednak zgodnie z panującymi zasadami zapisujemy go jako 127+3 = 130 co przedstawiamy w zapisie binarynym jako 10000010. Ponieważ kodujemy liczbę dodatnią bit znaku S = 0. Ostatecznie nasza liczba będzie wyglądała jak na rys.2.

Rysunek 2. Kodowanie liczby 10,25 zmiennoprzecinkowo

 

Liczby stałoprzecinkowe mają określoną ilość bitów wydzieloną na część całkowitą i ułamkową.

Kodowanie liczby 10,25 jako stałoprzecinkowej (np. 16 bitów dla części całkowitej, 16 bitów dla części ułamkowej) będzie wyglądać następująco: najpierw zapiszemy 10  = 1010 w postaci binarnej  , następnie część ułamkową 0,25 = 01. Ostatecznie nasza liczba stałoprzecinkowa będzie wyglądała tak jak na rys.3

Rysunek 3. Kodowanie liczby 10,25 stałoprzecinkowo

 

Teraz czas na testy – przeprowadzone zostały na płytce Freescale FRDM-KL25Z zawierającej procesor z rodziny Cortex M0+, który nie zawiera jednostki zmiennoprzecinkowej (FPU).

Sprawdźmy wydajność dla prostego dodawania liczb – przez czas w którym realizujemy dodawanie na zmiennych typu int dioda jest włączona (BLUE_LED_ON ), gdy dodawanie na float dioda jest wyłączona (BLUE_LED_OFF ).

int ia = 1;
int ib = 2;
int ic = 0;
float fa = 1.0;
float fb = 2.0;
float fc = 0.0;
int main(void)
{
    int x;
    for(;;)
    {
        BLUE_LED_ON;
        for(x=0;x<10000;x++)
        {
           ic = ia + ib;
        }
        BLUE_LED_OFF;
        for(x=0;x<10000;x++)
        {
           fc = fa + fb;
        }
    } 
} 

Na rys. 4 widać, że dodawanie używając zmiennych typu float zajmuje około 6 razy więcej czasu, niż używając zmiennych typu int.

Rysunek 4. Widok wskazań z oscyloskopu dla dodawania liczb stałoprzecinkowych i zmiennoprzecinkowych

Dodając liczby dwie liczby stałoprzecinkowe posiadające przecinki w różnych miejscach (zmienne w kodzie Q2a i Q3b mają przecinki na 2 i 3-cim miejscu, ponieważ zmienne są nazywane wg wzorca Qf, gdzie f to liczba bitów mantysy) jak widać z rys.5 zajmuje to około 5 razy więcej czasu niż zmiennymi float.

#define FADDG(a,b,f1,f2,f3) (FCONV(a,f1,f3)+FCONV(b,f2,f3)) //makro dodające dwie liczby o liczbie bitów mantysy f1 i f2 i zapisujące wynik działania jako liczba mająca f3 bitów mantys
#define FCONV(a,f1,f2) (((f2)>(f1) ? (a)<<((f2)-(f1)) : (a)>>((f1)-(f2))) //makro konwertujące liczbę o f1 bitów mantysy na liczbę o f2 bitach mantysy
int Q2a = 4;
int Q3b = 8;
int Q3c = 0;
float fa = 1.0;
float fb = 1.0;
float fc = 0.0;
int main(void)
{
    int x;
    for(;;)
    {
        BLUE_LED_ON;
        for(x=0;x<10000;x++)
        {
           Q3c = FADDG(Q2a, Q3b, 2, 3, 3);
        }
        BLUE_LED_OFF;
        for(x=0;x<10000;x++)
        {
           fc = fa + fb;
        }
    } 
}
Rysunek 5. Kod programu wykonującego dodawanie liczb stałoprzecinkowych i liczb zmiennoprzecinkowych

Używanie liczb stałoprzecinkowych tylko z pomocą dyrektyw preprocesora może być uciążliwe – łatwiej jest użyć do tego celu bibliotek. Jedną z bibliotek przeznaczonych do działań na liczbach zmiennoprzecinkowych jest MFixedPoint [2], wymaga jednak aby projekt był napisany w języku C++ , biblioteką jezyka C jest fixed-point math library [3].

Jeśli musimy użyć liczb zmiennoprzecinkowych do obliczeń z wykorzystaniem funkcji trygonometrycznych lub pierwiastkowania – z pomocą przychodzi biblioteka libfixmath [4]. Ciekawostką jest to, że biblioteka ta ma stały punkt w którym jest ustawiony przecinek (znajduje się na 16-stym miejscu licząc od prawej strony).