rektim napisał(a):
jest problem z dokładnością przy operacjach na liczbach zmiennoprzecinkowych.
Problemem niekoniecznie jest float. Problem dotyczy raczej ogólnie liczb ułamkowych w pozycyjnych systemów liczbowych. Przykładowo w systemie dziesiętnym też można powiedzieć, że jest problem z dokładnością wyniku dzielenia 1 przez 3, bo ile byśmy cyfr nie użyli (oczywiście nie mówię tu o ułamku zwykłym), będzie on obarczony jakimś błędem, ponieważ jest to ułamek o rozwinięciu dziesiętnym nieskończonym. Za to przykładowo w systemie o podstawie 3 (trójkowym) zapis będzie bardzo prosty, czyli
0.1Podobnie jest w przypadku konwersji pomiędzy systemami liczbowymi (w Twoim przypadku z dziesiętnego na dwójkowy), bo nie każdy ułamek skończony w jednym systemie będzie również skończony w innym systemie. W przypadku kompilatora avr-gcc zarówno
float jak i
double są 32-bitowe, więc tylko taką precyzją dysponujemy, choć to w tym przypadku nie ma większego znaczenia. Bardziej chodzi o to, że liczba
16.23 z Twojego przykładu jest w systemie dwójkowym ułamkiem nieskończonym. Najbliższą reprezentacją liczby
16.23 (przy założeniu 32-bitowej precyzji) będzie liczba
16.229999542236328125, resztą z dzielenia przez 16 będzie
0.229999542236328125, pomnożone przez 1000 to będzie
229.999542236328125. Konwersja do
int (bo takiego typu argumentu oczekuje funkcja
itoa() ) nie zaokrągla wyniku, tylko obcina jego część ułamkową, w efekcie czego otrzymujesz
229W programie również używasz skończonej precyzji, która (przynajmniej w momencie wyświetlania) zwykle nie przekracza 4 do 6 cyfr, przyjmijmy że w Twoim przypadku 4. Wystarczy więc podczas przygotowania do wyświetlenia części ułamkowej zaokrąglić wynik do odpowiedniej ilości cyfr, przykładowo modyfikując Twój sposób obliczeń:
język c
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.
Oczywiście można to zrobić na różne sposoby, pokazałem jak to zrobić sposobem którego użyłeś, żeby łatwiej było Ci zrozumieć sens (funkcja
fabs() została użyta, aby uzyskać prawidłowy wynik dla liczb ujemnych).
EDIT:
Ta metoda ma niestety taką wadę, że nie zadziała prawidłowo dla liczb o części całkowitej równej zero. Należałoby taką sytuację uwzględnić w kodzie i zmienić sposób obliczeń. Wspomniana poniżej (przez kolegę dziobak7) funkcja sprintf() na pewno będzie wygodniejsza w użyciu. Ja tutaj raczej tylko chciałem wytłumaczyć skąd się bierze ta "niedokładność" liczb zmiennoprzecinkowych i że jakimś rozwiązaniem może być zaokrąglanie wartości. Jeśli wydajność kodu i zajętość pamięci nie jest problemem, to na pewno lepiej jest skorzystać z gotowych funkcji z biblioteki standardowej, jak wspomniana funkcja sprintf(). Jeśli zależy nam na wydajności i ograniczenie użytych zasobów, lepiej zrobić tak, jak wspomniałem poniżej, czyli wykonywać obliczenia na liczbach całkowitych. Wprawdzie nie zawsze da się taką metodę zastosować, ale w większości przypadków jest wystarczająca i najbardziej optymalna dla mikrokontrolerów ośmiobitowych bez FPU.
ENDEDIT.
No i tak, jak napisał kolega wyżej, stosowanie arytmetyki zmiennoprzecinkowej w 8-bitowych mikrokontrolerach AVR nie posiadających sprzętowego wsparcia dla operacji na tych liczbach jest niewskazane. Istnieją sposoby użycia zamiast tego obliczeń na liczbach całkowitych (bez utraty precyzji). Mirek na pewno nakręcił o tym (być może nie jeden) poradnik, ale ja niestety nie potrafię podać łącza (nie chce mi się szukać), a opisywanie tutaj tego po raz kolejny mija się z sensem.