C double

Fra Kommunikation-IT Holstebro HTX
Skift til: navigering, søgning

Double er en type den kan indeholde decimal-tal[1].

Tal-området for float er fra 1.797 * 10308 ned til -1.797 * 10308, men kan også indeholde tal med en opløsning ned til ca. 5 * 10-324, hvilket betyder at tal helt ned til ca. 2.22 * 10-308 med fuld præcision. De lagres som 64 bits (8 bytes) information.

Float har en præcision på ca. 16 betydende cifre, hvilket vil sige at antallet af decimalers nøjagtighed er afhængigt af hvor stort tallet er. Grunden til dette er at tal-delen lagres i 52 bit, og normaliseres ved hjælp af en exponent.

Har man ikke brug for større præcision anvendes en float, der kun har 6-7 betydende cifres præcision, fordi den lagres i 4 bytes.

WARNING - det er ikke alle miljøer der understøtter double - WARNING

  • Arduino erstatter blot double med float, så der er IKKE præcisionen.
  • Processing har double til normale beregninger, men de fleste funktioner og biblioteker (matematik osv.) har IKKE understøttelse af double, og anvender blot floats.
  • Arduino Due har understøttelse af double til beregninger - funktioner og biblioteker er ikke undersøgt.

Lagring af en double

For at forstå hvordan præcisionen fungerer i en double, så er man nødt til at have en forståelse for hvordan tallet lagres. Dette er illustreret på følgende figur:

Illustration af hvordan et 64 bit double tal lagres
Illustration af hvordan et 64 bit double tal lagres[2]

Som det ses på illustrationen af tallet er der 3 dele i tallet.

  • Et Sign - der angiver fortegnet
  • En Exponent - der angiver den faktor der skal ganges på selve tallet
  • En fraction - der er selve tallet, eller i hvert fald det meste af det (det efter kommaet)

Fortegnet giver sig selv - hvis den bit er 1, så er tallet negativt ellers er det positivt. Det betyder at et positivt og det samme negative tal er helt fuldstændigt ens, bortset fra denne bit (det er ikke tilfældet ved heltals-typer som f.x. int).

Exponenten er den 2-tals eksponent der skal ganges på det tal der kommer fra fraction-delen. Hvis eksponenten har eksponent-værdien 3, så skal fraction-delen ganges med 2 i 3. potens, altså 8. Eksponenten er dog ikke lagret direkte som tal-værdien men er 1023 højere end talværdien, da der også skal kunne udtrykkes negative eksponenter, og i talområdet 1 til 2046 giver det eksponenter fra -1022 op til 1023.

I de 11 bit eksponenten indeholder kan der lagres op til tallet 2047, hvilket betyder at tallet er over det float-formatet kan håndtere, betegnet som uendelig eller som infinity (hvilket kan være både positivt og negativt) - i dette tilfælde er fraction 0. Eksponenten kan også indeholde 0, hvilket gør noget specielt ved fraction, som forklares i det følgende.

Fraction-delen er indrettet således at man altid forsøger at normaliserer den, så der er 1 på det mest betydende ciffer (at tallet er i området 1 <= tal < 2), og så justerer exponenten efter det (hver gang fraction bliver bit-skiftet til højre eller venstre, så bliver exponenten en mindre eller større, så tallet har den korrekte værdi). Denne normalisering gør at man ikke behøver at lagre det mest betydende bit, når det alligevel altid er 1, så på den måde har man faktisk 53 bit i tallet, men det er kun de 52 der er lagret i fraction.

Det specielle der sker når tallet bliver for småt til at det kan lagres på normal vis er ved 2-1022 = ca. 2.22 * 10-308, hvor exponenten bliver 0. Det betyder at man dropper det foranstillede 1-tal før fraction, og laver det til 0. Dermed kan tallet blive endnu mindre, altså helt ned til 2-1074 = ca. 5 * 10-324, men dog med faldende præcision, fordi det betydende antal bit bliver mindre end 53.

Forklarende eksempler

For at starte helt simpelt, så bliver tallet 2 lagret med en fraction på 0, hvilket betyder at der faktisk står 1. For at det skal blive til 2, så må exponenten være 1 (21 = 2), og da exponenten har et offset på 1023, så er exponenten 1024. Endelig er tallet positivt, så sign er 0.

2 bliver altså lagret som 0 i sign, 1024 i exponent og 0 i fraction udtrykt binært: 01000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

Lagrer man tallet 5, så skal det først normaliseres, hvilket gør at tallet bliver til 1.25, så den fraction der lagres er 0.25, eller binært 0.01000000. Forholdet mellem 5 og 1.25 er 4, så exponenten bliver 2 (22 = 4), hvilket med offset lagres som 1025, og sign som 0.

5 bliver altså lagret som 0 i sign, 1025 i exponent og 0.25 i fraction udtrykt binært: 01000000 00010100 00000000 00000000 00000000 00000000 00000000 00000000

