  ̌      ̌    P 48 (2020/2021) 4 23 O predstavitvi podatkov v računalniku: decimalna števila J S Z računalniki lahko dandanes izvajamo kompleksne matematično-fizikalne izračune in simulacije. V marcu 2020 je omrežje Folding@Home, ki se uporablja za izračune zlaga- nja proteinov, preseglo 1.5 exaFLOPS-a, to je več kot 1,500,000,000,000,000,000 računskih operacij na sekundo. Že v enoti sami, ki jo uporabljamo za merjenje hitrosti FLOPS, se skriva glavni podat- kovni tip, ki stoji za vsemi simulacijami. FLOPS na- mreč pomeni floating point operation per second in pove, koliko računskih operacij z decimalnimi števili lahko naredimo v eni sekundi. V tem pri- spevku se bomo poglobili v to, kako decimalna števila sploh predstavimo v računalniku in kako z njimi računamo. Decimalna ali realna števila Velikokrat se pogovorno reče, da v računalniku hra- nimo realna števila. Nekateri programski jeziki, npr. različne verzije SQL, tudi uporabljajo besedo real za oznako tipa. Vendar kljub temu v računalniku nekaterih realnih števil ne moremo predstaviti zelo enostavno. Iracionalna števila, kot npr. ? 2 ali π , obi- čajno le aproksimiramo. Ravno število π je v pro- gramskem jeziku C definirano kot #define M_PI 3.14159265358979323846, torej »le« na 20 decimalk, precej manj kot trenutni slovenski rekord 3333 decimalk, ki jih je znal na pamet povedati zmagovalec zadnjega π -dneva. Kot bomo videli, računalnik uporablja le racionalna šte- vila omejene natančnosti – morebitna realna števila so ustrezno zaokrožena. To nam omogoča enostav- nost računanja in hitrost, natančnost pa ni največja. A brez skrbi, večinoma so decimalna števila, ki jih uporablja računalnik, povsem dovolj natančna. Fiksna in plavajoča pika FLOPS se v prvih dveh črkah nanaša na operacije s plavajočo piko.1 Predstavitev decimalnih števil s plavajočo piko je ena izmed dveh osnovnih načinov predstavitve števil. Druga, enostavnejša možnost je predstavitev s fiksno decimalno piko. Pri slednji imamo na voljo nekaj mest za del pred piko in nekaj mest za decimalno piko. Denimo, da imamo dve me- sti pred in dve mesti po piki. Števila, ki jih lahko zapi- šemo, so torej od 00.00 do 99.99. Interval od r0,100s smo enakomerno pokrili s 10000 števili na razdalji 0.01. Tem številom rečemo predstavljiva, vseh osta- lim pa nepredstavljiva. Vsako realno število, s kate- rim želimo računati, moramo zaokrožiti; ponavadi izberemo najbližje predstavljivo število. Za število x bomo z flpxq označili predstavljivo število, kjer zao- krožimo x. Seštevanje in odštevanje predstavljivih števil je enostavno in natančno, lahko pa pride do prekora- čitve ali podkoračitve; to pomeni, da rezultat leži izven razpona možnih vrednosti. Pri množenju in deljenju pridemo do novih težav. Če pomnožimo 0.5 in 0.41, pri točnem računanju dobimo 0.205, kar pa ni predstavljivo; rezultat je potrebno zaokrožiti na najbližje predstavljivo število. V našem primeru imamo dve izbiri: zaokrožimo lahko na 0.20 ali na 0.21. Čeprav je v resničnem življenju pogosto, da polovice zaokrožamo navzgor, v računalništvu pona- 1Pogosto se jim v slovenščini reče tudi števila s plavajočo vejico, saj je vejica v slovenščini ločilo, ki se uporablja za ozna- čevanje decimalnih mest. Vendar je v računalništvu precej bolj pogosta pika, zato jo bomo uporabljali tudi v tem prispevku.   ̌      ̌    P 48 (2020/2021) 424 vadi velja, da polovice zaokrožamo proti »sodemu številu«. V našem primeru bi zaokrožili k 0.20, saj je zadnja števka 0 soda. Tako pravilo uporabljamo, da bolj enakomerno razporedimo realna števila k nji- hovim decimalnim številom, saj se nekatere polovice zaokrožijo navzgor, nekatere pa navzdol. Računanje s fiksno decimalno piko zagotavlja določeno absolu- tno natančnost: v našem primeru bo zaokrožitvena napaka kvečjemu 0.005, polovica ločljivosti števil, s katerimi delamo. Napisano z enačbo: |x ´ flpxq| ď 0.005. Absolutna natančnost pa prinese tudi svoje težave. Kaj če želimo izračunati 0.05 ˆ 0.08? Točen rezultat bi bil 0.04, kar se zaokroži na 0. Podobno ne mo- remo izračunati 30.12 ˆ 40.23, saj je rezultat večji od 99.99. Računanje s fiksni decimalno piko prepro- sto ni dovolj fleksibilno za delo z velikimi ali majh- nimi števili; dobro deluje le, če so vsa števila, s ka- terimi delamo, med 0 in 99.99. Tukaj pa je pred- nost drugega sistema predstavitve števil, predstavi- tev s plavajočo piko (angl. floating point). V tem sis- temu števila predstavimo v obliki mˆ 10e, za število m P r0,1q, ki se imenuje mantisa, in naravni ekspo- nent e. Kako sistem deluje, si poglejmo na podob- nem primeru kot pri predstavitvi s fiksno decimalno piko. Denimo, da imamo na voljo dve mesti za man- tiso m in dve za eksponent e. Nekaj možnih števil je tako 0.23, 0.54 ¨ 1012, 0.10 ¨ 101. Za eksponente bomo namesto dvomestnih števil od 0 do 99 dovolili števila od ´50 do 49. Še vedno obdržimo enako koli- čino števil, le malo jih zamaknemo, da lahko delamo tudi z negativnimi eksponenti. Tako so tudi števila 0.1 ¨ 10´12 in 0.43 ¨ 10´33 veljavna. Ker lahko števila zapišemo na več različnih nači- nov (npr. 1 “ 1 “ 0.1 ¨ 101 “ 0.01 ¨ 102q, se dogovo- rimo, da vedno pišemo v normalizirani obliki, to je v obliki, kjer se število začne z 0.x, za neničelno de- cimalko x. Število 0.0062 bi napisali kot 0.62 ¨ 10´2, število 123 pa bi zaokrožili na 0.12 ¨ 102. Razpon šte- vil, ki ga s plavajočo piko lahko dosežemo s štirimi mesti, je precej večji kot pri fiksni piki: najmanjše normalizirano pozitivno število je 0.1 ¨ 10´50, torej 0.0000000000000000000000000000000000000000 00000000001, največje 0.9 ¨ 1049, torej 900000000 00000000000000000000000000000000000000000. Seveda nimamo povsod enake absolutne natančno- sti, saj imamo še vedno na voljo samo 10000 števil – pri številih okoli 500 bo razlika med sosednjimi šte- vili 10 (npr. med 0.5 ¨ 103 in 0.51 ¨ 103), pri številih okoli 700000 bo ločljivost 10000, pri številih okoli 0.0003 pa 0.00001. Vidimo, da se ločljivost prilagaja velikosti števil. To je tudi smiselno: če računamo ne- kaj v kilometrih, pričakujemo tudi natančnost v redu velikosti kilometrov, če pa računamo v mikrometrih, prav tako pričakujemo natančnost v mikrometrih. Za števila, predstavljena s plavajočo piko, pravimo, da imajo približno enako relativno natančnost. Če šte- vilo x zapišemo s plavajočo piko, bo absolutna na- paka pri zaokrožitvi morda velika, relativna napaka | flpxq ´ x| |x| pa bo vedno manjša od 0.05. Tej vrednosti rečemo osnovna zaokrožitvena napaka. Za konec si razliko med fiksno in plavajočo vejico poglejmo še grafično. Na sliki 1 vidimo primerjavo predstavljivih števil v fiksni in premični piki. Računanje s premično piko Premična pika je bolj fleksibilna pri računskih ope- racijah. Računalnik nam za osnovne računske ope- racije (+, ´, ˆ, {) zagotavlja, da bo rezultat opera- cije zaokrožen na najbližje predstavljivo število. Če seštejemo 6.3 in 7.4 bi pri točnem izračunu dobili 13.7, vendar se rezultat zaokroži na 14, saj imamo na voljo le dve mesti natančnosti. Formalno izračun poteka tako: 0.63 ¨ 101 ` 0.74 ¨ 101 “ flp1.37 ¨ 101q “ 0.14 ¨ 102. Podobno se zgodi pri množenju: 6.3 pomnoženo s 7.4 bi s točnim računanjem dalo 46.62, v premični piki, pa se izračuna v 0.63 ¨ 101 ˆ0.74 ¨ 101 “ flp4.662 ¨ 101q “ 0.46 ¨ 102. Ker imamo le dve mesti natančnosti, moramo pri re- zultatu zavreči dve decimalki, toda relativna napaka p46.62´46q{46.62 « 0.013 je manjša od osnovne za- okrožitvene napake 0.05. Poglejmo še primer 0.05 ˆ 0.08, ki je pri fiksni decimalni piki povzročal težave. Točen rezultat je 0.004, v premični piki dobimo 0.5 ¨ 10´1ˆ0.8 ¨ 10´1 “ flp0.04 ¨ 10´1q “ 0.4 ¨ 10´2,   ̌      ̌    P 48 (2020/2021) 4 25 0.085 0.09 0.095 0.1 0.105 0.11 0.115 0.12 0.85 0.9 0.95 1 1.05 1.1 1.15 1.2 8.5 9 9.5 10 10.5 11 11.5 12 fiksna pika plavajoča pika SLIKA 1. Primerjava pogostosti predstavljivih števil na treh razlǐcnih delih realne osi. Števila s fiksno piko imajo enako absolutno natančnost ne glede na lo- kacijo, števila s premǐcno piko pa po- stajajo čedalje bolj redka, toda ohra- njajo enako relativno natančnost. Na grafih se spreminjajo enote, zato šte- vila s fiksno piko izgledajo, kot da so čedalje bolj gosta, števila s premǐcno piko pa se zdijo enako gosta. kar je popolnoma točno. Če bi dodali malo več deci- malk in izračunali 0.053 ˆ 0.082, bi izračun potekal tako: 0.53 ¨ 10´1 ˆ 0.82 ¨ 10´1 “ flp0.4346 ¨ 10´1q “ 0.43 ¨ 10´2. V tem primeru rezultat ni točen, je pa precej boljši kot pri fiksni piki. Njegova relativna napaka je pribli- žno 1%. Poučno je tudi,kaj se zgodi, če izračunamo npr. 100 ` 0.1. Dobimo 0.1 ¨ 103 ˆ 0.1 “ flp0.1001 ¨ 103q “ 0.1 ¨ 103. Rezultat je zopet točno 100, saj vrednost 0.1 ni bila dovolj velika, da bi jo upoštevali, in se je zgubila pri zaokroževanju. Še vedno pa je to znotraj zaokroži- tvene napake: 0.1 predstavlja le 0.1% od 100, kar je močno znotraj dovoljene 5% napake. Dejanska števila v računalniku Pri delu z decimalnimi števili v računalniku ne upo- rabljamo le dveh decimalnih mest, kot smo jih mi do sedaj, a principi računanja kljub temu ostanejo enaki. Delo z decimalnimi števili predpisuje stan- dard IEEE 754. Glavni tip, ki ga najpogosteje upo- rabljamo, se imenuje double. Velik je 64 bitov in se imenuje dvojna natančnost – tako ime ima, ker je dvakrat večji od 32-bitnega tipa single, ki predsta- vlja enojno natančnost. Decimalna števila so shra- njena v dvojiškem sistemu, ne v desetiškem. Primer števila bi bilo npr. 0.101101 ˆ 23, kar pretvorimo v p1¨2´1`0 ¨2´2`1 ¨2´3`1 ¨2´4 ` 0 ¨ 2´5`1 ¨ 2´6q ˆ 23 “ 5.625. Izmed 64 bitov, ki so na voljo, jih je 11 rezervira- nih za eksponent, 52 za mantiso (decimalke), in 1 za predznak števila (+ ali ´). 11-bitni prosto za ekspo- nent nam omogoča eksponente od ´1022 do 1023, najmanjši in največji eksponent ´1023 in 1024 pa sta rezervirana za posebna števila, o katerih bomo več povedali kasneje. V dvojiškem sistemu velja ome- niti tudi posebno obliko normaliziranega zapisa. Če število zapišemo v običajnem normaliziranem zapi- su, bo oblike 0.x, kjer je x neničelna števka. Toda v dvojiškem je ta števka lahko le 1, zato je nepotrebno, da jo shranjujemo, in se raje dogovorimo, da kot nor- malizirano obliko za dvojiška decimalna števila vza- memo 1.x, kjer je x poljuben, 0 ali 1. Tako najbolje izkoristimo 52 dvojiških decimalk, ki jih imamo na voljo, kar je enako približno 16 desetiškim decimal- kam natančnosti. V tem sistemu je osnovna zaokro- žitvena napaka enaka 1.11 ¨ 10´16.   ̌      ̌    P 48 (2020/2021) 426 Decimalno število s 64 biti je v računalniku pred- stavljeno kot: s eee . . . eee looooomooooon 11 bitov za eksponent mmm. . .mmm loooooooooomoooooooooon 52 bitov za mantiso , kjer s predstavlja en bit za predznak. Poglejmo si konkreten primer. Bitni zapis števila 4269.6842 je enak 0 10000001011 0000101011011010111100100 111101110110010111111101100, kjer so vrinjeni presledki za lažje branje. Prvi bit je enak 0, kar nam pove, da je število pozitivno. Sledi eksponent; če 10000001011 pretvorimo v desetiško, dobimo 1035. Vendar so eksponenti zamaknjeni, saj nimajo razpona od 0 do 2048, temveč od ´1023 do 1024. Z upoštevanjem zamika je iskani eksponent enak 1035 ´ 1023 “ 12. Zapisano število je tako enako 1.000010101101101011110010011110111011 0010111111101100 ˆ 212, kar je enako 1000010101101.101011110010011110111011 0010111111101100 oz. približno 4269.6841999999996915. Kot vidimo, rezultat ni točno 4269.6842, temveč je za približno 3.09 ¨ 10´13, kar (relativno gledano) ustreza osnovni zaokrožitveni napaki. Posledice zaokroževanja Zaokroževanje na najbližjo vrednost pomeni, da obi- čajna pravila računanja ne držijo več. Če v računal- niku izračunamo pa ` bq ` c, to ni več nujno enako kot a` pb` cq. Poglejmo primer: vzemimo a “ 0.88, b “ 0.56 in c “ 0.13 ¨ 101. Če izračunamo a` b ` c od leve proti desni, dobimo a` b ` c “ 0.88 ` 0.56 ` 0.13 ¨ 101 “ flp1.44q ` 0.13 ¨ 101 “ 0.14 ¨ 101 ` 0.13 ¨ 101 “ flp0.27 ¨ 101q “ 0.27 ¨ 101. Če pa izračunamo a ` b ` c od desne proti levi, dobimo a` b ` c “ 0.88 ` 0.56 ` 0.13 ¨ 101 “ 0.88 ` flp1.86q “ 0.88 ` 0.19 ¨ 101 “ flp2.78q “ 0.28 ¨ 101. Razlika je majhna, vendar števili nista enaki. To je tudi eden izmed razlogov, da decimalna števila red- ko neposredno primerjamo, ali imajo popolnoma enako vrednost. Že majhne razlike v načinu izračuna namreč lahko prinesejo napake pri zadnjih decimal- kah. Poslužimo se raje primerjanja s toleranco: na- mesto da bi pogledali, ali je a “ b, pogledamo, ali je absolutna vrednost razlike med a in b manjša od tolerance t: |a´b| ď t, za npr. t “ 0.00001. Z izbiro vrednosti t lahko tudi določimo, kako velike napake so še sprejemljive. Še ena zanimivost se pojavi pri računanju aritme- tične sredine dveh števil. V matematiki smo navajeni, da aritmetična sredina a`b2 dveh števil a in b leži na- tanko na sredini med številoma. Pri decimalnih števi- lih temu ni tako: vzemimo npr. a “ 0.21 in b “ 0.24. Njuna izračunana aritmetična sredina je a` b 2 “ 1 2 p0.21 ` 0.24q “ 1 2 flp0.43q “ “ 1 2 p0.43q “ flp0.215q “ 0.22, kar ni točen rezultat 0.215, toda je (eden izmed) naj- boljših možnih približkov. Oglejmo si še en primer izračuna aritmetične sre- dine, tokrat za a “ 0.66 in b “ 0.67: a` b 2 “ 1 2 p0.66 ` 0.67q “ 1 2 flp1.33q “ “ 1 2 p0.13 ¨ 101q “ flp0.65q “ 0.65. Tokrat smo dobili, da je sredina števil 0.66 in 0.67 enaka 0.65, kar seveda leži izven intervala r0.66,0.67s! Če pogledamo izračun, vidimo, da je bil glavni krivec za izgubo natančnosti to, da smo zašli v prevelika števila. Pri predstavitvi 1.33 smo lahko obdržali le dve mesti in smo bili prisiljeni zadnjo za- vreči, da smo shranili 1.3. Izračun bi lahko popravili tako, da bi ga namesto a`b 2 napisali kot a` b´a 2 . V tem primeru ne bi prišlo   ̌      ̌    P 48 (2020/2021) 4 27 do dela s tako velikimi števili in rezultat bi bil a` b ´ a 2 “ 0.66 ` 1 2 p0.67 ´ 0.66q “ 0.66 ` 1 2 flp0.01q “ 0.66 ` 1 2 p0.1 ¨ 10´1q “ 0.66 ` flp0.05 ¨ 10´1q “ 0.66 ` 0.5 ¨ 10´2 “ flp0.665q “ 0.66. V tem primeru je rezultat do zadnjega koraka kljub vmesnim zaokrožitvam točen, na koncu pa se zao- kroži na najboljši možni približek. Pokažimo še nekaj zanimivih primerov dejanske dvojne natančnosti. V izbranem programskem je- ziku (npr. Python) izračunajmo s “ 0.1 ` 0.1 ` 0.1 ` 0.1`0.1`0.1`0.1`0.1`0.1`0.1. Če bi bilo računa- nje točno, bi rezultat moral biti enak 1. Vendar, ko preverimo s == 1, dobimo odgovor False, da ena- kost ne drži. Res, če izpišemo vrednost s na dovolj decimalk, dobimo s “ 0.99999999999999988898, oz. razliko |s ´ 1| “ 1.110223 ¨ 10´16. Če bi pri pre- verjanju uporabili toleranco npr. 10´15, bi dobili pra- vilni rezultat, da sta odgovora enaka. Ponovimo eksperiment z 0.5 namesto 0.1. Rezul- tat bi v tem primeru moral biti 5, in vrednost s je 5.00000000000000000000 ter primerjava s == 5 vrne True. Zakaj tokrat nismo dobili 4.99999999999 999999 namesto 5? Odgovor je, da so bila vsa števila, ki so v računu nastopala, torej 0, 0.5, 1.0, 1.5, 2.0, 2.5 itd. . . , predstavljiva in nikoli ni prišlo do zaokroževa- nja. Tako je bil tudi rezultat točen. Če pazljivo izbi- ramo števila, lahko sicer konstruiramo primere, kot je zgornji, kjer napak ni. Vendar v praksi na kaj ta- kega ne moremo računati – sprijazniti se je potrebno z napakami in znati z njimi ustrezno ravnati. Posebne vrednosti Prej smo omenili, da sta eksponenta ´1023 in 1024 rezervirana za posebne vrednosti. To je potrebno, ker poleg decimalnih števil standard vsebuje tudi po- sebna števila ˘8 in NaN, ki predstavljajo (pozitivno ali negativno) neskočnost, in pa posebno število »not a number«, ki označuje, da je rezultat neveljaven ali nedoločen. Najlažji način, da dobimo vrednost 8, ki ga običajno napišemo z besedilom kot »inf«, je, da poskusimo izračunati npr. 1.0 / 0.0. Večina program- skih jezikov (z izjemo Pythona) bo vrnila vrednost inf. Za računanje z neskončnostjo veljajo naslednja pravila: 8 ` a “ 8 8 ´ a “ 8 8 ¨ a “ $ ’ ’ & ’ ’ % 8; če a ą 0 NaN; če a “ 0 ´8; če a ă 0 8{a “ # 8; če a ě 0 ´8; če a ă 0 a8 “ $ ’ ’ & ’ ’ % 0; če |a| ă 1 1; če |a| “ 1 8; če |a| ą 1 8a “ $ ’ ’ & ’ ’ % 0; če a ă 0 1; če a “ 0 8; če a ą 0 Pri tem smo predpostavili, da je a običajno število (torej ni 8 ali NaN). Definirano pa je npr. tudi 8`8 “ 8 in 8 ¨ 8 “ 8. Definicije so smiselne z vidika matematičnih limit: če imamo dve zaporedij števil, ki gresta proti 8, gre proti 8 tudi njun produkt ali vsota. Za razliko ali kvocient pa to ne velja, zato so rezultati izrazov 8{8, 8 ´ 8, 0{0 ali 0 ¨ 8 enaki NaN. Računanje z NaN pa je enostavno definirati: čim je eden od operandov enak NaN, je tudi rezultat NaN: torej a ` NaN “ NaN, a ¨ NaN “ NaN, aNaN “ NaN, . . . S posebnimi vrednostmi pa znajo delati tudi ele- mentarne funkcije. Velja npr. ?8 “ 8, ? ´2 “ NaN, sinp8q “ NaN, arctanp8q “ π{2. Za NaN pa velja še ena posebnost: če ga primer- jamo samega s sabo, vrne False. Torej, če npr. nare- dimo x = 0.0/0.0 in nato izračunamo x == x, do- bimo False. Ta posebna lastnost velja samo za NaN in ne drži za nobeno drugo decimalno število. Kot smo videli, je to le ena izmed pasti, v katero se lahko ujamemo pri delu z decimalnimi števili. S tem, kako računati, da se v naše izračune ne prikrade preveč napak, se ukvarja numerična matematika. Ta tudi neveščemu računalničarju omogoča zanesljivo delo z decimalnimi števili. ˆ ˆ ˆ