Je hebt vast wel eens gehoord van binaire getallen. Ze zijn namelijk van uiterst belang voor de werking van een computer. Maar weet je ook hoe dit talstelsel werkt? En waarom maakt de computer juist gebruik van dit systeem?

In dit leeslab leren we vanalles over bits en bytes. Wat we hier leren kunnen we natuurlijk ook in het echt toepassen! Hierom hebben we een vervolgproject voor je klaargezet: ‘Bits en bytes met een ledmatrix’. Deze kun je dus volgen wanneer je klaar bent!

  • Notitie: In dit artikel maken we, voor binaire getallen, gebruik van het veelgebruikte voorvoegsel ‘0b’ om verwarring te voorkomen (1 wordt dus 0b1).

Hoe werkt het binaire systeem?

Binaire getallen zijn vrij vergelijkbaar met het systeem wat we dagelijks gebruiken, echter is er één groot verschil. Het decimaal talstelsel bevat tien cijfers, deze ken je, dit zijn 0, 1, 2, 3, enzovoorts. Het binaire stelsel kent er daarentegen maar twee, 0 en 1.

Dit betekend echter niet dat we in het binaire systeem maar tot 1 kunnen tellen. Net als in het decimale stelsel kunnen we de losse cijfers combineren om grotere getallen te maken. 10 is bijvoorbeeld een samenstelling van 1 en 0, het staat voor het getal wat na 9 komt (9+1=10). Dit werkt precies hetzelfde in het binaire systeem, na 0b1 komt bijvoorbeeld 0b10 (0b1+0b1=0b10), het getal 0b10 is dus het eerst volgende getal na 0b1.

We kunnen dus, door telkens meer cijfers toe te voegen, ook in het binaire systeem oneindig ver doortellen. Met vier tekens komen we bijvoorbeeld al tot 15:

0 0b0000
1 0b0001
2 0b0010
3 0b0011
4 0b0100
5 0b0101
6 0b0110
7 0b0111
8 0b1000
9 0b1001
10 0b1010
11 0b1011
12 0b1100
13 0b1101
14 0b1110
15 0b1111

We zien in deze tabel echter iets vreemds, de getallen bestaan elk uit vier cijfers, bij sommige getallen zijn nullen aan de linkerkant bijgevoegd. Dit doen we nooit wanneer we werken met decimale getallen, 005 staat namelijk niet. Maar in het binaire stelsel kan dit ontzettend handig zijn. Dit heeft te maken met de werking van de computer:

Bits

Computers maken namelijk gebruik van bits, een bit is als het ware een plekje voor een binair cijfer, en kan dus een 1 of een 0 bevatten. We zullen dus voor elk binaire cijfer wat we mogelijk willen gebruiken een bit moeten reserveren, het aantal bits staat dan dus ook vast. Wanneer we bijvoorbeeld 8 bits reserveren kunnen we een getal tussen de 0 en de 255 opslaan, dit getal zal dus ook nooit groter kunnen worden dan 255.

Bytes en data types

Een computer groepeert bits in groepen van acht. Zo een groep heet een byte. Het groeperen verbetert de efficiëntie van de computer. De computer hoeft namelijk niet meer voor elke bit bij te houden waar deze zit, dit doet de computer per byte.

Vanwege deze groepering zal een programmeur er vrijwel altijd voor kiezen om een veelvoud van acht bits voor een getal te reserveren. Dit wordt tevens ook voor je gedaan door de programmeertaal. We maken hiervoor gebruik van data types. Door aan te geven welke data type je wilt gebruiken, kun je precies bepalen hoeveel bytes de computer zal moeten reserveren. Een byte of een char bevat bijvoorbeeld maar één byte, een short heeft er twee, een int bestaat over het algemeen uit vier bytes en een long is het grootst, deze heeft meestal acht bytes.

Een data type geeft echter meer aan dan alleen hoeveel bytes de computer moet reserveren. Er wordt ook aangegeven hoe de data opgeslagen en geïnterpreteerd moet worden. Een gewone short, int en long gebruikt bijvoorbeeld de eerste bit om aan te geven of het getal positief of negatief is, deze bit behoort dus niet tot het getal. Als programmeur kunnen we deze dingen vaak echter wel beïnvloeden. We kunnen deze short, int of long bijvoorbeeld ‘unsigned’ maken om dit te voorkomen, echter zullen we dan geen gebruik kunnen maken van negatieve getallen.

In C++ kan de lengte van de verschillende data types af en toe afwijken. Wanneer dit problemen op kan leveren kun je ook gebruikmaken van de data types int64_t en uint64_t. Het getal (in dit geval 64) geeft aan hoeveel bits er gereserveerd moeten worden (je hebt de keuze uit 8, 16, 32 en 64), de u staat voor unsigned. Voor AVR-boards zoals de Arduino zijn bytes en shorts de standaard lengte, maar een int is net als een short 2 bytes lang, en een long is 4 bytes lang.

Waarom binaire getallen?

Je vraagt je misschien af: Waarom maken computers gebruik van het binaire systeem? Dit komt vanwege het belangrijkste component van de computer: de transistor, en zijn voorganger: de vacuümbuis. Een transistor kan zich maar in twee toestanden bevinden: open of dicht. Door het stroomdraad, wat aan de transistor aangesloten is, kan vervolgens wel of geen spanning lopen. Deze toestanden komen goed overeen met de 1 en de 0 van het binaire systeem.

