Elektroda.pl
Elektroda.pl
X

Search our partners

Find the latest content on electronic components. Datasheets.com
Elektroda.pl
Please add exception to AdBlock for elektroda.pl.
If you watch the ads, you support portal and users.

[AVR][C] - czy to wielki kłopot zamieniać double na text?

mirekk36 08 Jun 2008 02:03 5076 13
Computer Controls
  • #1
    mirekk36
    Level 42  
    Witam,

    pytanie jak w temacie - czy w C jest to wielki kłopot (tak mi się to teraz jawi) aby zamieniać liczby zmiennoprzecinkowe na tekst w jakikolwiek sposób???? (zapewne niedługo i w drugą stronę będę potrzebował konwersję)

    doczytałem już o sposobie z funkcją sprintf ale to masakra (akurat testy robię na ATtiny2313 i implementacja tej funkcji zżera całą pamięć procka więc nawet nie mam jak jej sprawdzić do końca :(

    próbowałem z fabs no i część przed kropką już mam ale bez znaku :( a to co po kropce myślałem zassać za pomocą modf - ale na dzisiaj próba użycia tej funkcji na różne sposoby zawsze zwraca mi 0 , na pewno coś źle robię

    albo w ogóle zabieram się od tyłka strony do całego tematu - a może jest jakiś krótki i prosty sposób ???? (mam nadzieję, że nie trzeba pisać zaraz swojej funkcji do tego - robiąc wszystko od początku itp ??)

    proszę o jakieś info

    Dodano po 34 [minuty]:

    doczytałem jeszcze o dtostrf - ale to też zarzyna na śmierć pamięć programu ATtiny2313. Czy przy takich małych prockach lepiej nie mieć ochoty na tego typu konwersje ?

    Dodano po 11 [minuty]:

    no tak - "wziąłem w rękę Bascom'a" i spróbowałem taki kawałek kodu:

    Code:
    dim a as double
    
    a = 23.7

    LCD a


    no i też masakra bo Bascom też "napaprał" 115% kodu dla biednego ATtiny2313 .... czyli też mu rurę zatkał

    .... więc chyba marne szanse, że w C się to uda (jednak te obliczenia dla typu double troszkę muszą zajmować)

    chyba, że ktoś ma jeszcze jakieś pomysły ?
  • Computer Controls
  • #2
    ktrot
    Level 19  
    spróbuj jakoś tak:
    Code:

    int n; double x;
    char st1[5],st2[4];

    n=x; x -=n;
    itoa(n,st1);
    x *=10000;   //o ile potrzebujesz 4 miejsca po przecinku
    n=x;
    itoa(n,st2);
    // sklej jakoś st1 i st2 umieszczając przecinek odpowiednio;

    może to mnożenie nie będzie zbyt kosztowne
    Z zapisem inżynierskim (2.36e-3) też powinieneś sobie poradzić
  • Computer Controls
  • #3
    adamusx
    Level 27  
    Znalazlem w jakimś wątku na elektrodzie taki kod do zmiany float'a na stringa, który na pewno zajmuje mniej niż sprintf czy dtostrf:

    Code:
    void ftos(float nn)
    
    {
      unsigned char t1, t2, tt1, tt2, tt3, buf[6], l, i;
      l=0; i=0;
      t1 = (unsigned char)nn;
      t2 = 100*(nn-t1);
      tt1 = (t1 / 100) % 10;
      if(tt1)
      {
         buf[l] = 0x30+tt1;
         l++;
      }
      tt2 = (t1 / 10) % 10;
      if((tt2)|(tt1))
      {
         buf[l] = 0x30+tt2;
         l++;
      }
      tt3 = t1 % 10;
      buf[l] = 0x30+tt3;
      ++;
      if(t2 >0)
      {
          buf[l++] = '.';
          tt1 = (t2 / 10);
          buf[l] = 0x30+tt1;
          l++;
          tt2 = t2 % 10;
          if(tt2 > 0)
          {
             buf[l] = 0x30+tt2;
             l++;
           }
      }
      for (i = 0; i < l; i++) lcd_data(buf[i]);

    }


    Wystarczy przerobić funkcje by zamiast lcd_data(buf[i]); podawała wskaźnik do jakiegoś buufora z tekstem.
  • #4
    mirekk36
    Level 42  
    ktrot -> pomysł genialny i ładnie działa nie zżerając pamięci ale .... ale tylko gdy zmienną x zainicjuję jakąś wartością np:

    Code:
    x = 23.7;


    bo jeśli nie jest zainicjowana to nie wiem dlaczego ale nagle kompilator powoduje, że znowu kod wynikowy ma 192% :(
    gdyby nie to, to ze znakiem tylko trzeba by było powalczyć bo pojawia się po przecinku ale z tym to już bym sobie może poradził.


    adamusx -> taaak czytałem ten post bo tu w tym kodzi co pokazałeś wkradł się jakiś mały błąd przy może kopiowaniu - jest mniej więcej w środku linijka

    Code:
    ++;


    a powinno być:

    Code:
    l++;


    niestety pomimo to - też jakieś mam mega dziwne zachowanie kompilatora bo od razu po kompilacji daje 214% kodu jak dla ATtiny2313

    Dodano po 9 [minuty]:

    ktrot -> zobacz przygotowałem sobie na szybko taką funkcję:


    na wejściu trzeba do niej podać:
    liczbę double x
    wskaźnik do bufora na łańcuch *str
    liczbę miejsc przed przecinkiem c1
    liczbę miejsc po przecinku c2


    Code:
    char * double2str(double [b]x[/b], char [b]*str[/b], uint8_t [b]c1[/b], uint8_t [b]c2[/b])
    
    {
       char *rt = str;
       int n;
       char st1[c1+1],st2[c2+1];
       
       char *t;
       x = 53.7;  <-------------------- tu muszę inicjalizować żeby działało generując mini ilość kodu

       

       n=x; x -=n;
       itoa(n, st1, 10);
       x *= 10;     <-------- tu chciałem dać 10*c2 ale też od razu kod puchnie
       n=x;
       itoa(n, st2, 10);

       
       t = st1;
       while(*t)
       {
          *str++ = *t++;      
       }

       *str++ = '.';

       t = st2;
       while(*t)
       {
          *str++ = *t++;      
       }
       
       *str = 0;

       return rt;

    }


    można jakoś nakazać temu kompilatorowi żeby nie generował "głupot" gdy ma nie zainicjalizowane zmienne???
  • Helpful post
    #5
    Freddie Chopin
    MCUs specialist
    mirekk36 wrote:

    char st1[c1+1],st2[c2+1];

    to nie zadziala. tablice nalezy inicjowac rozmiarem znanym podczas kompilacji. masz wiec dwa wyjscia:
    1. albo rozmiar na sztywno
    2. albo szybki kurs uzycia funkcji malloc i obslugi dynamicznego przydzialu pamieci (obstawiam, ze jak procek nie ma pozadnej ilosci RAMu, to nawet nie ma co probowac)

    Quote:

    x = 53.7; <-------------------- tu muszę inicjalizować żeby działało generując mini ilość kodu

    dziala, poniewaz optymalizator przelicza ci wszystko podczas procesu kompilacji i ta funkcja na 99% tak naprawde nic nie robi. podejrzyj deassemblacje.

    Quote:

    można jakoś nakazać temu kompilatorowi żeby nie generował "głupot" gdy ma nie zainicjalizowane zmienne???

    nie generuje glupot, tylko poprawny kod [; popatrz na to w ten sposob - operujesz naprawde skomplikowanymi operacjami matematycznymi (mnozenie, dzielenie, reszta z dzielenia, czesc calkowita) na 64-bitowych liczbach zmiennoprzecinkowych na procesorku, ktory jest 8-bitowy... to go musi zabic [; nie ma sily <:

    mozesz sprobowac alternatywnego podejscia - wglebic sie w budowe samej liczby double, bo nie jest ona taka skomplikowana, i sprobowac wykonac funkcje ktora bedzie ja przeliczala - bez zadnych itoa, rzutowania (w tym niejawnego, ktorego tu masz pelno, np char = float) itp. rzeczy kompilatora, bo one zawsze zajmuja duzo <:

    EDIT: zapomnialem dodac, ze liczb double w 99,666% przypadkow mozna uniknac. pozostale 0.334% przypadkow mozna obsluzyc floatem albo wziasc wiekszy procesor <:

    EDIT2: odpowiadajac na pytanie z tytulu watku - problem to wielki, dokladnie o 56bitow przerastajacy procesory 8bitowe <:

    0x41 0x56 0x45!!
  • #6
    mirekk36
    Level 42  
    Freddie Chopin wrote:

    EDIT: zapomnialem dodac, ze liczb double w 99,666% przypadkow mozna uniknac. pozostale 0.334% przypadkow mozna obsluzyc floatem albo wziasc wiekszy procesor <:


    no ok po tym wszystkim zgadzam się, że nie ma sensu taka zabawa na prockach ATtiny ... Trza brać takie gdzie pamięci jest sporo więcej - co najmniej ATmega8

    ... a chodziło mi o wyświetlanie chociażby jakichś temperatur później z dokładnością do 1 miejsca po przecinku czy może napięć z przetworników AC z dokładnością do 2 miejsc itp. Czasami jest to potrzebne i wtedy się tego nie da uniknąć - a wyniki trzeba jakoś zaprezentować czasem choćby na LCD

    pozdr
  • #7
    Freddie Chopin
    MCUs specialist
    no dobra, ale do 1 czy 2 miejsc po przecinku wystarczaja integery.

    doubla to sobie mozna uzywac jak chcesz miec ze 40 miejsc po przecinku <:

    przyklad:
    Code:

    #define SIN_PI_Q 0.7071067811865474617150084668537601828575134277343750
                                                    /* sin(PI/4), (double) */
    #define PI 3.1415926535897931159979634685441851615905761718750 /* double */


    0x41 0x56 0x45!!
  • #8
    mirekk36
    Level 42  
    Freddie Chopin wrote:
    no dobra, ale do 1 czy 2 miejsc po przecinku wystarczaja integery.


    ponieważ w żadnym innym języku nie musiałem się nigdy martwić o to - to niestety teraz mam zaćmienie i nie wiem jak tu w C poradzić sobie z tym co powiedziałeś powyżej
  • Helpful post
    #9
    Freddie Chopin
    MCUs specialist
    mnozysz to co tam liczysz przez 100 i juz masz 2 miejsca po przecinku. albo (w specyficznych zastosowaniach) masz dwie zmienne - jedna od czesci calkowitej, druga od czesci ulamkowej.

    podaj konkretny przyklad problemu, to wtedy mozna wymyslic konkretne rozwiazanie go, bo przeciez zaden termometr cyfrowy nie zwraca ci wartosci typu double.

    na przyklad napiecie z przetwornika ADC. zalozmy 10 bitow i 5V daje full scale.

    1 bit ~= 0.0049V

    do liczenia takiego czegos narzucaja sie zmienne ulamkowe (float/double). ale po co, skoro mozesz liczyc w mV (4,9mV ~= 5mV) lub jesli to dokladnosc zbyt mala, to nawet w uV (4883uV).

    masz wynik np 325. mozesz wiec po prostu dokonac mnozenia liczb calkowitych typu long (co da ci 1586975) (najwieksza wartosc przy uV to 5M, long jest do ~4G) z czym twoj procek sobie poradzi raczej bez wiekszych problemow, a przed przedstawieniem wyniku obliczyc wynik dzielenia tego co otrzymasz przez 1M (czesc calkowita = 1) i reszte z dzielenia przez owy 1M (najlepiej poprzez pomnozenie tej 1 przez owy 1M i odjecie tego od poprzedniego wyniku), co da czesc ulamkowa (586975). poniewaz zmnozenie liczb typu long moze byc rowniez troche bolesne, to zauwaz, ze cala sprawe mozna rozwiazac poprzez 3 zmienne typu int. jedna jest od calosci (moze byc char), druga jest od mV (int), trzecia od uV (int), itd.

    dokonujac mnozenia przez owe 325 mnozysz osobno czesci odpowiedzialne za kazda 'trojke' czyli mnozysz 325 x 4mV, x 882uV, x 812nV, ... a nastepnie idac od dolu (od najmniejszych) sprawdzasz czy jest przepelnienie (powyzej 1000 - jesli tak, to dodajesz owe tysiace (czyli wynik dzielenia przez 1000) do wartosci wyzszej (nV -> uV, uV -> mV, mV -> V). i juz masz supermega dokladny wynik przy uzyciu operacji:
    - mnozenie int x int = long
    - dzielenie long / int = int

    z ktorymi AtTiny powinno dac rade.

    uniwersalnej metody nie ma, to wszystko zalezy od konkretnego przypadku. dla temperatury np. zalozmy, ze masz czujnik typu PT100 i 1 bitowi z przetwornika odpowiada np 0.125stopnia. no ale co stoi na przeszkodzie, zeby 1bitowi odpowiadalo 125 'tysiecznychstopnia' i juz masz arytmetyke liczb calkowitych -> na koncu po prostu rozbijasz sobie to na czesc calkowita ( /1000) i czesc ulamkowa ( %1000).

    pozatym - jak juz koniecznie chcesz zmiennoprzecinkowe liczby, to przeciez jest tez float, ktory jest o polowe mniejszy, ale wystarczajaco dokladny do wiekszosci zastosowan.

    0x41 0x56 0x45!!
  • #10
    markosik20
    Level 33  
    Jak już wcześniej koledzy wspomnieli liczby zmiennoprzecinkowe to "zabójstwo" dla 8bitowców :wink:.

    Tutaj masz najprostszy przykładowy kod odczytu temp. z DSxxxx

    Code:


    //potrzebne zmienne
    //globalna
    unsigned char Temperatura[8];
    //lokalna
    unsigned char ok,i,znak;
    unsigned char temp[2];
    signed char temp_jednosc;
    unsigned char temp_ulamek=0;
     

    temp[0]=read_byte_1WIRE();
    temp[1]=read_byte_1WIRE();

    if(temp[1] & 0x80) znak = *"-";
    else znak = *"+";
          
       if(temp[0] & 0x01) temp_ulamek+=6;
       if(temp[0] & 0x02) temp_ulamek+=12;
       if(temp[0] & 0x04) temp_ulamek+=25;
       if(temp[0] & 0x08) temp_ulamek+=50;
       temp_jednosc = temp[1] & 0x0F;
       temp_jednosc<<=4;
       i = temp[0] & 0xF0; i>>=4;
       temp_jednosc |= i;
       if(znak==*"-")temp_jednosc*=-1;

        Temperatura[0] = znak;
        Temperatura [1] = temp_jednosc;
        Temperatura [2] = temp_ulamek;   

                 //zamiana na ASCII
        Temperatura[5] = temp_jednosc%10 +0x30;temp_jednosc/=10;
        Temperatura[4] = temp_jednosc%10 +0x30;
        Temperatura[3] = temp_jednosc/10 + 0x30;
        Temperatura[6] = *",";
        Temperatura[7] = temp_ulamek%10 + 0x30;

  • #11
    adamusx
    Level 27  
    markosik20 wrote:
    Jak już wcześniej koledzy wspomnieli liczby zmiennoprzecinkowe to "zabójstwo" dla 8bitowców :wink:.


    NIe do końca się z tym zgodze. Jeśli mamy do dyspozycji ATmege16 lub większą to obsługa zmiennego przecinka nie jest jakims zabójstwem dla procesora (no chyba, że zależy nam na dużej prędkości działania programu).

    W urządzeniach ktore programujemy w pracy na 8 bitowacach często wykorzystuje się zmienny przecinek do przeliczeń wartości z czujników (urządzenia pomiarowe U , I , czujniki tensometryczne itp) , bo szybciej w ten sposó to zrealizować, niż bawić się w jakieś klejenie integerów itp.

    Oczywiście mówie o wykorzystaniu zmiennej typu float, bo dla double to jeszcze nie znalazłem zastosowania w aplikacjach 8bitowców.
  • #12
    ktrot
    Level 19  
    Kilka sprostowań:

    1. W kompilatorach C na AVR typ float jest tożsamy z double i jest 32 bitowy. Moze to się zmieni w przyszłych implementacjach.
    2. Mantysa w 32 bitowej reprezentacji liczby jest 23 bitowa a w 64 bitowej jest 52 bitowa. Jako, ze na jedną cyfrę dziesiętną jest potrzebne ok 3.32 lcyfr binarnych (dokładnie log(2)(10) <-podstawa 2 ) to ilość cyfr znaczących wynosi 23/3.32=7 dla 32 bitów oraz 52/3.32=16 cyfr dla reprezentacji 64 bitowej. Uwaga: jest to liczba wszystkich cyfr a nie po przecinku.
    3. Z binarnej reprezentacji liczby nie da się łatwo wyciągnąć cyfr dziesiętnych. O ile dla części całkowitej jest to jeszcze proste i dlatego rzutowanie float-> int zajmuje niewiele zasobów to część ułamkowa jest zapisana w postaci b*(1/2)+b*(1/4)+b*(1/8)+... gdzie b=0 lub 1

    Zgadzam się z kolegą wyżej, ze o ile to tylko mozliwe należy operacje zmiennoprzecinkowe zamienić na całkowitoliczbowe. Jeżeli to niemożliwe - bo np trudno sobie wyobrazić obliczenia trygonometryczne czy logarytmiczne bez formatu zmiennoprzecinkowego to cóż - nie robić tego na tiny.
  • Helpful post
    #13
    markosik20
    Level 33  
    No może przesadziłem z tym "zabójstwem" ale ograniczam float'y do niezbędnego minium. Kiedyś musiałem coś takiego wykobinować żeby rodzielić częśc całkowitą i ułamkową (akurat dana z czujnika tensomatrycznego).

    Kompilator w tym przypadku to Keil ale widzę że GCC też ma te funkcje które tu zostały użyte.

    Rozdzielana liczba znajduje się w dana.f.

    Code:


    union {
       float f;
       unsigned long l;
             }dana;

    float dana_ulamkowa, dana_calkowita;


    if(dana.f<0){znak=*"-";dana.f*=(-1);}
    else {znak=*" ";}

    if(dana.f){
    //rozdzielenie części ułamkowej i całkowitej
    dana_ulamkowa=modf(dana.f,&dana_calkowita);
    dana.f=pow(10,typ);//obliczenie doklności
    dana_ulamkowa*=dana.f;//przesuniecie przecnika
    //zaokrąglenie danej ułamkowej
    if(modf(dana_ulamkowa,&dana.f)>0.5){dana_ulamkowa=ceil(dana_ulamkowa);}
    else {dana_ulamkowa=floor(dana_ulamkowa);}
    dana.l=dana_ulamkowa; 

    }

  • #14
    mirekk36
    Level 42  
    dziękuję szczególnie koledze Freddy za łopatologiczne wyjaśnienie działań przy okazji z przetwornikiem AC oraz koledze markosik20 za przedstawiane przykłady

    jednak narazie dam sobie spokój z teoretycznymi ćwiczeniami nad liczbami zmiennoprzecinkowymi na małych prockach. Bo narazie te kombinowania są trochę kłopotliwe. Zabieram się narazie za inne rzeczy - może niedługo odezwę się z kolejnym pytaniem

    pozdrawiam i dziękuję wszystkim za cenne uwagi