Skocz do zawartości

[C] typy danych liczbowych


ciscoc

Pomocna odpowiedź

Nie bity ujemne, a liczby ujemne 😉

Generalnie jak masz zmienną signed (czyli ze znakiem) to jej najwyższy bit (MSB - most significant bit) reprezentuje liczbę minus coś.

Jak masz zmienną bez znaku to najwyższy bit reprezentuje liczbę dodatnią.

Np dla 8 bitowych zmiennych posczzególne bity (od najmłodszego do najstarszego) reprezentują

dla unsigned char:

bit->liczba

1=1

2=2

3=4

4=8

5=16

6=32

7=64

8=128

a dla signed char:

1=1

2=2

3=4

4=8

5=16

6=32

7=64

8=-128

Dlatego unsigned char reprezentuje liczby od 0 do 255, a signed char od -128 (10000000b) do 127 (01111111b)

Link do komentarza
Share on other sites

Dlatego unsigned char reprezentuje liczby od 0 do 255, a signed char od -128 (10000000b) do 127 (01111111b)

Nie znam C i chyba na szczęście w tym przypadku, bo jak zapisać -127 jako signed char, skoro najstarszy bit przechowuje jednocześnie informację o znaku jak i o wartości liczby. Albo źle to opisałeś albo ten zapis jest po prostu idiotyczny.

Link do komentarza
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

koval_blazej, tak to może być zapisane -1, -127 to byłoby 11111111b.

EDIT:

A jednak widzę, że nie mam racji, ale to wina zasugerowania się zapisem:

Dlatego unsigned char reprezentuje liczby od 0 do 255, a signed char od -128 (10000000b) do 127 (01111111b)

Bo powinien on wyglądać tak, że jest to liczba -128 i zakres od 0 do 127, a nie liczby od -128 do 127.

Już doczytałem i wiem, że liczby ujemne zapisuje się jako negację liczby dodatniej plus 1. Czyli będzie tak jak napisał koval_blazej, -127 to będzie -128+1 czyli właśnie 10000001b a -1 to będzie -128 plus 127 czyli 11111111b.

Jak dobrze, że jest Bascom 😃

Link do komentarza
Share on other sites

To o czym mówicie to kod U2. Aktualnie jest to standard kodowania liczb calkowitych w procesorach. Tutaj jest fajny konwerter liczb '10' na binarne w kodzie U2. Może pomoże to autorowi tematu zrozumieć różnice;)

Link do komentarza
Share on other sites

To, czy zadeklarowaliśmy zmienną ze znakiem czy bez przesądza jedynie o sposobie interpretacji danej kombinacji bitów przez arytmometr procesora a ściślej przez instrukcje badania warunków. Kod uzupełnieniowy do 2 (warto o nim poczytać), jakim posługują się dzisiaj chyba wszystkie procesory ma to do siebie, że ten sam układ cyfrowy może prawidłowo dodawać i odejmować liczby nic nie wiedząc o ich "znakowości". Wynik będzie zawsze prawidłowy pod warunkiem, że nie przekroczymy zakresów ale to chyba oczywiste. Tak więc mając zmienną typu unsigned char (czyli jeden bajt bez znaku) i robiąc takie podstawienie:

a = 200;

wpisujemy pewną kombinację bitów (11001000) do pamięci. Chcąc coś do niej dodać możemy napisać:

a += 10;

w wyniku czego arytmometr procesora wykona rozkaz zwykłego dodawania:

11001000 + 00001010 = 11010010

wcale nie wiedząc jaki jest typ tych liczb. Wynik jest oczywiście prawidłowy: 205 dziesiętnie.

Gdyby natomiast nasza zmienna była typu signed char, po pierwsze nie moglibyśmy wprost podstawić do niej liczby 200 ale to już jest cecha kompilatora. Ten wiedząc, że na 1 bajcie mozna ze znakiem zakodować tylko liczby z zakresu -128..+127 będzie marudził na takie jawne złamanie zasad:

a = 200

i będzie to błąd kompilacji.

Możemy natomiast bezkarnie wpisać tam taką liczbę (co było z kolei poprzednio zabronione):

a = -56;

Jeśli ktoś teraz zamieni to sobie na obraz bitowy wg zasad kodu U2 (tak go w skrócie nazywam) to zobaczy, że kombinacja bitów jest dokładnie taka sama jak poprzednio liczby 200:

11001000

I jeśli teraz powtórzymy operację:

a += 5

to procesor wykona dokładnie takie samo dodawanie:

11001000 + 00001010 = 11010010

w wyniku czego dostaniemy dokładnie taką samą kombinację bitów w pamięci wyniku ale teraz, ponieważ interpretujemy ją jako liczbę ze znakiem to rozumiemy ją jako -51.

Całym "sprytem" kodu U2 jest sposób konwersji liczb dodatnich na ujemne i odwrotnie. Aby jedną zamienić na drugą (to jest symetryczne i działa w obie strony tak samo) wystarczy odwrócić stany wszystkich bitów na przeciwne i dodać 1. Przykład:

+1 czyli 00000001 po zamianie na -1 daje 11111111

+100 czyli 01100100 po zamianie na -100 daje 10011100

+127 czyli 01111111 po zamianie na -127 daje 10000001

ale już +128 czyli 10000000 po zamianie na -128 daje nam 10000000 czyli zgodnie z tą interpretacją jest to znów +128 - tu już zakres się skończył..

Jak łatwo zauważyć najstarszy bit jest ustawiony gdy liczba jest ujemna ale wcale nie "waży" on -128.

