# -*- coding:utf-8 ; mode:org -*-
#+title: Lukujärjestelmistä
#+language: fi
#+options: timestamp:t toc:t num:nil author:nil creator:nil email:nil
#+LaTeX_CLASS_OPTIONS: [a4paper,finnish]
#+LaTeX_HEADER: \usepackage{babel}[finnish]
# LaTeX_HEADER: \usepackage{fullpage}
#+LaTeX_HEADER: \usepackage[a4paper]{geometry}
# (setq org-confirm-babel-evaluate nil)

* Desimaali

  Meille tutut luvut ovat desimaalilukuja, eli
  kymmenjärjestelmälukuja. Niiden kantaluku on 10, joten lukujen
  esittämiseen ovat käytössä numerot 0-9.

  Esimerkiksi luku 42 tarkoittaa /neljä kymmentä ja kaksi ykköstä/,
  eli 42 = 4 * 10 + 2 * 1.  Isompi luku, vaikka 254, olisi /kaksi sataa,
  viisi kymmentä ja neljä ykköstä/, eli 2 * 100 + 5 * 10 + 4 * 1.

  Kertoimet 100, 10 ja 1 edellä ovat /kantaluvun potensseja/, eli
  vastaavasti 100 = 10^2, 10 = 10^1 ja 1 = 10^0.

