Skocz do zawartości

[C] Nieudana zamiana z systemu binarnego na decymalny


Pomocna odpowiedź

Witam!

Postanowiłem napisać funkcję zamieniającą liczbę podaną w systemie dwójkowym na liczbę zapisaną w systemie dziesiątkowym. Jest to klasyczny algorytm konwersji liczb i gdy kompiluje go w DevC++ to działa, ale gdy wrzucam go do AtMega8 to się sypie. Program testowałem na 6 diodach LED, które mają mrugać w kombinacjach opisanych za pomocą liczby binarnej. Gdy zamieniam tę liczbę na system heksadecymalny (szesnastkowy) lub decymalny (dziesiątkowy) to wszystko działa prawidłowo, ale gdy otrzymuję liczbę dziesiętną z funkcji to kolejność diod jest zupełnie inna. Poniżej funkcja - co jest nie tak?

int na10(long int liczba2)
{
int liczba10=0, mnoznik=1;
while(liczba2>0)
{
	liczba10 += (liczba2 % 10) * mnoznik;
	liczba2 /= 10;
	mnoznik *= 2;
}
return liczba10;
}
Link to post
Share on other sites

Ja polecam używanie typów które wiadomo ile mają:

uint16_t na10(uint32_t liczba2)
{
   uint16_t liczba10=0, mnoznik=1;
   while(liczba2>0)
   {
       liczba10 += (liczba2 % 10) * mnoznik;
       liczba2 /= 10;
       mnoznik *= 2;
   }
   return liczba10;
}
Link to post
Share on other sites

O ile dobrze Cię zrozumiałem, chcesz zrobić konwersję bin→BCD, czyli zamianę przykładowo:

0000 0000 0111 1011 → 0000 0001 0010 0011

Obe liczby są tu 16-bitowe, ale pierwsza jest czysto binarna i ma wartość 123 dziesiętnie, a druga jest zakodowana w BCD i poszczególne tetrady bitów niosą liczby 0, 1, 2 i 3. Czy o to chodzi?

Moim zdaniem padłeś ofiarą częstego błędu powstającego przy pisaniu kodu na szybko: w kilku miejscach używasz tej samej stałej znaczącej to samo. Zamiast zdefiniować sobie nowy symbol lub dostarczać tę wartość do funkcji jako parametr, wstawiłeś liczby bezpośrednio w tekst a przy zmianie przeoczyłeś jedno z wystąpień. Chodzi mi oczywiście o bazę systemu liczenia, czyli teraz liczbę 10. Ja bym zrobił to tak:

#define RADIX_BASE 10
int na10(long int liczba2) 
{ 
   int liczba10=0, mnoznik=1; 
   while(liczba2>0) 
   { 
       liczba10 += (liczba2 % RADIX_BASE) * mnoznik; 
       liczba2 /= RADIX_BASE; 
       mnoznik *= RADIX_BASE; 
   } 
   return liczba10; 
} 

Dodatkowo problemem jest u Ciebie długość zmiennych - na to zwrócił uwagę Chumanista. Jeśli long int ma 32 bity, to jej maksymalna wartość dziesiętna wynosi 2147483647 a to jest 10 cyfr. 10 cyfr zakodowanych w BCD to aż 40 bitów, więc typem funkcji powinno być uint64_t zajmujące 8 bajtów i umożliwiające zmieszczenie 16 cyfr BCD. A co się stanie, jeśli funkcja dostanie liczbę ujemną? Wynik będzie jeszcze bardziej bez sensu: ponieważ typ long int może być ujemny, już pierwszy while uzna, że kończymy i w wyniku dostaniesz zero. Pisząc na małe kontrolery lub robiąc dziwne myki z konwersjami czy rzutowaniami warto używać typów o długościach zaszytych "na twardo": uint_8t, int16_t, uint32_t itd.. Inaczej będziesz skazany na wybory kompilatora: jak długie są int-y w PC a jak długie w AVR - sprawdzałeś to?