Taki chwyt jak w dodawaniu i odejmowaniu kodu U2 nie działa jednak podczas operacji mnożenia i dzielenia. Małe procesory wyposażone są najczęściej w układy mnożące tylko liczby dodatnie więc mnożenie liczb ze znakiem musi być zastąpione sekwencją policzenia modułów z obu argumentów, pomnożenia ich jako liczb dodatnich a potem ew. zmianą znaku wyniku jeśli znaki argumentów były różne - to trwa troszkę dłużej (jakieś 2 razy?) niż mnożenie liczb bez znaku i warto o tym pamiętać.

No i teraz badanie warunków - procesor ma inne instrukcje do badania większości/mniejszości liczb ze znakiem i bez. Dlatego też kompilator umieszcza inne intstrukcje gdy w if-ie porównujemy liczby bez znaku i ze znakiem. Porównywanie sprowadza się jak wiadomo do odjęcia jednej liczby od drugiej i sprawdzeniu znaku wyniku ale tutaj już symetrii nie ma. Proponuję sobie to prześledzić na kilku przykładach na papierze 🙂

BTW:

Pisząc w C mamy jeszcze jedną "warstwę" pośrednią - kompilator, który próbuje nas chronić przed trywialnymi błędami. Dlatego też nie pozwoli nam założyć pętli for korzystającej np. ze zmiennej bajtowej ze znakiem w ten sposób:

for(n=0; n<200; n++)

ponieważ "wie", że ta zmienna takiej wartości nigdy nie osiągnie. Gdyby zmienna była bez znaku, linia kodu skompilowałaby się prawidłowo. Oczywiście jest to trochę sztuczne, bo przecież instrukcja dodająca 1 w każdym obrocie pętli w końcu dostałaby w wyniku kombinację 11001000 czyli 200 ale tym samym zostałaby złamana idea C rozróżniania zakresów liczb ze znakiem i bez.

EDIT: Kling, pisaliśmy razem.. 🙂

Link do komentarza
Share on other sites

Nie mam racji, ale to wina zasugerowania się zapisem:
Dlatego unsigned char reprezentuje liczby od 0 do 255, a signed char od -128 (10000000b) do 127 (01111111b)

Bo powinien on wyglądać tak, że jest to liczba -128 i zakres od 0 do 127, a nie liczby od -128 do 127.

Tak jak podałem poszczególne bity oznaczają poszczególne liczby, które potem się sumują.

Jak masz 10000001b to masz = -128 + 1 = -127.

Jak masz 11000000b to masz = -128 + 64 = -64.

Jak masz 11111111b to masz = -128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = -1

Wszystko jest proste 🙂

Link do komentarza
Share on other sites

marek1707 przytoczył najważniejszą kwestię, która pomaga w zrozumieniu "jak to wszystko działa".

Zmienne typu signed czy unsigned nie różnią się absolutnie niczym dla procesora, w pamięci są zapisane identycznie. To jedynie informacja dla kompilatora jakich instrukcji skoku użyć po porównaniu (które jest zwyczajnym odjęciem od siebie 2 liczb).

Nie pamiętam nazw mnemoników dla Atmeg, ale dość podobnie jest dla x86, gdzie dla porównań bez znaku mamy (upraszczając) dwa skoki: JA, JB (Jump if Above, Jump if Below) a dla porównań ze znakiem mamy: JG, JL (Jump if Greater, Jump if Less). Instrukcje te badają inne bity rejestru stanu, który jest ustawiany przez większość innych instrukcji, w tym instrukcję porównania (jak wspomniano, identycznej z instrukcją odejmowania gubiącej jedynie wynik operacji).

Dlatego właśnie nie przepadam za Bascomami etc. Programista nie wie co się dzieje "pod kapeluszem", często nie rozumiejąc kodu oraz sposobu, w jaki procesor go wykonuje. Przy miganiu LEDem to może nie ma znaczenia, ale jeśli trzeba napisać uber wydajny kod, to znaczenie rozumienia kodu jest spore.

Fakt, że teraz za nas wiele robi kompilator, ale wtedy liczymy na to, że ktoś przewidział daną sytuację.

Dla prostego przykładu: badanie parzystości liczby. 98% programistów napisze

if (a % 2) {nieparzysta;} else {parzysta;}

a ja przywykłem do pisania

if (a & 1) {nieparzysta;} else {parzysta;}

Zakładając, że kompilator nie zoptymalizuje kodu i zostawi nam rzeczywiście dzielenie to zamiast jednego taktu procesora na operacji bitowej spędzamy kilka(naście) na dzieleniu by otrzymać dokładnie ten sam efekt...

Link do komentarza
Share on other sites

Marooned, tylko trzeba pamiętać, że nie zawsze warto poświęcać przejrzystość kodu na rzecz jego wydajności. W Twoich przykładach, w pierwszym od razu widać, że badana jest parzystość, a w drugim trzeba się już chwilę zastanowić.

Nieraz faktycznie można zastosować różne fajne sztuczki, które przyspieszą działanie programu, ale jeśli nie są to fragmenty wykonywane miliony razy, to IMHO lepiej jest zostać przy formie bardziej czytelnej.

Link do komentarza
Share on other sites

Niby tak, ale to zależy też od tego kto czyta kod. Dla niektórych oba warianty są jasne, a inny będzie dumał "co to za procencik".

Zdarzało mi się pisać taki kod, że współtwórca projektu robił wielkie oczy. Wtedy oczywiście stosowny komentarz przy kodzie wyjaśniał elegancko sprawę.

No ale chyba zbyt mocno odbiegłem od meritum, posypuję głowę popiołem.

Link do komentarza
Share on other sites

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • 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.