C float

Fra Holstebro HTX Wiki
(Omdirigeret fra Float)
Skift til: navigering, søgning

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

Tal-området for float er fra 3.4028235E+38 ned til -3.4028235E+38, men kan også indeholde tal med en opløsning ned til 1.0E-49, hvilket betyder at tal helt ned til 1.18E-38 med fuld præcision. De lagres som 32 bits (4 bytes) information.

Float har en præcision på 6-7 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 23 bit, og normaliseres ved hjælp af en exponent.

Har man brug for større præcision anvendes en double, der har op til 15 betydende cifres præcision, fordi den lagres i 8 bytes.

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

  • 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 float

For at forstå hvordan præcisionen fungerer i en float, 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 32 bit float tal lagres
Illustration af hvordan et 32 bit float 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 127 højere end talværdien, da der også skal kunne udtrykkes negative eksponenter, og i talområdet 1 til 254 giver det eksponenter fra -126 op til 127.

I de 8 bit eksponenten indeholder kan der lagres op til tallet 255, 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 24 bit i tallet, men det er kun de 23 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-126 = ca. 1.18 * 10-38, 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-149 = ca. 10-49, men dog med faldende præcision, fordi det betydende antal bit bliver mindre end 24.

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å 127, så er exponenten 128. Endelig er tallet positivt, så sign er 0.

2 bliver altså lagret som 0 i sign, 128 i exponent og 0 i fraction udtrykt binært: 01000000 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 129, og sign som 0.

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

Tallet -5 lagres ligesom 5, blot med sign som 1, ellers det samme: 129 i exponent og 0.25 i fraction udtrykt binært: 11000000 10100000 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 124, og sign er 0.

0.21875 bliver altså lagret som 0 i sign, 124 i exponent og 0.75 i fraction udtrykt binært: 00111110 01100000 00000000 00000000

Tallet 3*1014 = 300.000.000.000.000 har 0 som sign, og tallet bliver normaliseret til ca. 1,065814, så den lagrede fraction bliver 0,065814, eller binært 0.00010000110110010011001. Exponenten bliver til 48, der lagres som 175.

300.000.000.000.000 bliver altså lagret som 0 i sign, 175 i exponent og 0.065814 i fraction udtrykt binært: 01010111 10001000 01101100 10011001

Eksemplerne er opsummeret i tabelform herunder:

Tal Sign Exponent Exponent lagret fraction Binært format
2 0 1 128 0 01000000 00000000 00000000 00000000
5 0 2 129 0.25 01000000 10100000 00000000 00000000
-5 1 2 129 0.25 11000000 10100000 00000000 00000000
0,21875 0 -3 124 0.75 00111110 01100000 00000000 00000000
300.000.000.000.000 0 48 175 0.065814 01010111 10001000 01101100 10011001

Problemer med præcision

Da fraction bliver gemt i 23 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 24 bit, der kan gengive tal fra 0 til 16.777.215.

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

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

Når tallene bliver mindre, så vokser den præcision der kan opnås i decimalerne, da tallets præcision er betinget af de 24 bit der angiver tallets værdi. Hvis fx. tallet er mellem 128 og 256, så vil denne del optage de 8 bit, så til resten af tallet er der 16 bit til decimalerne. Det giver en opløsning på 2-16 = 0,000015258789.
Det betyder altså at tallene der kan lagres lige omkring 255 er 255.000000, 255.000015, 255.000030, 255.000045, 255.000060, 255.000076 osv. hvor det er de sidste bit i fraction-delen der bestemmer de sidste decimaler.

Programkode der efterviser float-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 float-variabel oven på et byte-array, så man kan gemme variablen som float, og så trække bytes ud og analysere dem som enkelt bits.

Programmet er lavet til Arduino, 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.

Koden ligger i denne ZIP-fil med Arduino kode, og kræver selvfølgelig en Arduino 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 float-tal
Visning af tolkninger at float-tal

Referencer