* Heksadesimaali

  Heksadesimaalijärjestelmä taas on kuusitoistajärjestelmä, eli
  sen kantaluku on 16.  Koska meillä ei ole numeroita käytössä enempää kuin
  0-9, otetaan kirjaimet A-F avuksi, seuraavan taulukon mukaan:

  |----------------+-----------|
  | Heksadesimaali | Desimaali |
  |----------------+-----------|
  | 0              |         0 |
  | 1              |         1 |
  | ...            |       ... |
  | 9              |         9 |
  | A              |        10 |
  | B              |        11 |
  | C              |        12 |
  | D              |        13 |
  | E              |        14 |
  | F              |        15 |
  |----------------+-----------|

  Nyt on hyvä opetella ulkoa heksalukujen A-F ja desimaalien 10-15
  vastaavuus!  Kirjoita tuo yllä oleva taulukko ajatuksella paperille
  ja opettele heksojen A-F ja  desimaalien 10-15 vastaavuus.

  Eli aivan kuten 10-kantajärjestelmässä oli numerot 0-9, eikä numeroa
  10, on meillä 16-kantajärjestelmässä numerot 0-F (15), eikä
  numero 16.

  Miksi?  No, kymmenenhän oli jo eri potenssi, joten 16 on
  heksalukujen seuraava potenssi.  Eli kun 10-kantaluvuille käytettiin
  1, 10, 100, 1000, ...  käytetään heksoille 1 = 16^0, 16 = 16^1, 256
  = 16^2, 4096 = 16^3, ...

  Seuraavaksi on hyvä oppia heksat 0...FF.  Miksi?  Koska ne ovat
  luvut, jotka mahtuvat tavuun, eli yhteen tietokoneen muistipaikkaan.
  Perustelu tälle tulee myöhemmin binäärilukujen yhteydessä.

  Jotta heksanumero eroaa desimaalinumerosta, lisään desimaaliin
  alatunnisteen 10, esimerkiksi 15_10, ja heksaan alatunnisteen 16,
  esimerkiksi F_16.  Eri ohjelmointikielissä on sitten eri tapoja
  esittää heksalukuja, esim. =0xF=, =$F=, tai =#xF=.

  Tuo F_16 onkin ensimmäinen mielenkiintoinen luku, koska sen jälkeen tulee
  ensimmäinen kertaluokan kasvu (kertoimen kasvu) heksoissa, eli seuraava
  luku onkin 10_16, joka luetaan ”heksaluku yksi nolla”.  No, jos emme
  tietäisi, mitä tuo on desimaalilukuna, nythän voit arvata sen olevan 16_10,
  se selviäisi pienellä laskulla.  Otetaan Python-kieli avuksi:

  #+begin_src python :exports both :name hex10-to-dec.py :cache yes
    desimaaliluku = 1 * 16 + 0 * 1
    return f'10 heksadesimaalina on desimaalina {desimaaliluku}.'
  #+end_src

  #+RESULTS[269c47a9ce2e60469550742ae5a3e77c92c6229a]:
  : 10 heksadesimaalina on desimaalina 16.

  Vastaushan on tietysti desimaalilukuna 16.  Nyt tietysti kiinnostaa,
  mitä ovat 1F_16, 20_16, ..., 9F_16, A0_16, ... ja FF_16.  Jottei hommasta
  tulisi turhan raskasta, määritellään apufunktio, joka helpottaa meitä:

  #+begin_src python :name heksatavu.py :exports both :cache yes :results value table raw :session heksa2des :hlines yes :vlines yes
    def heksa_desimaaliksi(kuudettoista, yhdet):
        return kuudettoista * 16 + yhdet * 1

    print('Heksa 0x10 desimaalina on {heksa_desimaaliksi(1, 0)}, tulisi olla 16.')
    print('Heksa 0x1F desimaalina on {heksa_desimaaliksi(1, 15)}, tulisi olla 31.')
    print('Heksa 0x20 desimaalina on {heksa_desimaaliksi(2, 0)}, tulisi olla 32.')

    (('10_16', 'on', heksa_desimaaliksi(1,  0), 'tulisi olla', 16),
     ('1F_16', 'on', heksa_desimaaliksi(1, 15), 'tulisi olla',  31),
     ('20_16', 'on', heksa_desimaaliksi(2,  0), 'tulisi olla',  32))
    #+end_src

    #+RESULTS[1e8a794b5f5558b33a500bd113c68a3af085d791]:
    | 10_16 | on | 16 | tulisi olla | 16 |
    | 1F_16 | on | 31 | tulisi olla | 31 |
    | 20_16 | on | 32 | tulisi olla | 32 |

  Näyttää hyvälle, jos jätetään alaviite 16 sotkemasta, joten kaikki
  kysytyt luvut olisivat:

  #+begin_src python :name heksa10-FF.py :exports both :cache yes :results value table raw :session heksa2des :hlines yes :vlines yes
    result = []
    for h16 in range(0,16):
        result.append((f'{h16:#X}0'[2:]+f' {heksa_desimaaliksi(h16, 0):#3d}',
                       f'{h16:#X}F'[2:]+f' {heksa_desimaaliksi(h16, 15):#3d}'))
    result
  #+end_src

  #+RESULTS[ccd4c413d82a0c9c50db074361ccbef94d0a151f]:
  | 00   0 | 0F  15 |
  | 10  16 | 1F  31 |
  | 20  32 | 2F  47 |
  | 30  48 | 3F  63 |
  | 40  64 | 4F  79 |
  | 50  80 | 5F  95 |
  | 60  96 | 6F 111 |
  | 70 112 | 7F 127 |
  | 80 128 | 8F 143 |
  | 90 144 | 9F 159 |
  | A0 160 | AF 175 |
  | B0 176 | BF 191 |
  | C0 192 | CF 207 |
  | D0 208 | DF 223 |
  | E0 224 | EF 239 |
  | F0 240 | FF 255 |

  Tarkkasilmäisimmät varmaan huomaavat, että yllä merkkijonon
  muodostuksessa muutammekin Pythonin avulla automaattisesti
  desimaaliluvun vastaavan heksaluvun sisältäväksi merkkijonoksi.
  Jokaisessa kielessä on tällaisia komentoja, joten etsi käyttämästäsi
  ohjelmointikielestä vastaava.

** Heksaluku desimaaliluvuksi

  Heksaluku on siten helppo muuttaa desimaaliluvuksi, kerrotaan
  oikeanpuoleisin numero ykkösellä, seuraava 16:lla, sitten 256:lla
  niin kauan kuin numeroita riittää, ja lasketaan tulokset yhteen.