Inna sprawa, że operowanie liczbami BCD w formacie spakowanym (2 cyfry w jednym bajcie) może być uciążliwe. Pomyśl nad oddawaniem wyniku raczej do tablicy bajtów, w których umieszczałbyś po jednej cyfrze BCD.

Link to post
Share on other sites

malum, o ile rozumiem próbujesz zamieniać liczby dziesiętne na inne liczby dziesiętne.

Przykładowo: jako wejście dajesz 1010 (tysiąc dziesięć). Na wyjściu chcesz uzyskać 9 (czyli 1010 binarnie).

To jest zły pomysł sam w sobie. Jeśli już to polecałbym zamieniać ciągi napisów na liczby np. "1010" na 9.

Ale skoro już bardzo chcesz przechowywać liczby binarne w zmiennych int, to musisz pamiętać o zakresach. W przypadku zmiennej int, na komputerze 32-bitowym, zakres wynosi 2^31, czyli 2147155967. Możesz więc w Twoim systemie maksymalnie zapisać 1111111111 - czyli 10 bitów.

W przypadku AVR typ int jest "mniejszy", bo tylko 16-bitowy. Więc maksymalna wartość jaką możesz przechowywać to 32767, co w wersji niby-binarnej daje 11111, czyli 5 bitów. Skoro piszesz, że sterujesz 6 diodami, to pewnie trochę brakuje.

Na próbę możesz wysterować 5 diod - powinno działać.

Możesz zmienić typ int na long, albo uint32_t, ale musisz też pamiętać, o stałych, przykładowo 10 kompilator może zrozumieć int, a więc liczbę 16-bitową. Więc obliczając resztę z dzielenia, wyjdą niepoprawne wyniki. Zamiast 10 najlepiej użyć 10uL, co poinformuje kompilator, że jest to 32-bitowa wartość (bez znaku).

Ale jak napisałem - najlepiej napisz program, który zamiast liczb jako parametr będzie wczytywał napisy. Plus będzie chociaż taki, że będziesz mógł dłuższe liczby obsługiwać, a nie maksymalnie 10-bitowe.

PS. "Jest to klasyczny algorytm konwersji liczb" - to nieprawda. Nie jest to klasyczny algorytm. Delikatnie mówiąc jest to koszmar, a nie klasyka.

Link to post
Share on other sites
malum, o ile rozumiem próbujesz zamieniać liczby dziesiętne na inne liczby dziesiętne.

Przykładowo: jako wejście dajesz 1010 (tysiąc dziesięć). Na wyjściu chcesz uzyskać 9 (czyli 1010 binarnie).

To jest zły pomysł sam w sobie. Jeśli już to polecałbym zamieniać ciągi napisów na liczby np. "1010" na 9.

Ale skoro już bardzo chcesz przechowywać liczby binarne w zmiennych int, to musisz pamiętać o zakresach. W przypadku zmiennej int, na komputerze 32-bitowym, zakres wynosi 2^31, czyli 2147155967. Możesz więc w Twoim systemie maksymalnie zapisać 1111111111 - czyli 10 bitów.

W przypadku AVR typ int jest "mniejszy", bo tylko 16-bitowy. Więc maksymalna wartość jaką możesz przechowywać to 32767, co w wersji niby-binarnej daje 11111, czyli 5 bitów. Skoro piszesz, że sterujesz 6 diodami, to pewnie trochę brakuje.

Na próbę możesz wysterować 5 diod - powinno działać.

Możesz zmienić typ int na long...

Dlaczego pomysł jest zły? Teoretycznie podaję liczbę w postaci dziesiętnej, ale gdy obliczam reszty z dzielenia to otrzymuję liczby jednocyfrowe dziesiętne, czyli dokładnie takie jakich oczekuję. Wystarczy przemnożyć je przez kolejne czynniki tworzące rzędy systemu binarnego, czyli 1, 2, 4, 8, 16, 32, ...i otrzymuję wartość dziesiętną danego rzędu. Jest to jeden z klasycznych algorytmów.

