Bitin nypläys C-kielellä ja vähän Boolen algebraa.

Sulautettuja järjestelmiä tehdessä on usein syytä käsitellä bittejä.

Ajatellaan, että meillä on ongelma. 8-bittisestä sisään tulosta tulee ottaa XOR funktio vierekkäisistä biteistä. 0 ja 1 tulos menee vähiten merkitsevään bittiin, 2 ja 3, 4 ja 5, 6 ja 7 tuloksen neljänteen (bitti numero kolme) bittiin. Lisäksi tuloksen viides bitti (numero 4) on yksi jos sisääntulon kaksi ylintä bittiä ovat ylhäällä ja muuten nolla. Tuloksen kuudes bitti (numero 5) on sisääntulon kuudennen bitin käänteisarvo. Tuloksen kaksi korkeinta bittiä ovat aina yksi ja 0. Kuvassa on esitetty sama graafisesti.

xor.png

Operaattoreja biteille on C-kielessä tarjolla NOT, AND, OR, XOR, siirto oikealle ja vasemmalle.
Lisäksi on olemassa loogiset NOT, AND ja OR.

Tutustutaan ensin operaattoreihin. 1 on tosi tai bitti ylhäällä.

NOT
Muuntaa arvon käänteiseksi ja ottaa vain oikean puoleisen muuttujan tai arvon käyttöönsä.

Arvo 0 1
NOT  1 0 operaation jäleen.

AND
And antaa toden, mikäli molemmat arvot ovat tosia. AND ottaa ympäriltänsä kaksi parametria.

Eka  0 1 0 1
Toka 0 0 1 1
AND  0 0 0 1

OR
OR antaa toden, mikäli toinen tai molemmat arvot on tosia.

Eka  0 1 0 1
Toka 0 0 1 1
OR   0 1 1 1

XOR
eXklusiivinen OR on tosi jos vain toinen on totta.

Eka  0 1 0 1
Toka 0 0 1 1
XOR  0 1 1 0

Loogiset versiot operaattoreista AND && OR || NOT ! ovat käytössä, kun tehdään if-lauseita

if((1+1 == 2)&&(2 + 3 == 5)){
  tehdaan();
}
if(( 1+ 1 == 2) || ( 2 + 3 != 5)){
  taas_tehdaan();
}
if( ! (1 + 1 ==3)){
  vielakin_tehdaan();
}

Ensimmäisessä if lauseessa ensin tutkitaan onko 1 +1 todellakin kaksi ja mikäli se oli totta, tutkitaan onko 2 + 3 = 5. Mikäli molemmat pitävät paikkaansa, suoritetaan tehdaan() funktio.

Toisessa lausessa on OR operaatio ja riittää, että tutkitaan ensimmäinen tapaus. Mikäli se oli totta ei tarvitse tutkia enempää.

Viimeinen if-lause oli NOT eli käänteisarvosta.
On hyvä muistaa mainitut suoritusehdot. AND ehto pysähtyy ensimmäiseen nollaan ja OR ehto pysähtyy ensimmäiseen ykköseen. Mikäli teet jotain if-lauseen ehdossa, voi ohjelmointivirheen paikka olla lähellä.

bitti versiot ovatkin meidän ongelmassamme tärkeimmät. AND &, OR |, NOT ~, XOR ^ lisäksi tarvitaan << ja >> siirto-operaattoreita. Mainittakoon, että C++ nuo operaattorit on ylikuormitettu virtojen kanssa. Silloin Hello worldin pihvi on cout << “Hello World.”;

Siirto-operaatori toimii seuraavasti.

unsigned char muuttuja = 3; //Bittikuvio 0000 0011 arvo 3
muuttuja = muuttuja << 2;//Bittikuvio 0000 1100 arvo 12
muuttuja = muuttuja >> 1;//Bittikuvio 0000 0110 arvo 6
muuttuja = 3 << 2; //Bittikuvio 0000 1100 arvo 12
muuttuja = 15 << 5 //Bittikuvio 1110 0000 arvo 224 
//Ensimmäinen ykkönen tippui pois ylivuodon takia.

Eli luku siirtyy vasemmalle tai oikealle ja perään laitetaan nollia.

Kuten esimerkistä huomaa on bittien siirto-operaattori voimakas työkalu. Sen käytössä kannattaa aina käyttää sulkeita, koska >> ja << sitovat vähemmän kuin esimerkiksi &

muuttuja & 1 << N;//ilman sulkuja tarkoittaa vastaa sulkujen kanssa kirjoitettuna:
(muuttuja & 1) << N;//joka taas on aivan jotain muuta kuin haluttu
muuttuja & ( 1 << N);

Usein käytetyt merkintä (1 << N) tarkoittaa seuraavia lukuja. ensin desimaali ja sitten heksadesimaali.

(1 << 0) 1   0x01
(1 << 1) 2   0x02
(1 << 2) 4   0x04
(1 << 3) 8   0x08
(1 << 4) 16  0x10
(1 << 5) 32  0x20
(1 << 6) 64  0x40
(1 << 7) 128 0x80