** Desimaaliluku heksaluvuksi

  Desimaaliluvun muuntaminen heksaluvuksi onkin sitten haastavampaa,
  mutta siihen on kaksi tapaa.  Toinen onnistuu helpommin ohjelmalla,
  toinen helpommin päässälaskien.

  Otetaan päässälaskutapa ensin.  Idea on, että etsit edellisen
  taulukon vasemmasta sarakkeesta suurimman desimaaliluvun, joka on
  pienempi tai yhtäsuuri kuin muutettava luku.  Näin saat 16:n
  kertoimen.  Vähennät löytämäsi luvun muunnettavasta luvusta ja näin
  saadulle luvulle teet saman heksanumeroiden 0_16-F_16 avulla.  Nuohan
  olet tässä vaiheessa jo opetellut ulkoa.

  Esimerkiksi seuraavassa muutetaan 203 heksaksi. Suurin luku, joka
  tuohon mahtuu on C0_16 eli 192.  Erotus 203-192 antaa tulokseksi 11,
  mikä on heksana B_16.  Vastaus on näin ollen CB_16.

  Toinen tapa on hieman matemaattisempi.
    1. Jaa luku 16:lla ja ota jakojäännös talteen.
       Jakojäännös vastaa heksaluvun numeroa.
    2. Ota jakolaskun tuloksen kokonaisosa käsittelyyn.
    3. Jos se on nolla, on muunnos valmis.
    4. Jos se ei ole nolla, toista kohdasta 1.

   Esimerkkinä $203/16$ jakojäännös on 11, joka on heksana B_16.
   Kokonaisosa laskusta $203/16$ 12, joten lasketaan $12/16$ ja
   saadaan jakojäännös 12 eli C_16.  Tuon jakolaskun kokonaisosa on
   nolla, joten vastaus on valmis CB_16.  Muista, että ensin tulee
   oikeanpuoleisin numero, sitten sen vasemmalla puolella oleva.

** Nelilukuiset heksat

   Otetaan vielä pari esimerkkiä nelilukuisista heksoista.  Ensin
   vaikka muunnos heksasta CAFE_16 desimaaliksi:

   #+begin_example
        C * 16^3  +   A * 16^2  +   F * 16^1  +   E * 16^0
     = 12 * 16^3  +  10 * 16^2  +  15 * 16^1  +  14 * 16^0
     =   49152    +    2560     +     240     +     14
     = 51966
   #+end_example

   Ja toisinpäin

   #+begin_example
     51966 % 16 =   14  -> E
     51966 / 16 = 3247
      3247 % 16 =   15  -> F
      3247 / 16 =  202
       202 % 16 =   10  -> A
       202 / 16 =   12
        12 % 16 =   12  -> C
        12 / 16 =    0
   #+end_example

   Eli CAFE_16, mutta vielä sama toisin:

   #+begin_example
     51966 / (16 * 16 * 16) = 51966 /  4096 ≈ 12  -> C
     51966 - 12 * 16*16*16  = 51966 - 49152 = 2814
      2814 / (16*16)        =  2814 /  2560 ≈ 10  -> A
      2814 - 10 * 16*16     =  2814 -  2560 = 254
       254 / 16                             ≈ 15  -> F
       254 - 15 * 16        =   254 -   240 = 14
        14 / 1                              = 14  -> E
   #+end_example

   Sama tuttu CAFE_16!

