Det binära talsystemet Datorer arbetar med digitala signaler, signaler som bara kan anta två olika värden. Dessa representeras elektriskt som spänning eller frånvaro av spänning, och beteckas 1 och 0. Trots att datorer alltså egentligen bara kan räkna till ett så kan man representera godtyckligt stora tal genom att använda det binära talsystemet. Vårt vanliga talsystem är ett så kallat positionssystem med den för människor praktiska basen 10. Vi har bara siffor från 0 till 9, större tal representeras med flersiffriga tal där siffrorna successivt får större vikt ju längre åt vänster de står i talet. Detta kallas decimalsystemet. Decimalsystemet och även våra moderna siffror är en arabisk uppfinning, men själva namnet kommer från grekiskans och latinets ord för “tio gånger”. 3 2 1 0 4701 = 4 ⋅ 10 + 7 ⋅ 10 + 0 ⋅ 10 + 1 ⋅ 10 = 4000 + 700 + 0 + 1 Om man bara har siffrorna 0 och 1 kan man i stället använda basen 2. Tal representerade i bas 2 noteras ofta med ett index om det finns risk för förväxling. 3 2 1 0 1101 2 = 1 ⋅ 2 + 1 ⋅ 2 + 0 ⋅ 2 + 1 ⋅ 2 = 8 + 4 + 0 + 1 = 13 En binär siffra kallas ofta en bit, vilket från början var en hopdragning av engelskans “binary n digit”. Med n binära siffror kan man representera 2 olika tal. Med 8 bitar kan man representera 256 olika värden, med 16 bitar 65536, och med 32 bitar litet drygt 4 miljarder. Datatyper i programspråk Datorer lagrar och behandlar alla data som binära bitar, oftast i grupper om 8 bitar som kallas bytes1. En dators minne är oftast organiserat i bytes, så att man kan adressera enstaka grupper om 8 bitar för att läsa och skriva dem. Heltal För att representera heltal används en eller flera bytes. Den vanligaste sortens heltal representeras med 32 bitar, lagrade i 4 bytes efter varandra i minnet. Det finns i de flesta programspråk även heltalstyper med 16 och 8 bitar. Om man vill representera positiva heltal med 8 bitar ser det ut så här: 0 1 43 254 255 00000000 00000001 00101011 11111110 11111111 När man representerar tal med mer än en enstaka byte stöter man på problemet i vilken ordning man skall lägga dem i minnet. Tyvärr har inte datortillverkarna lyckats enas om en standard, så en PC lägger dem i en ordning, och en Mac i motsatt ordning. Man talar om “little-endian” och “big-endian” representation. Detta är en källa till förtret för programmerare som vill få sina program att fungera på flera plattformar. Om man även vill ha negativa tal (och det vill man oftast) så kan man låta hälften av talen representera negativa tal, och man väljer då oftast de största, de som har en etta längst till vänster. 1. Ordet “byte” är en lätt förvanskning av engelskans “bite” som betyder “tugga”, alltså en lagom mängd bitar att behandla i taget. Stavningen ändrades från “i” till “y” för att undvika förväxling med det liknande ordet “bit”. Detta kallas tvåkomplementrepresentation. Exakt varför det heter så skulle vara litet för krångligt att förklara här, men representationen gör att man kan använda samma regler för addition med negativa tal som med positiva tal. Den som vill veta detaljerna hänvisas till det inledande kapitlet i valfri bok i datorteknik. Några exempel på 8-bitars tal i tvåkomplementsrepresentation finner du nedan. 0 1 43 127 00000000 00000001 00101011 01111111 -1 -2 -43 -128 11111111 11111110 11010101 10000000 Tal med decimaler Heltal är bra till mycket, men alla former av beräkningar behöver tal med högre noggrannhet, dvs med decimaler. Ett sätt är att använda några av bitarna i talet som vikter för negativa potenser av 2. Detta kallas fixtal och är ett ganska vanligt sätt att göra enklare beräkningar där precisionen inte är huvudsaken, exempelvis i grafikkort och i digitala filter för signalbehandling. Generella datorer använder dock oftast vad som kallas flyttal (floating point numbers). Det är egentligen precis samma system som man använder för teknik och fysik för att representera väldigt stora eller väldigt små tal med en tillräcklig precision utan att skriva långa rader med nollor: man använder en notation med en mantissa och en exponent: 14 362842970000000 = 3.6284297 ⋅ 10 Mantissan “3.6284297” anger värdesiffrorna, medan exponenten “14” anger hur många steg man har flyttat decimalkommat åt ena eller andra hållet. Datorer gör likadant, men binärt: 11 1011000000002 = 1.011 2 ⋅ 2 , 0.00001101 2 = 1.1012 ⋅ 2 –5 I moderna datorer används två sorters flyttal, en version har 64 bitar (8 bytes) varav 53 bitar används för mantissan och 11 bitar för exponenten. Det är inte alltid man behöver så hög precision eller så stort talområde, och därför används ofta en variant med 32 bitar (4 bytes), med 24 bitar för mantissan och 8 bitar för exponenten. Representationen med 32 bitar tar hälften så stor plats i minnet, samt går snabbare att överföra och behandla. I många programmeringsspråk har dessa datatyper egna namn. Oftast heter 32-bitarsversionen “float” och 64-bitarsversionen “double”. Text Datorer behandlar som bekant inte bara siffror. En stor del av de data som lagras och bearbetas av datorer är text. Text lagras som en följd av tecken, där varje tecken representeras (kodas) som ett heltal. Kodningen som används kallas för teckentabell och varierar beroende på vilket språk texten använder, och tyvärr också beroende på vilket slags dator som används. En vanlig teckentabell som används i många sammanhang och fungerar för de flesta västerländska språk är den som kallas “ISO 8859-Latin-1”, och den använder 8 bitar för varje tecken. För språk med många speciella accenttecken krävs dock olika varianter av teckentabellen, och de latinska, grekiska och kyrilliska alfabeten får tyvärr inte plats samtidigt bland de 256 tecknen som kan representeras. En bra och generell internationell standard som löser detta problem är Unicode (www.unicode.org), men det kommer nog tyvärr att ta lång tid innan den standarden är genomförd överallt. Så mycket kan man i alla fall säga att text representeras i datorer som en följd av tecken, där varje tecken kodas som ett binärt tal enligt en överenskommen teckentabell. I teckentabellen ingår mellanslag, skiljetecken, siffror och ett antal specialtecken, och även vissa formateringstecken som radslut har egna koder. Datorteknik för programmerare Sett ur traditionell ingenjörsvetenskaplig synvinkel så bygger datorteknik direkt på digitalteknik, och på många tekniska utbildningar ges stora separata kurser i digitalteknik, datorteknik och digital konstruktion med mikrodatorer. På andra utbildningar läser man endast programmering och mycket litet eller inget alls om tekniken bakom datorers funktion. Datorteknik ger dock en del grundläggande kunskaper om datorers funktionssätt som kan vara mycket praktiska att ha när man programmerar datorer. Helt utan dessa kunskaper måste man anta att en dator är en magisk manick, eller själv skaffa sig en vag inre bild av en apparat som man inte förstår sig på, och det är i bästa fall otillfredsställande, i sämsta fall rent förvirrande. En dators innanmäte En fullt fungerande, ytlig men korrekt mental bild av en dator är den som visas nedan. Datorn består i grova drag av tre delar: en centralenhet (Central Processing Unit, CPU), ett minne och ett antal in- och utenheter (Input/Output, I/O). Minne Program Centralenhet In- och utenheter Register Data I minnet lagras binära bitar, oftast i grupper om 8, alltså 1 byte. Varje byte kan pekas ut av en unik adress och kan både läsas och skrivas. Man kan tänka sig minnet som en lång lista med numrerade rader där man på varje rad kan skriva in åtta binära bitar, och senare läsa vad som skrevs och ändra det. Minnet i en modern dator innehåller typiskt många miljoner eller till och med miljarder bytes. En miljon bytes benämns en megabyte, förkortat MB. En miljard bytes betecknas gigabytes, GB1. Centralenheten är den enhet som läser och skriver i minnet. Inuti centralenheten finns även ett antal interna register som kan innehålla mindre mängder data. Läsning och skrivning i de interna registren går oftast mycket snabbare än läsning och skrivning i minnet. In- och utenheterna är det som gör att datorn kan utföra något arbete. Det kan vara enheter för att sköta inmatning från människor, som t ex mus och tangentbord, eller utmatning för presentation för människor, som bildskärm och ljudutgångar, men det kan även vara enheter för kommunikation med andra datorer, såsom nätverkskort och liknande. Enheter för extern datalagring, som hårddisk och DVD- eller CD-spelare, är också in- och utenheter. 1. Egentligen skall man numera använda enheterna “mibi” och “gibi”, förkortat Mi och Gi, som står för “mega-binary” respektive “giga-binary” i stället för SI-prefixen “mega” och “giga”, eftersom man 10 avser multiplar av 2 = 1024 och inte 1000, men den standarden har ännu inte (2005) fått genomslag. Program Centralenhetens arbete styrs av ett program. Programmet ligger också lagrat i minnet, och närcentralenheten exekverar ett program läser den alltså instruktionerna i minnet, och utför sedan de begärda operationerna på data i samma minne. Principen att program och data ligger i samma minne och att programmet lätt kan ändras gör en modern dator mycket flexibel och generell, och de flesta datorer byggs på det sättet, även om det också är en källa till allvarliga och svårfunna fel när program inte fungerar som de skall och av misstag tolkar program som data eller tvärtom. En vanlig dator utför en enda operation i taget. Även om man numera snabbar upp datorer genom att låta flera olika saker ske samtidigt så är hela grundkonstruktionen av ett datorprogram något som förutsätter en viss ordning mellan operationerna som utförs. Detta kallas med ett formellt ord för sekvens. En modern centralenhet exekverar flera miljarder instruktioner per sekund, och om ett program bara var en rak sekvens av operationer skulle det inte rymmas program i minnet mer än för någon enstaka sekunds exekvering. De flesta program löser problem av repetitiv struktur, så att samma operation utförs många gånger på en stor mängd data. På lägsta nivå i centralenheten finns två slags operationer som möjliggör detta, nämligen repetition och villkor. Repetition medger att man tar om en sekvens av operationer och gör slingor (loopar) i programmet. Villkoren gör att man kan avbryta repetitioner efter ett visst antal varv, när ett visst villkor i data är uppfyllt eller när en inenhet talar om att den vill ha uppmärksamhet. Sekvens, repetition och villkor samt förmågan att läsa och skriva i ett minne är de huvudsakliga kännetecknen för en modern dators funktion. De grundläggande operationer som utförs är mycket enkla, och kan i stort sett karaktäriseras som att man flyttar några enstaka bytes från ett ställe i datorn till ett annat. De interna registren i centralenheten kan kopplas till funktionsblock som kan utföra aritmetiska och logiska operationer, till exempel addera eller mulitplicera två tal. På detta sätt kan en dator fås att behandla data och utföra nyttiga uppgifter. Högnivå- och lågnivåprogrammering Utan att gå in på exakt hur det ser ut någon speciell modern dator kan det ändå vara bra att se hur en till synes enkel operation i ett högnivåspråk skulle kunna utföras som en sekvens av lågnivåinstruktioner. Antag att vi har två variabler i ett program och vill beräkna summan av dem i en tredje variabel. Detta skulle göras med en tilldelningssats och ett aritmetiskt uttryck: a, b, summa: Integer; summa := a + b; Detta program måste översättas (kompileras) till så kallad maskinkod som centralenheten kan exekvera. Först måste de tre variablerna lagras på var sin adress i minnet. Centralenheten har inget begrepp om variabelnamn, allt är bara adresser i minnet. När detta är gjort och summan skall beräknas blir det en ganska lång sekvens av operationer för den enda tilldelningssatsen: 1. Läs heltalet på adressen för variabeln “a” och lagra det i ett register 2. Läs heltalet på adressen för variabeln “b” och lagra det i ett annat register 3. Beräkna summan av heltalen i de två registren och lägg resultatet i ett tredje register 4. Skriv heltalet i det tredje registret till adressen för variabeln “summa” Endast en av fyra deloperationer i denna sekvens är själva beräkningen, resten är datatransporter hit och dit. Detta är typiskt för de flesta program: det blir mycket flyttande av data, och en förhållandevis liten del av programmet utför de egentliga operationer man är ute efter. Även om det är fullt möjligt så är det naturligtvis inte särskilt inspirerande för människor att tvingas programmera på denna låga nivå, och därför har man konstruerat högnivåspråk som Ada, Java, C, C++ med flera. Översättningen från högnivåspråket till ett körbart program i maskinkod görs automatiskt av en kompilator, som är ett program i datorn. Det är precis den sortens repetitiva rutinarbete som datorer är bra på. Datorer kan inte riktigt programmera sig själva, men de kan göra programmeringen mycket enklare för människor!