Tallet -5 lagres ligesom 5, blot med sign som 1, ellers det samme: 1025 i exponent og 0.25 i fraction udtrykt binært: 11000000 00010100 00000000 00000000 00000000 00000000 00000000 00000000

Lagrer man tallet 0,21875, så bliver det normaliseret til 1,75, så den lagrede fraction der lagres er 0,75, eller binært 0.11000000. Forholdet mellem 0,21875 og 1,75 er 1/8, så exponenten bliver -3 (2-3 = 1/8), som lagres som 1020, og sign er 0.

0.21875 bliver altså lagret som 0 i sign, 1020 i exponent og 0.75 i fraction udtrykt binært: 00111111 11001100 00000000 00000000 00000000 00000000 00000000 00000000

Tallet 3*1025 = 30.000.000.000.000.000.000.000.000 har 0 som sign, og tallet bliver normaliseret til ca. 1,5509636485, så den lagrede fraction bliver 0,5509636485, eller binært 0.1000110100001011111101000010001111000000001111011001. Exponenten bliver til 84, der lagres som 1107.

30.000.000.000.000.000.000.000.000 bliver altså lagret som 0 i sign, 1107 i exponent og 0.5509636485 i fraction udtrykt binært: 01000101 00111000 11010000 10111111 01000010 00111100 00000011 11011001

Eksemplerne er opsummeret i tabelform herunder:

Tal Sign Exponent Exponent lagret fraction Binært format
2 0 1 1024 0 01000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
5 0 2 1025 0.25 01000000 00010100 00000000 00000000 00000000 00000000 00000000 00000000
-5 1 2 1025 0.25 11000000 00010100 00000000 00000000 00000000 00000000 00000000 00000000
0,21875 0 -3 1020 0.75 00111111 11001100 00000000 00000000 00000000 00000000 00000000 00000000
3*1025 0 84 1107 0.5509636485 01000101 00111000 11010000 10111111 01000010 00111100 00000011 11011001

Problemer med præcision

Ja - det kan diskuteres om der er problemer med præcision, men som alt andet i computerverdenen, så er der ikke fuldstændig præcision, men der er noget bedre end med en float.

Da fraction bliver gemt i 52 bit, så vil dette give en begrænsning i den præcision tallet kan give.

Som en del af præcisionen, så indgår det implicitte 1-tal før fraction-delen, så den reelle opløsning på tallet er 53 bit, der kan gengive tal fra 0 til 9.007.199.254.740.991.

Hvis det drejer sig om heltal, så betyder det at alle heltal kan gengives korrekt fra 0 til 9.007.199.254.740.991 uden problemer, men når tallet 9.007.199.254.740.993 forsøges lagret, så bliver det afrundet til 9.007.199.254.740.992, fordi tallets størrelse forsøgt bevaret, men den sidste bit der skulle angive tallet skulle ende på 93 er der ikke plads til, så det bliver til 92.

På tilsvarende måde vil decimaler blive begrænset, alt efter tallets størrelse. Hvis tallet fx. er under 2.251.799.813.685.248, der er 1/4 af 9.007.199.254.740.992, så vil der være 2 bit til overs til decimaler, så opløsningen på tal op til 2.251.799.813.685.248 vil have en opløsning på 0,25. Dette betyder at tallene 2.251.799.813.685.247,0 2.251.799.813.685.247,25 2.251.799.813.685.247,5 og 2.251.799.813.685.247,75 vil kunne lagres mens der bliver 0,5 mellem tallene fra 2.251.799.813.685.248,0, så det næste tal der kan lagres er 2.251.799.813.685.248,5 og 2.251.799.813.685.249,0.

Programkode der efterviser double-formatet

For at eftervise at tallene faktisk lagres i hukommelsen på den måde, så laves der et lille test-program, hvor princippet er at man placerer en double-variabel oven på et byte-array, så man kan gemme variablen som double, og så trække bytes ud og analysere dem som enkelt bits.

Programmet er lavet til Arduino Due, da der er adgang til en let måde at lægge variabler oven på hinanden. Processing er ikke så fleksibel, da den er baseret på java, der ikke er glad for at lave den slags strukturer.
Der er også en version hvor indholdet hentes via pointere, for at illustrere den teknik.

Koden til begge versioner ligger i denne ZIP-fil med Arduino kode, og kræver selvfølgelig en Arduino Due for at afvikle programmet.

Programmet viser resultaterne i Serial Monitor, hvor man kan indtaste tallene i linjen for oven, hvor arduinoen læser det der skrives ind, og udskriver en tolkning af tallet.

De eksempler der er vist før få følgende udskrift:

Visning af tolkninger at double-tal
Visning af tolkninger at double-tal

Referencer

  1. Arduino om double
  2. Billede fra wikipedias forklaring af double