* Binääriluvut

  Binääriluvut ovat 2-kantaisia, eli numeroita on käytössä vain kaksi:
  0 ja 1.  Kyllä, tämä on yksi bitti.  Kertaluvut menevät kahden
  potensseissa, eli toivottavasti tutussa litaniassa: 1, 2, 4, 8, 16,
  32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536,
  ...  huh!

  Binäärilukuja esitetään harvemmin lähdekoodissa literaaleina, mutta
  pari esimerkkiä voisivat olla =0b1111= ja =#b1111=, molemmat vastaa
  lukua 15, eli F_16.

  Desimaaliluku on helppo muuttaa binääriluvuksi ja takaisin
  heksalukujen keinoin, vaihdetaan vaan kertoimen 16:n tilalle 2.

  #+begin_example
    51966 % 2 =       0
    51966 / 2 = 25983
    25983 % 2 =       1
    25983 / 2 = 12991
    12991 % 2 =       1
    12991 / 2 = 6495
     6495 % 2 =       1
     6495 / 2 = 3247
     3247 % 2 =       1
     3247 / 2 = 1623
     1623 % 2 =       1
     1623 / 2 = 811
      811 % 2 =       1
      811 / 2 = 405
      405 % 2 =       1
      405 / 2 = 202
      202 % 2 =       0
      202 / 2 = 101
      101 % 2 =       1
      101 / 2 = 50
       50 % 2 =       0
       50 / 2 = 25
       25 % 2 =       1
       25 / 2 = 12
       12 % 2 =       0
       12 / 2 = 6
        6 % 2 =       0
        6 / 2 = 3
        3 % 2 =       1
        3 / 2 = 1
        1 % 2 =       1
        1 / 2 = 0
   #+end_example

   Eli lopputulos olisi 1100101011111110.  Meniköhän oikein?
   Itseasiassa binäärejä ja heksadesimaaleja on helpompi muuttaa
   toisikseen kuin desimaaleiksi.  Juju on siinä, että neljän
   binäärinumeron rypäs vastaa aina yhtä heksanumeroa!  Kokeillaan,
   eli jaetaan saatu binääri neljän bitin paloihin
   oikeanpuoleisimmasta bitistä vasemmalle edeten, ja muunnetaan
   saadut ryhmät heksoiksi.

   #+begin_example
     1100101011111110
     – jaetaan neljän bitin ryhmiin
     1100    1010    1111    1110
     – muutetaan jokainen kuin neljän bitin lukuna desimaaliksi
     8+4     8+2    8+4+2+1  8+4+2
     12      10       15       14
     – muutetaan desimaalit heksoiksi
     C       A        F        E
   #+end_example

   Ok, tunnustan, poimin ensin bitit väärinpäin ja sain eri
   vastauksen!  Korjasin sen sitten oikeaksi.  Tarkkana täytyy olla
   näissä muunnoksissa.

   Huomaa, että heksan muuttaminen binääriksi onnistuu tekemällä
   edellinen esimerkki toisin päin.

   Muunnokset heksojen ja binäärilukujen välillä ovat siis tavattoman
   helppoja.  Muunnokset heksojen ja desimaalien välillä kannattaakin
   päässälaskien tehdä binäärien kautta.


* Oktaaliluvut

  Oktaaliluvut ovat kahdeksankantaisia lukuja, eli käytössä ovat
  numerot 0-7.  Hetken ajattelun jälkeen huomaa, että binäärit
  auttavat tässäkin, sillä kolmen bitin ryhmät antavat juurikin
  nuo numerot väliltä 0-7.

  Oktaaliluvut esitetään ohjelmointikielissä esim. =017=, =0o17= tai
  =#o17=, mikä on jälleen sama luku kuin 15 tai F_16.  Tässä
  kirjoituksessa oktaali merkitään kuten heksat alaviitteellä,
  esim. 17_8.

  Kuten heksojen muunnos onnistui helpoiten binäärien kautta, onnistuu
  oktaalien muunnoskin helposti binäärien kautta.

  #+begin_example
    1100101011111110
    – jaetaan kolmen bitin ryhmiin
    001   100   101    011   111    110
    – muutetaan jokainen kuin neljän bitin lukuna desimaaliksi
     1     4    4+1   2+1   4+2+1   4+2
     1     4     5     3      7      6
   #+end_example

   Eli heksa CAFE_16 olisi binäärinä 1100101011111110_2, eli oktaalina
   145376_8.

   Mitä iloa oktaaleista on?  Ainakin niitä on helppo
   muuttaa päässään binääreiksi ja takaisin, joten niiden
   avulla on vähemmän tilaa vievää esittää binäärejä, jotka
   tulkitaan lipuiksi, kuten vaikka POSIXissa tiedostolla
   olevia oikeuslippuja, kuten onko tiedosto käyttäjän,
   ryhmän ja muiden luettavissa.

   Nyt olisi varmaan kahvi- tai teekupposen aika.  Sitä hörppiessä
   kannattaa paperilla ja kynällä harjoitella
   lukujärjestelmämuunnoksia desimaalien, heksojen, binäärien ja
   oktaalien välillä, ainakin lukualueella 0–255, eli 0_16–FF_16, eli
   0_8–377_8.

