Onderwerpen Inleiding Algemeen 12 Getallen Getallen Representaties Rekenen Problemen Piet van Oostrum 12 dec 2001 INL/Alg-12 1 X INL/Alg-12 1 X Getallen Soorten getallen Wat is een getal? Experiment: met Python 6.3 6.2999999999999998 0.1*3 0.30000000000000004 Java: net iets anders Wat is hier aan de hand? Een getal is een abstract wiskundig begrip Een abstractie van het begrip hoeveelheid Wat wij zien zijn representaties van getallen: 123 is de decimale representatie van een getal CXXIII is de romeinse representatie van hetzelfde getal Natuurlijke getallen: 0, 1, 2,... Gehele getallen:..., -2, -1, 0, 1, 2,... Rationale getallen (breuken): m/n, m, n geheel Worden in computers niet vaak gebruikt: m, n worden snel erg groot Reële getallen: Hiervan zijn er heel veel: ook niet-berekenbare In computers beperken we ons tot een kleine deelverzameling hiervan Daarom meestal benaderingen Complexe getallen: x + y i, (x, y reëel) worden veel in de natuurkunde en soortgelijke takken van wetenschap gebruikt Quaternionen: generalisatie van complexe: x + y i + z j + u k, Octavionen (octonionen) met 8 reële componenten INL/Alg-12 2 X INL/Alg-12 3 X Natuurlijke getallen Voorbeelden We representeren natuurlijke getallen meestal met een positioneel systeem: We kiezen een grondtal g (vaak g = 10) Elk cijfer heeft een g maal zo grote waarde als hetzelfde cijfer een plaats rechts ervan Bijv. 237 = 2*100+3*10+7*1 (g = 10) De waarde van d n 1 d n 2... d 1 d 0 is n 1 d i g i Voor n cijfers zijn de mogelijke waarden 0 t/m g n 1 i=0 Als het grondtal niet uit de context duidelijk is schrijven we dddddd g decimaal: algemeen bekend binair (g = 2) wordt meestal in computers gebruikt 1011 2 = 11 10 Meestal n = 32, soms n = 16, 64 octaal (g = 8) makkelijk vanuit binair: neem telkens 3 bits samen hexadecimaal (g = 16) ook makkelijk vanuit binair: neem telkens 4 bits samen een byte = 2 hexadecimale cijfers voor de cijfers 10 t/m 15 nemen we A-F 64 10 = 100 8 64 16 = 100 10 INL/Alg-12 4 X INL/Alg-12 5 X slides12.pdf December 14, 2001 1
Optellen van binaire getallen Optelschakeling Optellen gaat net als op de basisschool: van rechts naar links de cijfers optellen en evt. onthouden en doorgeven : xi XOR XOR voorbeeld: 1011 2 + 101 2 1001 101 1110 1 onthouden yi c i AND AND OR Optellingstabel: a b a+b 0 0 00 0 1 01 1 0 01 1 1 10 Formules: rechtse cijfer = axorb = (a b (a b)) = (a b) ( a b) linkse cijfers = a b; dit cijfer moet links erbij geteld worden (carry), Bij alle behalve de meest rechtse moeten 3 bits opgeteld worden INL/Alg-12 6 X INL/Alg-12 7 X Negatieve getallen 1 Negatieve getallen 2 We beperken ons voorlopig tot binaire representaties Als we ook negatieve getallen willen representeren moeten we ca. de helft van onze representaties daarvoor reserveren Probleem: 0 is niet positief en niet negatief (+0 = 0), dus één van de twee heeft een getal teveel, of we krijgen twee representaties voor 0 (zowel +0 als 0) Representatie: teken + absolute waarde reserveer één bit voor het teken (bijv. 0 = +, 1 = ) de rest van de bits voor de absolute waarde van het getal Probleem: electronische schakelingen voor optellen en aftrekken worden ingewikkeld INL/Alg-12 8 X Representatie: 1-complement een negatief getal wordt voorgesteld door alle bits van het positieve getal omgekeerd vb: 11 10 = 1011 2 11 10 =... 11110100 2 Probleem 1: Er zijn twee representaties voor 0: +0 en 0 Probleem 2: Bij het optellen moet de carry van de linkse bits rechts er weer bijgeteld worden (end-around carry) Dit kan problemen met de timing geven. Representatie: 2-complement Dit is momenteel het meest gebruikte systeem de representatie van een negatief getal krijg je door 1 op te tellen bij de 1-complement representatie dus 1 =... 111111 2, 2 =... 111110 2, etc. Er is nu slechts één 0 Optellen gaat precies zoals voor positieve getallen! Probleem: er is één negatief getal meer dan positieve! 2 n Dit getal heeft geen absolute waarde INL/Alg-12 9 X Overflow Conversie van representatie naar getal Als een resultaat van een operatie (optellen, aftrekken, vermenigvuldigen) niet meer past in de hoeveelheid gereserveerd bits noemen we dit overflow Bij optellen ontdek je dit doordat je twee getallen met hetzelfde teken optelt en het resultaat heeft het andere teken Als je een positief en een negatief getal optelt is er nooit overflow Bij overflow is het resultaat 2 n te groot of te klein static int tonumber (String in, int g) { int result = 0; int aantal = in.length(); for (int i = 0; i < aantal; i++) { int digit; char c = in.charat(i); if (c >= A && c <= Z ) digit = c - A +10; else if (c >= a && c <= z ) digit = c - a +10; else digit = c - 0 ; result = result*g+digit; return result; INL/Alg-12 10 X INL/Alg-12 11 X slides12.pdf December 14, 2001 2
Conversie naar representatie Resultaten static String tostring(int in, int g) { String result = ""; boolean neg = false; if (in<0) { neg = true; in = -in; do {int digit = in % g; in = in/g; if (digit < 10) result = (char)(digit+ 0 ) + result; else result = (char)(digit-10+ A ) + result; while (in!= 0); if (neg) result = - + result; return result; INL/Alg-12 12 X System.out.println (tonumber("34567", 10)); System.out.println (tonumber("64", 16)); System.out.println (tostring(100, 16)); System.out.println (tostring(-123456, 10)); System.out.println (tostring(0, 23)); geeft als resultaat: 34567 100 64-123456 0 INL/Alg-12 13 X Decimale representaties 1 Decimale representaties 2 Voor sommige toepassingen (zakelijke software) is een decimale representatie soms efficienter: Er wordt vaak weinig gerekend (btw uitrekenen e.d) Wel veel in- en uitvoer De conversies kosten dan relatief veel tijd In dat geval wordt vaak een decimale representatie in de computer gebruikt Omdat de decimale cijfers toch weer binair opgeslagen moeten worden spreken we dan van Binary-Coded Decimal (BCD) Er zijn dan nog verschillende keuzes mogelijk: Sla gewoon de ASCII tekens van de cijfers op Sla alleen de getalswaarde van de cijfers op, één per byte Stop twee cijfers in één byte (packed decimal) INL/Alg-12 14 X ASCII getal = 259 2 0 0 1 1 0 0 1 0 5 0 0 1 1 0 1 0 1 9 0 0 1 1 1 0 0 1 alleen cijfers 2 0 0 0 0 0 0 1 0 5 0 0 0 0 0 1 0 1 9 0 0 0 0 1 0 0 1 packed decimal 0 2 0 0 0 0 0 0 1 0 5 9 0 1 0 1 1 0 0 1 De halve bytes worden ook wel nibbles genoemd. De eerste nibble wordt vaak gebruikt om het teken aan te geven. Voor + en worden dan speciale bitpatronen gebruikt. INL/Alg-12 15 X Scaled Numbers 1 Scaled Numbers 2 Voor niet-gehele getallen zijn er verschillende mogelijkheden Net als bij decimale getallen kunnen we binair getallen met een punt gebruiken: 3.25 10 = 11.01 2 In de computer slaan de we de punt niet op. Neem bijv 16 bits voor het gehele deel en 16 bits voor de breuk: 0000000000000011 0100000000000000 Als we dit samen in een 32-bits woord zetten hebben we eigenlijk een int die 2 16 maal zo groot is als het bedoelde getal. Optellen en aftrekken blijven hetzelfde: 2 16 x + 2 16 y = 2 16 (x + y) Bij vermenigvuldigen en delen moeten we echter een factor 2 16 corrigeren: 2 16 x 2 16 y = 2 16 (2 16 (x y)) 2 16 x/2 16 y = (2 16 (x/y))/2 16 Niet elke decimale breuk is binair weer te geven bijv. In plaats van de punt in het midden te zetten kunnen we die ook ergens anders zetten (denken) Omdat we getallen representeren als ints die een vaste factor (2 k ) groter zijn spreken we van scaled numbers of scaled arithmetic We kunnen zelfs een compleet andere factor nemen (niet 2 k ) Voorbeeld: Als we met geldbedragen (euro s) rekenen kunnen we een factor 100 nemen; en in feite met eurocenten rekenen. Als we nauwkeurig met lengtes moeten rekenen kunnen we een factor 100000 nemen (feitelijk met 1/100 mm rekenen). Probleem: Als we in een berekening zowel heel grote als heel kleine getallen nodig hebben moeten we met verschillende schaalfactoren werken 0.1 10 = 0.0011001100110011... 2 INL/Alg-12 17 X INL/Alg-12 16 X slides12.pdf December 14, 2001 3
Floating point Problemen met floating point Floating point lost dit probleem op door de schaalfactor in de representatie te coderen Een getal wordt voorgesteld door een mantisse en een exponent (bij een bepaald grondtal). Voorbeeld: als het grondtal 10 is, de mantisse is 5 en de exponent is 3 dan is de waarde van het getal 5 10 3 Algemeen als het grondtal g is, de mantisse m en de exponent e, dan is de waarde van het getal m g e Rekenen wordt nu een stuk moeilijker: Bijv bij een optelling of aftrekking moeten de exponenten eerst gelijk gemaakt worden: 5 10 3 + 7 10 5 = 5 10 3 + 700 10 3 = 705 10 3 INL/Alg-12 18 X Als we een binaire representatie gebruiken (g = 2) Niet elk getal kan gerepresenteerd worden De meeste getallen moeten benaderd worden Bijv. 0.1 wordt benaderd door 0.10000000000000001 en 0.3 door 0.29999999999999999 3 0.1 0.3 = 5.5511151231257827e 17 Operaties geven ook een benaderd resultaat Soms verdwijnt een deel gewoon: >>> x=2.0 >>> y=1e-20 >>> z=x+y >>> z 2.0 >>> z-x 0.0 INL/Alg-12 19 X Floating point representaties Normalisatie Tegenwoordig wordt bijna altijd de IEEE 754 representatie gebruikt Dit is een binaire representatie (g = 2) Deze is ontworpen om zoveel mogelijk problemen met het rekenen met floating point getallen te vermijden Hiervoor hadden veel computers rariteiten (bijv. bij heel kleine getallen) Twee vormen in gebruik: single precision (32 bit, float): 1 bit teken, 8 bits exponent, 23 bits mantisse double precision (64 bit, double): 1 bit teken, 11 bits exponent, 52 bits mantisse Daarnaast nog extended met extra mantissebits De meeste computers nu hebben dit ingebouwd in de CPU Een getal kan verschillende floating point representaties hebben: 5 10 3 = 50 10 2 = 500 10 1 = 0.05 10 5 Welke kiezen we in de computer? In het algemeen wordt een genormaliseerde representatie genomen, waarbij er 1 cijfer voor de punt staat in de mantisse: Dus 5 10 3 in het voorbeeld Een andere goede keus zou zijn om altijd direct achter de punt te beginnen met een cijfer 0 (behalve bij 0 zelf): Dus 0.5 10 4 INL/Alg-12 20 X INL/Alg-12 21 X Binaire normalisatie Binaire representatie In een binaire genormaliseerde representatie is het cijfer voor de punt altijd 1 (behalve bij het getal 0). Je kunt dan ruimte besparen door deze niet op te slaan maar erbij te denken Neem 0.375 10 0.375 10 = 0.25 10 + 0.125 10 = 0.0100 2 + 0.0010 2 = 0.0110 2 Genormaliseerd wordt dit 1.1000 2 2 De 1 voor de punt slaan we niet op We slaan dus op: (als we 4 bits voor de mantisse gebruiken) mantisse = 1000 en exponent = 2 Om negatieve exponenten ook goede ruimte te geven wordt de exponent meestal omhooggeschoven zodat de negatieve exponenten een lage representatie hebben en positieve exponenten een hoge Dit wordt de bias genoemd Als we 3 bits voor de exponent zouden gebruiken kunnen we de exponent bijv 4 opschuiven: exponent 2 wordt dan opgeslagen als 2 + 4 = 2. Als we ook nog een bit voor het teken ervoor zetten (0 = +, 1 = ) wordt ons getal: 0 0 1 0 1 0 0 0 teken exp mant INL/Alg-12 22 X INL/Alg-12 23 X slides12.pdf December 14, 2001 4
Binaire representatie IEEE representatie Let op: het getal 0 kan op deze wijze niet weergegeven worden We zouden ervoor kunnen kiezen om de representatie met alleen 0 bits als het getal 0 te interpreteren (in plaats van als 1 2 4 = 1/16 = 0.0625) Single precision: teken: 1 bit, exponent: 8 bits (bias 127), mantisse 23 bits Double precision: teken: 1 bit, exponent: 11 bits (bias 1023), mantisse 52 bits exponenten met allen nullen of alleen enen (hoogste en laagste waarde) worden voor speciale doelen gebruikt (bijv. oneindig) 0 wordt voorgesteld door allemaal nullen Getallen met een zeer kleine absolute waarde worden niet genormaliseerd (anders zouden ze niet passen): gedenormaliseerde getallen INL/Alg-12 24 X INL/Alg-12 25 X Floating Point Operaties Afronden Bij operaties op floating point getallen (optellen, vermenigvuldigen etc.) is het vaak niet mogelijk om het resultaat exact weer te geven In dat geval moet een benadering gegeven worden Meestal wordt het getal genomen dacht het dichtst bij het echte resultaat ligt De IEEE standaard schrijft een aantal rounding modes voor waaruit het programma kan kiezen Afronden naar het dichtstbijzijnde te representeren getal Afronden naar boven (in de richting van + ) Afronden naar beneden (in de richting van ) Afronden naar 0 toe Afronden gaat altijd uit van het theoretische (wiskundige) resultaat Bij het afronden naar dichtsbijzijnde is de fout maximaal de helft van de waarde van het meest rechtse cijfer in de mantisse Bij de andere afrondingen maximaal de waarde van dit cijfer (ULP = Units in the Last Place) Als het grondtal g is, de exponent e en de mantisse heeft n bits achter de punt dan is de ULP = g n g e Voorbeeld: Als g = 10, n = 2 en we ronden naar beneden af dan worden de echte resultaten tussen 2.340000000000000... en 2.349999999999999.... De fout is dan maximaal 0.01. Als er een exponent is dan moet nog de factor g e toegepast worden Bij afronden naar dichtstbijzijnde is de maximale fout 0.005. INL/Alg-12 26 X INL/Alg-12 27 X Voorbeelden van Operaties Zonder extra cijfers In deze voorbeelden gaan we uit van grondtal 10, mantisse van 3 cijfers Bijvoorbeeld π = 3.141592... wordt afgerond op 3.14 Stel x = 2.15 10 12, y = 1.25 10 5. We willen x y uitrekenen: We gaan eerst het theoretische resultaat uitrekenen We moeten de exponenten gelijk maken: Afgerond x y = 2.15 10 12 x = 2.15 10 12 y = 0.0000000000000000125 10 12 x y = 2.1499999999999999875 10 12 We hebben al die cijfer voor niets uitgerekend Bovendien kan het aantal extra cijfers zeer groot worden Dit zou exorbitant veel hardware kosten INL/Alg-12 28 X Kunnen we de berekening doen zonder extra cijfers? Als we de extra cijfers gewoon weggooien: x = 2.15 10 12 y = 0.00 10 12 x y = 2.15 10 12 Echter zie het volgende voorbeeld: x = 10.1, y = 9.93 x = 1.01 10 1 y = 0.99 10 1 x y = 0.02 10 1 Het echte resultaat is echter 0.17 i.p.v. 0.2. De fout is 30 ulps!! INL/Alg-12 29 X slides12.pdf December 14, 2001 5
Guard digit Overflow/underflow Met één cijfer extra kunnen we het veel beter doen: x = 1.010 10 1 y = 0.993 10 1 x y = 0.017 10 1 Maar ook hier gaat het soms fout, bijv. bij 110 8.59: x = 1.100 10 2 y = 0.085 10 2 x y = 1.015 10 2 Het echte resultaat is 1.0141 10 2 en zou dus afgerond moeten worden op 1.014 10 2. Er kan bewezen worden dat met één guard digit de fout in het antwoord nooit meer is dan de waarde van het rechtse cijfer (1 ulp) Overflow treedt op als de absolute waarde van het resultaat groter is dan het grootste te representeren getal Meestal wordt dan een exception gegenereerd of de waarde ± als resultaat genomen Underflow treedt op als de waarde van het resultaat kleiner is dan het kleinste te representeren positieve getal Meestal wordt dan een exception gegenereerd of 0 als waarde genomen Bij delen door 0 wordt wordt ± als waarde opgeleverd behalve bij 0/0, dit lever de speciale waarde NaN (Not a Number). INL/Alg-12 30 X INL/Alg-12 31 X Vertalen van decimale representatie Vertalen van decimale representatie We gaan nu weer uit van binaire representatie in het geheugen Extern representeren we getallen als dddd.ddddeddd evt aangevuld met tekens en het exponent gedeelte optioneel We hebben gezien dat niet iedere decimale representatie exact binair weergegeven kan worden (bijv. 0.1) Bij het omzetten moet idealiter het dichtsbijzijnde floating point getal gekozen worden Bij het omzetten moeten diverse operaties uitgevoerd worden: Mantisse omzetten Plaats van de punt verdisconteren Exponent meerekenen Bij al deze operaties kunnen afrondfouten optreden (i.h.b bij het berekenen van 10 e ) De totale fout kan te groot worden Het is beter om de berekening te doen in een grotere precisie (extended) Of eerst als long integers te berekenen De waarden 10 e kunnen ook met tabellen gedaan worden Zie literatuur op het Internet ( How to read floating point numbers accurately ) INL/Alg-12 32 X INL/Alg-12 33 X Vertaling naar decimale representatie Conclusie Eerst vermenigvuldigen met geschikte factor 10 k om de punt op de juiste plaats te krijgen De mantisse kan omgezet worden als een (long) integer met de punt op de juiste plaats ingevoegd Hier ook problemen met nauwkeurigheid Extra probleem: hoeveel cijfers moeten we afdrukken in de mantisse? Als we te weinig cijfers afdrukken dan is het resultaat niet echt nauwkeurig Als we veel cijfers afdrukken dan krijgen we artefacten als 0.10000000000000001 Minder cijfers afdrukken lost het probleem niet op maar verdoezelt het!! Idealiter wil je die representatie afdrukken die bij (correct) inlezen weer exact hetzelfde getal oplevert Dit is vrij tricky Zie de literatuur op het Internet ( How to print floating point numbers accurately ) INL/Alg-12 34 X Voor sommige toepassingen moet je wel met floating point getallen werken Je kunt onverwachte resultaten krijgen vanwege de afrondfouten Voor sommige toepassingen is het niet geschikt (bijv. rekenen met geldbedragen) Je moet geen floating point getallen gebruiken als je niet weet wat de problemen zijn. Voor sommige toepassing zou een decimaal floating point systeem beter zijn. INL/Alg-12 35 X slides12.pdf December 14, 2001 6