Bardziej obrazowo wygląda to tak: 00111111(2) = 0*2^7 + 0*2^6 + 1*2^5 + 1*2^4 + 1*2^3 + 1*2^2 + 1*2^1 + 1*2^0 = 0*128 + 0*64 + 1*32 + 1*16 + 1*8 + 1*4 + 1*2 + 1*1 = 0 + 0 + 32 + 16 + 8 + 4 + 2 + 1 = 63(10)

Można oczywiście pobawić się w przesyłanie znaków i odejmowanie od każdego z nich wartości 48, czyli wartości zera z tablicy ASCII, ale efekt będzie dokładnie taki sam, a obciążenie pamięci większe.

Jeśli chodzi o typ zmiennych to używam nie INT'a a LONG INT'a. Popatrz na zmienną, którą przekazuję do funkcji. Nazywa się LICZBA i jest typu LONG INT. Za pomocą INT'a przechowuję jedynie obliczoną wartość dziesiętną, która nigdy nie będzie większa od 255 więc spokojnie mieści się w typie INT.

Czy istnieje jakiś symulator na PC, który pokazuje wartości zmiennych przy przetwarzaniu programu przez mikrokontroler, bo w standardowych kompilatorach jak DEV czy CODE:BLOCK wszystko działa, ale po wgraniu programu do mikrokontrolera już nie 🙁.

[ Dodano: 03-11-2015, 17:03 ]

Dodatkowo problemem jest u Ciebie długość zmiennych - na to zwrócił uwagę Chumanista. Jeśli long int ma 32 bity, to jej maksymalna wartość dziesiętna wynosi 2147483647 a to jest 10 cyfr...

Chcę uzyskać inny efekt, a mianowicie zamienić liczbę binarną 00111111 (najwyższa wartość, czyli zapalone wszystkie 6 diod) na wartość dziesiętną, czyli 63.

Taka liczba binarna ma zaledwie 8 znaków, więc spokojnie mieści się w long int. Próbowałem nawet pracować na long long int, ale efekt był dokładnie taki sam 🙁

[ Dodano: 03-11-2015, 17:10 ]

Pisząc na małe kontrolery lub robiąc dziwne myki z konwersjami czy rzutowaniami warto używać typów o długościach zaszytych "na twardo": uint_8t, int16_t, uint32_t itd.. Inaczej będziesz skazany na wybory kompilatora: jak długie są int-y w PC a jak długie w AVR - sprawdzałeś to?

Podczas doboru typu zmiennych korzystałem z tabelki z artykułu Kurs AVR-GCC cz.3

Link to post
Share on other sites

Oj to niewiele chłopie zrozumiałeś. To co chcesz uzyskać to tzw. spakowany format BCD - możesz spokojnie o tym gdzieś poczytać. Jeszcze raz popatrz na kod który wstawiłem. W pętli występuje liczba 10. Powinna być użyta trzy razy a u Ciebie jest tylko dwa razy, bo zmienną mnożnik też powinieneś mnożyć przez 10. Widzisz różnicę? To dlatego Twoja konwersja na BCD nie działa a moja tak. Wcale nie dlatego, że inny kompilator czy komputer.

Dywagacje o długościach zmiennych to wątek poboczny i chyba tego nie łapiesz, nieważne, zapomnij.

Link to post
Share on other sites

Z kodu autora oraz

00111111(2) = 0*2^7 + 0*2^6 + 1*2^5 + 1*2^4 + 1*2^3 + 1*2^2 + 1*2^1 + 1*2^0 = 0*128 + 0*64 + 1*32 + 1*16 + 1*8 + 1*4 + 1*2 + 1*1 = 0 + 0 + 32 + 16 + 8 + 4 + 2 + 1 = 63(10)

Sugeruje że on tego nie wie i chodzi mu o zwykłe 111111(10) jako zapis na 111111(2), a nie 00111111 = 111111(8) = 37449(10)

