Skip to content
Snippets Groups Projects
heksaa.org 14.65 KiB

Lukujärjestelmistä

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:

desimaaliluku = 1 * 16 + 0 * 1
return f'10 heksadesimaalina on desimaalina {desimaaliluku}.'

#+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ä:

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))

#+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:

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

#+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:

   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

Ja toisinpäin

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

Eli CAFE_16, mutta vielä sama toisin:

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

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.

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

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.

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

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.

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

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

  • (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.)