Een breuk in de Atom Inleiding Nee, het gaat hier niet over de zoveelste hardware storing in de originele Atom. In dit verhaal wil ik het een en ander uit de doeken doen over de opbouw van Floating point getallen. Met deze wijsheid in de binnenzak, moet het mogelijk zijn om verder te gaan met de in het vorige nummer van Atom Nieuws vermeldde project over de 8087 floating point processor als hulpje van de Atom. Opzet De floating point getallen worden op een zodanige manier opgeslagen, dat het eenvoudig moet zijn om er mee te rekenen. Deze opslag gebeurt op een min of meer standaard manier. Deze manier is in de Atom als een 5 bytes getal opgeslagen. De opbouw is als volgt: 1 byte voor een exponent 4 bytes voor een mantissa Een mantissa is een moeilijk woord voor het gebroken deel van een getal. Mantissa In dit getal wordt het getal opgeslagen wat een fractie repre- senteert. In theorie is het voldoende om alle mogelijke getal- len tussen 0.000 en 1.000 op te slaan. Dat kan je doen door een afpraak te maken over de postities van de bits. Stel dat we de volgende 4 bits nemen: 1111 U en ik maken de afspraak dat het meest linker bit staat voor een 1/2 teken, zijn rechter buurman voor 1/4 zijn buurvrouw voor een 1/8 en haar rechter nicht een 1/16. Op basis van deze afspraak, kunnen we er mee rekenen. Wie niet van de afpraak afweet ziet alleen maar wat nullen en enen staan, zonder de onderligende betekenis te kennen. Tel je die dingen op: 1/2 + 1/4 + 1/8 + 1/16, dan kom je op de waarde 0.9375 uit, iets wat toch al aardig in de buurt van het getal 1 komt. Zou je de reeks uitbreiden (1/32, 1/64 etc) dan nader je oneindig dicht, zoals dat heet het getal 1. Op zich is dit een bruikbare methode om getallen in op te slaan. Maar er kleeft ook een nadeel aan. Bij een getal tussen 0 en 1 is er geen vuiltje aan de lucht, maar wordt het groter of kleiner dan deze, dan staat de komma op een andere plaats en is ook de betekenis van het bitpatroon anders. Hoe dat op te lossen. Normaliseren Hoewel het woord 'normaliseren' bijna een politiek/maatschap- pelijke betekenis zou kunnen hebben, wordt normaliseren binnen de wiskunde gebruikt om een getal binnen een bepaald bereik te brengen. Dit kan bijvoorbeeld door vermenigvuldigen, delen, etc. Gemakshalve noemen we dit het Bias bit. Een '1' geeft aan dat er vermenigvuldigd moet worden, zijn antipode, de '0', geeft aan dat delen de bedoeling is. Bij Atom-floating point getallen is het de bedoeling dat het getal genormaliseerd wordt tussen de waarden 0.5 en 1. Uiteraard moet worden opgeslagen wat er voor nodig was om het getal op deze waarde te krijgen, anders gaan alle getallen wel heel veel op elkaar lijken. Behalve dat, wordt ook opgeslagen of dit resultaat door delen is verkregen, dus als een getal groter was dan 1, of door vermenigvuldigen, in geval het getal kleiner dan 0.5 was. Exponent Het exponent wordt gebruikt om het getal aan te geven wat nodig is bij de vermenigvuldiging of de deling om het getal weer zo te krijgen, zoals de maker het bedoeld heeft. Dit getal is altijd een exponent van 2. Staat er een 3 in de exponent, dan is het de bedoeling dat het getal 2 tot de macht 3 wordt verheven om het gewenste resultaat te krijgen. Dus dat wordt 2 x 2 x 2 = 8. Tevens wordt bij het exponent nog even aangegeven of het negatief getal was dan wel een positief. (=bias bit) En nu aan de slag Een voorbeeldje. Het getal 1.5 zal als volgt worden opgesla- gen: Eerste de handel delen door 2, want het is groter dan 1. Het resultaat is 0.75. In de reeks betekent dat, dat het be- staat uit 1/2 + 1/4 (0.5 + 0.25). De exponent moet aangeven dat de mantissa met 2 moet worden vermenigvuldigd om op het gewenste resultaat te komen. Het grondtal is al 2, dus de exponent zal 1 moeten worden, want 2^1 = 2. Daar we van de Atom al een 1/2 cadeau krijgen, getallen worden immers alleen in de reeks van 1/2 .. 1.0 opgeslagen, ziet dit getal in Atom notatie er als volgt uit: --EXPON-- ----Fractie-------- 1000.0001 0100.000 ---- .0000 Nog even in de herhaling: Voor de exponent Linker bit is 1, want het is een positief getal. Bit 8 is 1, wat staat voor een exponent van 2. (2^1) Voor de fractie: Het linker bit is 0, wat aanduidt dat het getal positief is; een 1 bit wat een 1/4 aangeeft. Neem daarbij de 1/2 die we reeds cadeau kregen en zie daar, daar staat dat 1.5 gelijk is aan 1/2+1/4 * 2. En dat klopt als een zere vinger. Wel even onthouden dat u altijd een 1/2 er bij krijgt voor het zelfde geld. U ziet dat hier dus staat: 2^1 * 0.75 = 1.5 Terug naar het geheel Zoals u ziet is het opslaan van floating point getallen niet echt ingewikkeld. Maar als je er voor het eerst tegen aan- kijkt, lijk het toch allemaal wat onlogisch. Om nog even wat gevoel te krijgen met de opslag van getallen, nog twee voor- beelden. Een groter getal en een getal kleiner dan 1, wat bovendien nog negatief is. 124.75 Dit getal is bepaald groter dan 0.5, dus we moeten net zolang gaan delen door 2, totdat het antwoord kleiner is dan 1.0 en groter dan 0.5 124.75 : 2 = 62.375 62.375 : 2 = 31.1875 31.1875 : 2 = 15.59375 15.59375 : 2 = 7.796875 7.796875 : 2 = 3.8984375 3.8984375 : 2 = 1.94921875 1.949218375 : 2 = 0.974609375 We hebben 7 maal door twee moeten delen om op het resultaat van 0.974609375 uit te komen. 7 wordt dan de exponent van het getal. Het getal 0.974609375 moeten we nog even uitwerken in een breuk en dat is 1/2 + 1/4 + 1/8 + 1/16 + 1/32 + 1/256 + 1/512. In de afgesproken notatie ziet dat er uit als: 111110011. U weet nog dat het meest linker bit staat voor 1/2 etc. Nu nog even afwerken: Het getal is positief, dus het tekenbit wordt '0'. De exponent hadden we bepaald op 7, dus het eerste getal is 1000.0111. Het bias bit staat op vermenigvuldigen, dus wordt '1'. De half kregen we cadeau, dus hoeven wel alleen de andere getallen op te slaan. De mantissa ziet er dan uit als: 0111.1001.1000.0000 en verder 0. -0.046875 Daar dit getal kleiner is dan 0.5 moet er vermenigvuldigd worden totdat dit wel het geval is. Daar gaan we: 2 x 0.046875 = 0.09375 2 x 0.09375 = 0.1875 2 x 0.1875 = 0.375 2 x 0.375 = 0.75 We kunnen dus schrijven -0.046875 = -0.75 2^-4 Voor een exponent met negatieve notatie, moet er gebruik gemaakt worden van het two-complement van het getal. Dat klinkt heel ingewikkeld, maar dat wil zeggen dat het geheel ge‹nverteerd wordt, de nullen worden enen en vice versa, en er wordt vervolgens 1 bij opgeteld. 4 noteert als 0000.0100, dus -4 noteert als 1111.1100. (4 ge‹nverteerd is 1111.1011. Daarbij 1 opgeteld geeft 1111.1100.) Daarbij laten we het hoogste bit vervallen en noteren -4 als 0111.1100. Het hoogste bit gebruiken we immers voor het bias bit. Hieruit volgt dat -0.046875 te schrijven valt als 0111.1100.1100.000---0 De halve hebben we weer cadeau gekregen en de kwart zien we staan. En nu voor de Intel 80x87 De intel heeft een ruimere plaats voor zowel de exponent als de mantissa. Waar de Atom zich tevreden houdt met 7 bits en een bit voor het teken, daar wil de Intel er meer of minder. Evenzo voor het fractionele. De Atom red zich uitstekend met 31 bits, de Intel vergrijpt zich aan 23, 52 of64 bits. Dit betekent dat er wat schuifwerk voor nodig is om het geheel toch in de pas te krijgen en het aanvullen met nullen voor de niet terzakendoende deelgegevens. Van Atom naar Intel verlies je dus niets, van Intel naar Atom moet er even vermanend met het afrondingsstokje worden gezwaaid. Het verschil De x87 kan de getallen op drie manieren opslaan: als real-4, een 8 bits exponent en een 23 bits mantissa als real-8, een 11 bits exponent en een 52 bits mantissa als real-10, een 15 bits exponent en een 64 bits mantissa. In de voorbeelden gaan we uit van een real-4, maar dat is alleen om wat type werk te besparen. Het principe is gelijk, alleen de nauwkeurigheid neemt toe naar mate er meer bits beschikbaar zijn. De IEEE norm voor het opslaan van floating point getallen schijft voor dat het fractionele deel van het getal opgeslagen wordt voor de waarden tussen 1.00 en 2.00. Dat is het dubbele van wat bij de Atom wordt opgeslagen. Een kweste van vermenig- vuldigen of delen door 2 bij het omrekenen dus. De exponent wordt ook op basis van het grondgetal 2 opgesla- gen. Dat is dus net als bij de Atom. Maar omdat de reeks fractionele getallen altijd een factor 2 groter is op de Intel dan de Atom, is de exponent altijd 1 kleiner dan op de Atom. Verder wordt de exponent opgeslagen met een bepaalde basis- waarde, een bias. Bij het getal wordt altijd 127, 1023 of 16383 opgeteld. Op deze manier is het mogelijk om ook eenvou- dig negatieve exponenten op te slaan. Een voorbeeld 0.75 In Intel Notatie is 0.75 als volgt vast te stellen. Eerst het resultaat vermenigvuldigen, omdat het kleiner is dan 1. 0.75 x 2 = 1.5 Dit voldoet aan de reeks van 1.00 - 2.00 Bij de Intel krijgen we de waarde 1.0 cadea, zoals we bij de Atom 0.5 cadea kregen. De waarde zou er dus uitzien als 1.5 * 2^-1 = 0.75 Exp Mantissa 0000.0001 0100.000-----0 (Het exponent is -1. In deze notatie is dat 0111.1111) Bijna waar. Maar toch een klein addertje onder het gras. Deze notatie is anders dan die van de atom. Er wordt een offset van 127 gebruikt en daar wordt de waarde van de exponent bij opgeteld of van afgetrokken. De waarde komt in dit geval uit op 126. Dat kennen we weer als 0111.1110. Het "bias bit" (tussen aanhalingstekens want Intel werkt niet met een bias bit maar een bias-waarde) staat op delen (0 voor Intel). De volgorde van bits is ook iets anders, namelijk eerst het teken, dan de 8, 11 of 15 bits exponent, gevolgd door de mantissa. Dus 0.75 ziet er uiteindelijk uit als: SXXX.XXXX.XMMM.MMMM.----.M 0011.1111.0100.0000.----.0 ~~~~~~~~~~| exp | 1/2 Conclusie Het is niet echt moeilijk te doorgronden hoe de opslag van floating point gegevens in elkaar steekt. Toch is het lastig, maar gelukkig kon ik putten uit een artikel van Bas Kasteel uit een Atom Nieuw uit de tijd dat het woord Laserprinter nog niet bestond en Windows allen door Engelstaligen werden ge- bruikt om door een muur heen te kijken. Leendert