Link to post
Share on other sites

A ja zrozumiałem, że ma 6 czy 8 diodek LED i chce na nich pokazywać liczby dziesiętnie, jak w modnych ostatnio zegarkach "binarnych". Np. funkcja dostaje (ograniczmy się do 8 bitów) wartość dziesiętną 23, naturalnie w postaci kombinacji bitów 00010111 a chce, by na diodkach pokazało się 0010 0011 czyli 2 i 3. Dokładnie to robi pokazana przez maluma funkcja. Ma oczywiście kłopot z zakresem liczb wejściowych, znakami, błędem w mnożeniu itp, ale jej "core" to właśnie konwersja bin na spakowany BCD, czyż nie?

Link to post
Share on other sites

Marku, to jeszcze dziwniejszy program. Na wejściu ma dostawać liczbę dziesiętną, taką która się składa tylko z cyfr 0 i 1. Jako wynik daje wartość, też dziesiętną, liczby która powstałaby, gdyby te same cyfry tworzyły liczbę w binarną... Byłoby to fajne ćwiczenie ze schematu Hornera, ale poza tym raczej dziwny pomysł.

Przykładowo:

(101001)10 => (101001)2 = 0x29

Link to post
Share on other sites

Ależ jestem ograniczony. Wciąż usiłowałem doszukać się zastosowania tego czegoś i teraz rozumiem: mamy wejście znakowe do urządzenia, np. konsola UART gdzie wczytujemy jedynie liczby dziesiętne. Ktoś jednak postanowił, że przy pomocy tego wejścia i istniejących już funkcji konwersji ASCII->bin zakładających podstawę 10, wczytamy liczby binarne. Podaje więc np. ciąg znaków "10001", który - zrozumiany jako liczba dziesiętna, po konwersji przekłada się na binarne, 16-bitowe 10011100010001 w pamięci procesora. To podajemy na wejście rzeczonej funkcji na10() a ona oddaje nam binarne 10001 co było celem. Wow 🙂 Czy Autor może to potwierdzić? Czy rzeczywiście o to chodzi?

Link to post
Share on other sites
Co ciekawe jest to dość standardowe zadanie na OIG.

To nie tylko standardowe zadanie na OIG, ale również jeden z podstawowych algorytmów ... i na pewno działa.

Problem rozwiązany, a zwycięzcą jest Elvis!

Chodzi o to, że liczby z 0 na początku oznaczają dla kompilatora kod ósemkowy. Więc 00111111 to nie o samo co 111111...

Dziękuję bardzo za podpowiedź i rozwiązanie, chociaż teraz mam jeszcze jeden problem 🙁

Pierwotnym problemem było złe rozumienie liczby przez mikrokontroler. Gdy podawałem 00111111 niby w systemie dziesiętnym, to mikrokontroler rozpoznawał ją jako liczbę ósemkową a nie dziesiętną no i zamiana nie wychodziła. Wystarczyło usunąć te dwa zera na początku i wszystko działa.

Drugi problem to sprzeczność z moim założeniem. Chciałem podawać kolejność ledów zawsze z wykorzystaniem całego ciągu 6 lub 8 zer i jedynek. Dla przykładu, jeśli miała się zapalić pierwsza i ostatnia dioda to do funkcji na10 miałem przekazać 00100001, czyli na10(00100001), a gdy miały się zapalić dwie ostatnie, to na10(00000011). Niestety te dwa początkowe zera psują całą koncepcję i wywołania muszą mieć postać na10(100001) oraz na10(11). W ostateczności nie ma tragedii, ale wolałbym jednak podawać całe 0 cyfr. Ma ktoś jakiś pomysł? Na początku myślałem o przekazaniu tablicy znaków string i odwoływaniu się do kolejnych komórek tej tablicy, ale mikrokontroler nie przyjmuje stringa lub brakuje mi jakiejś biblioteki - jakiej?

Link to post
Share on other sites
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.