* Negatiiviset luvut?

  Muilla kuin binääreillä laitetaan vain miinus-merkki luvun eteen,
  jos luku on nollaa pienempi.  Binääreillä pitää päättää, miten
  negatiiviset luvut esitetään.  Yleinen tapa on /kahden
  komplementti/.  Sitä ei nyt selitetä tarkemmin, ohjeet löytyy
  verkosta ja muilta kursseilta.

  Literaaleina, eli merkintätapana, toimii yleensä sama kuin
  desimaaliluvuille, eli esim. -10_16 = -16_10.

* Rationaal- ja reaaliluvut?

  Näissä käy samoin kuin negatiivisissa, joskaan heksoja ja oktaaleja
  näkee usein vain kokonaislukuina.  Binääreille pitää taas sopia
  tapa, näitä löytyy verkosta.  Kurssin alueeseen ne eivät kuulu.
  Hyvin yleistä näille on, että ne eivät esitä välttämättä tarkkaa
  arvoa luvulle, vaan luku on aina likiarvo.  Tästä johtuu monen
  ohjelmointikielen kuvitellut pyöristysvirheet laskennassa.  Eivät ne
  virheitä ole, lukualueet vaan ovat aukkoja täynnä.  (Tietokone
  käyttää liukulukujen laskennassaan kahden potensseja,
  binäärinumeroita siis, joten jo 0.1 ja 0.01 ovat mahdottomia esittää
  tarkasti.)

  Helpointa on kertoa luku vaikka tuhannella, niin saadaan esitettyä
  desimaalit tuhannesosan tarkkuudella.  Tulos pitää muistaa jakaa
  tuhannella ainakin tulostuksen yhteydessä.

  Rationaaliluvut toteutetaan yleensä ohjelmallisesti, eikä
  prosessorin laskentayksikössä.  Rationaalilukujen käyttö on toinen
  mahdollisuus tarkaan laskentaan, silloin kun rationaaliluvut siihen
  matemaattisesti riittävät.  Sitten on tietty irrationaaliluvut,
  mutta niistäkin selvitään approksimoiden tai symbolisesti.  Nämä
  eivät ole kurssin asioita, joten jätetään nämä tähän.

* Muita lukujärjestelmiä?

  Jep, löytyy ainakin BCD, eli /binary coded decimal/, jossa käytetään
  neljä bittiä numeron 0-9 esittämiseen.  Näin 16 bitillä voidaan
  esittää desimaaliluku väliltä 0...9999.  Tämä oli aikoinaan
  suosittua jopa prosessoritasolla, muttei varmaankaan enää.

  Ja erikantaiset luvut, vaikkapa 7381-kantaiset, tehdään sitten
  soveltamalla edellä esitettyjä sääntöjä.

* Pari muistilappua Emacsille                                      :noexport:
  - (setq org-confirm-babel-evaluate nil py-python-command "python3")
  - (cl-loop for i from 0 to 16 do (insert (format "%02X_16 " i)))
  - C-c C-x \
  - C-x * *
  - 2 2 +
  - 2a =>
  - 5 s t a
  - d r 2
  - 10#0.1, 10#0.01, 10#0.5
    (tai 0.1, 0.01, ... koska syöttö aina oletuksena desim.)