Bitin tilan tutkiminen
Bitin tilan voi tutkia & operaatiolla.

/* Muuttujan bitit 76543210 ja meitä kiinnostaa onko bitti 3 päällä. */
if(muuttuja & (1 << 3)){
  oli_paalla();
}
/* kiinnostaa onko joku biteista 357 päällä. huomioi sulutus.*/
if(muuttuja & ((1 << 3)|(1 << 5)|(1 << 7))){
  oli_joku():
}
/* kiinnostaa onko kaikki biteista 357 päällä. huomioi looginen NOT.*/
if( ! (muuttuja & ((1 << 3)|(1 << 5)|(1 << 7)) ^((1 << 3)|(1 << 5)|(1 << 7) )){
  oli_kaikki():
}

viimeisessä on ensin otettu muuttuja ja tehty sillä kiinnostavien bittein , eli bittimaskin, kanssa & operaatio. Tällöin on muut bitit menneet nollille. Tätä sanotaan bittien maskaukseksi.

Bitti päälle
OR operaatio ykkösen ja kumman tahansa kanssa tuottaa tulokseksi ykkösen. Joten otetaan muuttuja ja tehdään | operaatio halutun bitin kanssa ja tallennetaan se sinne.

/*Bitit 0 ja 1 päälle kolmella eri tavalla*/
muuttuja = muuttuja | (1 << 0)|(1 << 1);
muuttuja |= (1 << 0)|(1 << 1);
muuttuja |= 3;

Bitti pois
Bitin pois päältä ottaminen on hieman monimutkaisempi.

muuttuja = muuttuja & ~(1<<2);
muuttuja &= ~(1 << 2);
/* Tapahtuu seuraavaa:
  luodaan bittikuvio 0000 0100
  tämän jälkeen käännetään se ja saadaan 1111 1011
  & operaatio 0 kanssa on aina nolla. 1 kanssa tulokseksi tulee alkupeäinen.
  Eli muuttujasta nollautuu haluttu bitti.*/

Bitin tilan vaihto

Bitin tilan voi vaihtaa XOR operaattorilla. tai myös NOT operaattorilla, mutta se on työläämpi.

muuttuja = muuttuja ^ (1 << 2);
muuttuja ^= (1 << 2);
muuttuja = ~(muuttuja &(1 << 2))|(muuttuja & ~(1 << 2));

Viimeisimmän käyttö ei ole suositeltavaa kuin joissain todella vajaamielisissä DSP prosessoreissa, joissa XOR on uskomattoman hidas. Onneksi niitä ei ole tehty pitkään aikaan.

Ongelman ratkaisu
Seuraavassa koodissa on kommentoitu ongelman ratkaisu. Se ei ole tarkoituksella helppo. Sen on tarkoitus näyttää, miten siirtoa ja bitti operaatiota voi käyttää monipuolisesti. Lisäksi siellä on pari painajaismaista näytettä, joiden hallinta helpottaa huomattavasti muiden kirjoittaman koodin luentaa.

Esimerkin voi ajaa PC:llä Linux ympäristössä kopioi koodi editoriin ja laita haluamasi syöte. Tallenna esimerkiksi tiedostoon nyplays.c

$ gcc -o nyplays nyplays.c 
$ ./nyplays 
 Sisaan: 01101011
XOR apu: 00110101
XOR
XOR apu: 01011110
0 tulos: 00000000
1 tulos: 00000010
2 tulos: 00000110
3 tulos: 00001110
AND
  tulos: 00001110
NOT
  tulos: 00001110
RATKAISU
 Sisaan: 01101011
  tulos: 10001110
$

Vaihtaaksesi tutkittavaa arvoa, muuta sissan muuttujan arvoa ja käännä ohjelma uudelleen. Arvoiksi kelpaa 0 … 255

/*
   Esimerkkejä bittioperaatioista C-kielellä
*/
/* printf funktio konsoliin tulostusta varten.*/
#include <stdio.h>

void bittien_printtaus(char foo);
int main(){
  /* Luodaan muuttuja nimeltään muuttuja, joka on etumerkitön 8-bittinen 
     muuttuja. Luotaessa asetetaan se tutkittavaan arvoon.
     C standardi sanoo charin olevan aina 8-bittinen. Muuta ovat 
     suurempia TAI yhtäsuuria. Tutustu mitä sizeof operaattori tekee. 
  */
  unsigned char sisaan = 107; //Laita tähän tutkittava luku.
  unsigned char tulos = 0;
  unsigned char apu_xorraukseen;
  unsigned char i;
  printf(" Sisaan: ");
  bittien_printtaus(sisaan);
  printf("\n");
  
  /* Siirretaan sisääntuloa oikealle ja tallennetaan se apu_xorraukseen.
     Nyt bitit 0 ja 1 ovat päällekkäin.
  */
  apu_xorraukseen = sisaan >> 1;
  printf("XOR apu: ");
  bittien_printtaus(apu_xorraukseen);
  printf("\n");
  /* Tehdään XOR operaatio.
     Vaihtoehtoinen syntaksi:
     apu_xorraukseen ^= sisaan;
   */
  apu_xorraukseen = apu_xorraukseen ^ sisaan;
  /* Tulos on nyt biteissä 0 2 4 6
   */
  printf("XOR\n");
  printf("XOR apu: ");
  bittien_printtaus(apu_xorraukseen);
  printf("\n");
  for (i = 0;i < 4; i++){
    /* Jos apu_xorraukseen muuttjan kiinnostava bitti on ylhäällä. 
       Asetetaan se.
       tulos:     xxxx xxxx
       kierros 0: 0000 0001
       kierros 2: 0000 0100
       Mikäli jokaisella kierroksella on if lause on ollut 
       vaihtoehtoinen syntaksi:
       tulos |= (1 << i);
     */
    if(apu_xorraukseen & (1 << (2*i))){
      tulos = tulos | (1 << i);
    }else{
      /*Nollataan tuloksen bitti.
	Vaihtoehtoinen syntaksi:
	tulos &= ~(1 << i);
      */
      tulos = tulos & ~(1 << i);
    }
    printf("%d tulos: ", i);
    bittien_printtaus(tulos);
    printf("\n");
  }
  /*AND operaatio.
    Ensin valitaan bitit 6 ja 7. Bitti 7 siirretään oikealle yhden verran,
    jotta se on kohdakkain & operaatiota varten. Sen jälkeen tulos siirretään
    oikealle kohdalle bitti nelosta varten ja otetaan tulos muuttjan kanssa |
    joka tallennetaan sitten tulos muuttujaan.

    Sulkeilla on tärkeä merkitys tämän lauseen toiminnan kannalta.
   */
  printf("AND\n");
  tulos = (((sisaan & (1 << 6)) & ((sisaan & (1 << 7)) >> 1)) >> 2)|tulos;
  printf("  tulos: ");
  bittien_printtaus(tulos);
  printf("\n");

  /*NOT operaatio. Käytetään tässä XOR operaatiota taas miten bitti kääntyy.
    Bitin tilan voi vaihtaa muuttuja ^= (1 << N); Missä N on bittinumero.
  */
  printf("NOT\n");
  tulos = ((1 << 5) & sisaan ^ (1 << 5))|tulos;
  printf("  tulos: ");
  bittien_printtaus(tulos);
  printf("\n");
  /* Ja nuo aina olevat ykköset ja nollat.
   */
  tulos = tulos | (1 << 7);
  tulos = tulos & ~(1 <<6);

  printf("RATKAISU\n");
  printf(" Sisaan: ");
  bittien_printtaus(sisaan);
  printf("\n");
  printf("  tulos: ");
  bittien_printtaus(tulos);
  printf("\n");
  return 0;
}

/* Kotitehtävä: Mitä tässä tapahtuu ja miksi?
 */
void bittien_printtaus(char foo){
  char bit;
  for(bit = 7; bit >= 0; bit--){
    if((1 << bit) & foo){
      printf("1");
    }
    else{
      printf("0");
    }
  }
}

Lopuksi vielä sananen sulautetuista järjestelmistä. Usein on niin, että rekistereille ja biteille luodaan vakioita. Esimerkiksi Atmelin mikrokontrollereissa voi komentaa TCCR0A |= (1 << WGM00) ja näitä vakioita kannattaa käyttää. Mikäli joskus kontrolleri vaihtuu, niin ohjelmistoon ei välttämättä tarvitse tehdä muutoksia.

Toivon, että tästä oppaasta on jollekulle iloa ja oppimistakin tapahtuu. Saa myös kommentoida. Ja tiedän esimerkin olevan karvainen, mikäli siihen ei perhedy.

Sattuu vanhallekin mokia tehdessä. Yksi puuttuva taikasana unsigned aiheutti mielenkiintoisia ominaisuuksia maailmaan.

Aloin oikein miettiin miten tämä toimii ja kirjoitin seuraavan pätkän, joka tulostaa -1:n heksana etumerkillisenä ja etumerkittömänä. Sitten sitä yritetään siirtää yhden verran oikealle >> operaattorilla ja jakamalla kahdella.

char n = -1;
unsigned char u = (unsigned char) n;
printf("-1 char: %hhx %hhd unsigned: %hhx %hhu\n", n, n, u, u);
printf("Shift char: %hhx %hhd unsigned: %hhx %hhu\n", (n >> 1), (n >> 1), (u >> 1), (u >> 1));
printf("Division char: %hhx %hhd unsigned %hhx %hhu\n", n/2, n/2, u/2, u/2);

Ja tulokseksi tulee:

-1 char: ff -1 unsigned: ff 255
Shift char: ff -1 unsigned: 7f 127
Division char: 0 0 unsigned 7f 127