En er is juist voor de transistor gekozen, omdat deze verschillende bewerkingen, zoals het optellen van getallen, makkelijk maakt.

Bitwise operators

Naast optellen zijn er ook veel andere bewerkingen mogelijk, sommige zijn nog veel eenvoudiger, dit zijn de bitwise operators. En juist vanwege de eenvoud zijn dit ook de belangrijkste bewerkingen die een computer kan maken. Elke bitwise operator wordt in vrijwel elk programma gebruikt.

NOT (~)

De NOT-bewerking is de aller simpelste binaire bewerking wat bestaat. Je voert één getal in, en er komt één getal uit. De uitkomst is precies het tegenovergestelde van wat je invoert:


Om een NOT-bewerking aan te geven maken we, in computercode, gebruik van het ~-teken. De uitkomst van ~0b10100110 is dus 0b01011001. De bewerking wordt, zoals je kan zien, voor elke positie afzonderlijk uitgevoerd.

AND (&)

Om een AND-bewerking uit te voeren hebben we twee getallen nodig. Alleen wanneer beide getallen één zijn zal er als antwoord een één uitkomen. Anders zal het antwoord nul zijn:


Voor een AND-bewerking wordt het &-symbool gebruikt. De bewerking 0b01010101 & 0b00001111 zal als resultaat dus het getal 0b00000101 geven.

Ook is de AND-bewerking vanuit een ander oogpunt te bekijken. Wanneer we spreken van ‘bitmasking’ zien we het tweede getal wat we invoeren als een soort masker. In het masker vullen we een één in op de posities waarvan we de waarde willen behouden. Op de posities waarvan we de waarde weg willen gooien vullen we, in het masker, een nul in. Op de posities van de weggegooide getallen zal zich in het antwoord altijd een nul bevinden (kijk maar naar de tabel hierboven).

Wanneer we dus bijvoorbeeld 0b01010101 invoeren met het masker 0b00001111, zien we dat de eerste vier cijfers gemaskerd worden. Het resultaat van deze bewerking zal dus 0b00000101 zijn (0b01010101 & 0b00001111). De gemaskerde cijfers zijn naar een nul verandert, terwijl de niet-gemaskerde cijfers onaangepast zijn.

OR ( | )

De OR-bewerking lijkt erg op de AND-bewerking. Het verschil is echter dat maar één van de twee ingevoerde getallen een één hoeft te zijn om als uitkomst één te krijgen:


We gebruiken voor de OR-bewerking het |-teken. Dit betekent dus dat de bewerking 0b01010101 | 0b00001111 het getal 0b01011111 als resultaat zal krijgen.

Net als de AND-bewerking kunnen we ook de OR-bewerking gebruiken om te bitmasken. Echter gebruiken we in het masker een één om aan te geven dat een cijfer gemaskerd moet worden. In het eindresultaat zal het cijfer op deze positie altijd op een één uitkomen. Wanneer we dus bijvoorbeeld het getal 0b01010101 invoeren en het masker 0b11110000 gebruiken zal het resultaat 0b11110101 zijn. We zien dus dat de gemaskerde cijfers veranderen naar een één, de andere getallen blijven onveranderd.

XOR (^)

XOR betekent OR, maar niet AND. Wanneer de bits van elkaar verschillen zal de uitkomst één zijn, anders komt er nul uit. Dit is in de volgende tabel duidelijk te zien:


0b01010101 ^ 0b00001111 zal dus uitkomen op 0b01011010.

Ook de XOR-bewerking kunnen we zien vanuit het oogpunt van het bitmasken. De posities waar we in het ‘masker’ een één invullen zullen omkeren (net als een NOT-bewerking). 0b01010101 met het ‘masker’ 0b00001111 zal als resultaat het getal 0b01011010 krijgen. Echter heet dit proces technisch gezien niet bitmasking, de cijfers van het ingevoerde getal hebben op de positie van het masker namelijk alsnog invloed op het resultaat van de bewerking.

Bitshift(<<, >>)

Bitshifting verschilt vrij sterk van de hierboven genoemde bewerkingen. Het eindresultaat van één losse bit is namelijk, in tegenstelling tot de andere bewerkingen, afhankelijk van zijn buren. Wanneer we bitshiften verplaatsen we namelijk alle bits naar links of naar rechts. Bits die van de rand af vallen worden vergeten, bits die erbij komen zullen een nul zijn. Hieronder zien we dit proces in werking:

We zien dat het teken ‘<<‘ alle bits naar links verplaatst. ‘>>’ verplaatst alle bits naar rechts.

Complexe bewerkingen

Door de hierboven genoemde simpele bewerkingen op handige manieren samen te voegen kunnen computers ook complexere bewerkingen uitvoeren. Op deze manier kunnen we bijvoorbeeld veelgebruikte wiskundige bewerkingen als optellen, aftrekken, vermenigvuldigen en delen uitvoeren.

Computers doen dit, voor de veelgebruikte bewerkingen, echter niet door de simpele bewerkingen achter elkaar uit te voeren. Deze complexe bewerkingen worden in hardware gedaan. Hierdoor zullen ze veel sneller uitgevoerd worden dan wat zou kunnen door de bitwise operators achter elkaar uit te voeren.

In het echt

Wat we hier geleerd hebben kunnen we natuurlijk ook in het echt toepassen! Hiervoor hebben we een vervolgproject voor je klaargezet: ‘Bits en bytes met een ledmatrix’.