Float omzetten in meerdere bytes

Zoals de titel verteld probeer ik een float variabele om te zetten in meerdere bytes. Ik heb het volgende geprobeerd, ik heb het wel voor elkaar gekregen om de getallen voor de komma/punt te splitsen in losse bytes, dit met iets wat op onderstaande code lijkt. Alleen het getal achter de komma/punt levert problemen op als ik er iets mee wil doen.

c code:


float myFloat = 15.7;
byte tienen;
byte enen;
byte puntEen;
byte temp;

temp = byte(myFloat); //temp = 15
tienen = temp % 10; //tienen = 1
enen = temp / 10; //enen = 5

Ps: ik gebruik de Arduino IDE om te programmeren.

Bezoek mijn site! | Geen project is compleet zonder 4000 series ICs!

Als je echt in tientallen, eenheden en tienden / honderdsten wil werken kun je natuurlijk het gehele getal aftrekken van je input en vervolgens vemenigvuldigen met bijvoorbeeld 100.
Voorbeeld 15.23
15 aftrekken -> 0.23
Vermenigvuldigen met 100 => 23

Als het er om gaat je waarde op een display te tonen kun je string formatting gebruiken.
https://playground.arduino.cc/Main/Printf
https://www.arduino.cc/en/Reference.StringConstructor

[Bericht gewijzigd door mbbneon op dinsdag 6 februari 2018 12:37:37 (30%)

Je hebt de 10en en 1en verwisseld

100en = (x%1000) / 100
10en = (x%100) / 10
1en = x % 10
1e dec = (x×10) % 10
2e dec is (x×100) % 10

[Bericht gewijzigd door hardbass op dinsdag 6 februari 2018 12:50:34 (12%)

PE2BAS
EricP

mét CE

Probeer die hele float kwijt te raken. Die dingen zijn hoe dan ook horken om mee te werken.
Ofwel: waarom heb je die float nodig en kun je niet met wat fatsoenlijks werken? Ik kan me zo voorstellen dat je bijvoorbeeld de temperatuur niet in graden hebt maar in centigraden. Dan past het opeens in een uint16...

big_fat_mama

Zie Paulinha_B

Dat is toch C=code? Daar heb je toch een functie atof() ? Of zit die in een library die niet beschikbaar is? Het heruitvinden van het wiel kan misschien "lering ende vermaak" brengen, veel meer nut is er zeker niet aan!

Edit: de opmerking van Eric hier net boven is erg goed. Zelf heb ik heb meegemaakt dat een geografische applicatie mooi werkte op een netbook, maar overgebracht naar een Raspberry kreeg ze niet genoeg cpu-power ter beschikking. Oplossing: de coordinaten (die bepaald waren als graden met decimalen, in floats) omzetten naar seconden, daarmee pasten ze in een long integer. Snelheidwinst +/- maal drie.

[Bericht gewijzigd door big_fat_mama op dinsdag 6 februari 2018 12:54:50 (48%)

hoe beter de vraag geschreven, zoveel te meer kans op goed antwoord

Atoi zit in string.h denk ik. Ff googlen.

Ik weet niet wat ts zn uiteindelijke doel is.

Inderdaad, arduinos zijn vele male rapper met integers dan met floats. Dus als dat kan zeker doen.
Je declareerd je float ook fout.
Het moet zo zijn:

c code:


float x = 15.7f;

[Bericht gewijzigd door hardbass op dinsdag 6 februari 2018 13:01:28 (52%)

PE2BAS

@mbbneon ik wil het op een display laten zien, alleen werkt mijn multiplexing code met een byte voor elke digit.

@hardbass dat kan kloppen, ik heb dit snel op m'n tablet getypt.

@EricP de library voor de ds18b20 geeft mij een float als resultaat.

Bezoek mijn site! | Geen project is compleet zonder 4000 series ICs!

@EricP: In de arduino wereld wordt er veel "gezondigd" tegen dat soort regels die de wat oudere microcontroller programmeurs "raar" vinden om het "zo" te doen.

Het kan dus goed zijn dat er een arduino library is die "makkelijk in het gebruik" een float temperatuur terug geeft van je DS18B20 sensor.

(EricP: [edit: En BFM] even de andere kant op kijken!) :-)

code:


 char buf[10];
 sprintf (buf, "%05.2f", temp);
 tienen      = buf[0] - '0';
 enen        = buf[1] - '0';
 tienden     = buf[3] - '0';
 honderdsten = buf[4] - '0';

[edit: foutje met de indices gefixed. Sorry te snel geweest.]

ALS ik uit een library een float zou krijgen en een hekel heb aan floats zoals EricP (*).... dan:

code:


 int temp_cg; // temp in centigraden: honderdsten van een graad. 

 temp_cg = (int) (gettempfloat () *100);

en dan:

code:


 tienen = temp_cg/10; 
 .. enz. 
four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/
EricP

mét CE

Inderdaad, arduinos zijn vele male rapper met integers dan met floats. Dus als dat kan zeker doen.

Niet alleen Arduino spul. Zo'n beetje alles wat niet in hardware met floats overweg kan...

@EricP: In de arduino wereld wordt er veel "gezondigd" tegen dat soort regels die de wat oudere microcontroller programmeurs "raar" vinden om het "zo" te doen.

Het heeft niks te maken met het 'raar' vinden om het zo te doen. Het slaat helemaal nergens op in de context van dat die controller er vanalles voor moet gaan emuleren, zo je wilt een stuk 'runtime library' nodig heeft. Het is qua performance een bagger oplossing. Het is qua gebruik van memory een bagger oplossing. En het is qua nauwkeurigheid een bagger oplossing - al vaak zat meegemaakt dat het optellen van een stapel floats niet het gewenste resultaat oplevert omdat de runtime library die voor de binaire verwerking zorgt ergens afrondfouten maakt - wat ook logisch is. Ofwel: floats gebruik je alleen als ze niet gebruiken een grotere berg bagger oplevert dan ze wel gebruiken...

In dit geval: een andere library zoeken dus :+. Zo spannend is 1Wire nou ook weer niet. (en eh... dit is dus precies waarom ik nooit wat met Arduino's gedaan heb - de kwaliteit van de libraries is gokken, dus je weet nooit wat je gebruikt... :(

(EricP: even de andere kant op kijken!) :-)

Inderdaad. sprintf is de volgende die je niet wilt gebruiken. Erg handig, dat wel. Maar vreselijk DUUR.

En eh... rew... heb je er over nagedacht waar je decimal point in die character array staat? Volgens mij tussen tienden en enen in. Toch :+ .

big_fat_mama

Zie Paulinha_B

@rew: noteer aub dat er nog meer deelnemers zijn met een delicate maag... :) Ik onderschrijf de kritiek van EricP voor de volle 100%!

hoe beter de vraag geschreven, zoveel te meer kans op goed antwoord
High met Henk

Special Member

Ik probeer rew te volgen, maar volgens mij staat er in buf toch echt:
Buf[0] = tienen
Buf[1] = enen
Buf[2] = ","
Buf[3] = tienden
Buf[4] = honderdsten

E = MC^2, dus de magnetische compatibiliteit doet kwadratisch mee???
Arco

Special Member

Inderdaad 'systeemvreemde' variabelen als floats zoveel mogelijk vermijden. Kosten veel geheugen en tijd... ;)
Beter opdelen in 2 words: met het deel voor en achter de komma. Dat rekent ook veel sneller.
Bij variabelen hangt het van de processor af: bij 8 bits:bytes, bij 16 bits:words, en bij 32 bits:dwords gebruiken. (indien mogelijk)

Arco - "Simplicity is a prerequisite for reliability" - hard-, firm-, en software ontwikkeling: www.arcovox.com

Ik heb nu dit geprobeerd, het resultaat is dat het niet helemaal werkt.

Op 6 februari 2018 12:35:41 schreef mbbneon:
Als je echt in tientallen, eenheden en tienden / honderdsten wil werken kun je natuurlijk het gehele getal aftrekken van je input en vervolgens vemenigvuldigen met bijvoorbeeld 100.
Voorbeeld 15.23
15 aftrekken -> 0.23
Vermenigvuldigen met 100 => 23

Dit is de code:

c code:


temp = byte(celsius);
tienen = temp / 10;
enen = temp % 10;
temp = celsius - temp * 100;
decEen = temp / 100;

Dit is de output van de arduino serial monitor:

code:


Temperature = 22.94 Celsius
 
2 2,1 *C

@Arco in mijn geval zou het 8 bit zijn.

Bezoek mijn site! | Geen project is compleet zonder 4000 series ICs!
EricP

mét CE

Nogmaals, gooi die library weg en zoek wat fatsoenlijks. Je moddert jezelf steeds verder het moeras in.

Als je van moerassen houdt, dan kun je nog zoiets als dit doen:

c code:

uint8_t hundreds=0;
uint8_t tens=0;
uint8_t units=0;
uint8_t tenths=0;
uint8_t hundredths=0;

while (temp > 100)
{
  hundreds++;
  temp=temp-100;
}

while (temp > 10)
{
  tens++;
  temp=temp-10;
}

while (temp>1)
{
  units++;
  //temp--; //Geen idee of dat met floats goed gaat. Zoals gezegd: het zijn waardeloze dingen!
  temp=temp-1;
}

while (temp>0.1)
{
  tenths++;
  temp=temp-0.1;
}

while (temp>0.01)
{
  hundredths++;
  temp=temp-0.01;
}

Baggeroplossing? Yep. Maar het hele gedoe met floats is hoe dan ook een K*T oplossing, dus ach... of het schip nou hier strandt of 1 zandbank verder...
Elegant? Mwah... voor kleine getallen lukt het nog wel.
Snel? Op zich wel, als het met integers zou werken - met floats blijf je strompelen met een runtime library
Werkt het? Als er geen denk of typefout in zit wel. Het concept werkt in elk geval prima :).

Arco

Special Member

Ik vind kant en klare libraries (vaak ook zonder sourcecode) griezelig; ik gebruik ze indien mogelijk nooit.
Je weet vaak niet hoe de boel in elkaar steekt en wat voor resources er gebruikt worden.

Zo zijn de meeste i2c libraries 'blocking'; als er iets mis gaat op de bus hangt de boel tot sint juttemis... ;)
Ik maak er dan een waar altijd een time-out komt, da's wat vriendelijker voor de gebruiker. Een i2c probleem hoeft niet de hele boel plat te leggen.

Libraries zijn ook vaak veel te lomp en uitgebreid. De PPS (poort) initialisatielib bij pic24 is 1414 bytes; in losse code maar 58 bytes...

Arco - "Simplicity is a prerequisite for reliability" - hard-, firm-, en software ontwikkeling: www.arcovox.com

Voordat er een float wordt gemaakt is er een int16_t, deze wordt dan door 16 gedeeld en de uitkomst is de temperatuur in celsius.
Wat ik gebruik is een voorbeeld van de OneWire library, het heet DS18x20_Temperature.

Update:
Als ik de in16_t deel door 0,16 en de uitkomst in een int gooi dan ben ik van de float af en heb ik een bruikbaar getal (milliCelsius zoals eerder werd vermeld).

Hier is de code waarmee het uiteindelijk is gelukt:

c code:


//celsius = raw / 0.16; // raw is 258 bij 16,1 °C, dit geeft 1612
celsius = raw * 100 / 16; //n.a.v. onderstaand bericht
tienen = celsius / 1000;
enen = celsius / 100;
enen = enen % 10;
decEen = celsius % 100;
decEen = decEen / 10;

Als ik alles in de serial monitor print dan krijg ik dit als resultaat:

code:

raw = 258
celsius = 1612
1 6,1 *C
Bezoek mijn site! | Geen project is compleet zonder 4000 series ICs!

[edit n.a.v. bovenstaande edit]
Ik weet niet hoe slim je compiler is. Want met een berekening waar 0,16 in zit heb je dus wederom een sommetje met niet enkel integers.
Ik zou eerst even maal 100 doen, en daarna delen door 16 (wat je compiler ongetwijfeld netjes naar een right-shift omzet).

[Bericht gewijzigd door Jochem op dinsdag 6 februari 2018 17:38:55 (37%)

If you want to succeed, double your failure rate.
EricP

mét CE

Ik vind kant en klare libraries (vaak ook zonder sourcecode) griezelig; ik gebruik ze indien mogelijk nooit.
Je weet vaak niet hoe de boel in elkaar steekt en wat voor resources er gebruikt worden.

Amen.

Maar wat TS eigenlijk zegt... is dat hij eea. al in een integer heeft. Waarom er dan weer een vermenigvuldiging met 0.16 plaats moet vinden (en er weer floats gemaakt worden...) ontgaat me.

Dat delen door 16... heeft vast iets met de output van de sensor te maken. Men leest het register en daar zitten inderdaad 5 'extra' bits in - als je alleen de hele graden wilt, 12 bit resolutie.

Dan komt ook meteen de vraag... wat wil je met het resultaat? Als je het wilt displayen, dan snap ik het nog. Als je het wilt vergelijken, dan snap ik het al een stuk minder.
Maar goed... is snap hoe het zo komt :). Feitelijk kun je ook het stuk 'voor de komma' en het stuk 'achter de komma' er zo min of meer uithalen. Immers, 20 is '1'. 2-1 is 0,5 etc. Daar is natuurlijk met wat shiften wel wat leuks mee te doen. En dan moet je je nog afvragen of je die data nodig hebt - zo nauwkeurig is dat sensortje niet, dus je zult er hoe dan ook nog wat meer mee moeten dan botweg displayen.

De DS18B20 heeft iets van 7 bits voor en 4 bits achter de binaire comma. Deze samen kan je als 11 bit getal (of 12 met teken-bit) beschouwen.

Dus vandaar die 16 steeds: Je kan het 11bit getal als "in 1/16e graad" getal beschouwen.

Goed... Dan doe je maal 100 en delen door 16. Wanneer gaat dat fout? Nou, als de temp boven de 41 graden uitkomt. Dan is 4100 centigraden maal zestien dus meer dan 65535 wat in een (unsigned!) "int" past.

De truuk om niet naar LONGs te moeten grijpen is door te zien dat 100 en 16 allebei deelbaar zijn door vier.

temp_cg = raw * (100/4) / (16/4)

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Ik heb ook lang gedacht dat floats zoveel mogelijk moet vermijden

Maar dat blijkt reuze mee te vallen. Een Atmel heeft hardware support voor integer multiply (MUL, MULS, MULSU) maar ook voor float multiply (FMUL, FMULS, FMULSU), dus een float multiply is sneller dan een int multiply doordat de float maar een 3 byte mantisse heeft en een int 4 bytes.

En delen gaat ook sneller (24 tegen 32 bits en even zoveel loops). Alleen het normaliseren (komma op de juiste plaats zetten) van het resultaat neemt telkens extra tijd.

Bij echt rekenen zijn floats veel handiger dan integers. Als je bijvoorbeeld een ADC waarde moet omrekenen naar een display waarde met een gain van zeg 0.6735, dan moet je je ints eerst met 6735 vermenigvuldigen en daarna delen door 10000. Met allerlei risico's op overflows van het tussen resultaat. Met floats is een simpele multiply met 0.6735 voldoende en dat werkt altijd.

Omrekenen van floats naar string gaat met een handige library funktie:

code:


   float temp = 12.75;
   char Buffer[10];   

   dtostrf(Temp, 8, 2, Buffer); //< Conversion float-to-Ascii

Op 6 februari 2018 19:47:46 schreef deKees:
Alleen het normaliseren (komma op de juiste plaats zetten) van het resultaat neemt telkens extra tijd.

Normaliseren van een vermenigvuldigen of delen is vrij simpel en snel: Volgens mij kunnen er maar 1 of 2 bitjes anders zijn dan "verwacht". Dat is anders bij een verschil berekening. Bij een optelling valt het eigenlijk ook mee. Je kan alleen een overflow krijgen en dus een enkele bitshift om te normaliseren.

(het optellen van een positief en een negatief getal noem ik aftrekken. het verschil tussen een negatief en een possitief getal is dan weer een optelling.)

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Voor een vergelijking even een programmatje gemaakt om te zien hoeveel het scheelt.

Uitgaande van een counter die een Adc simuleert.

Dan in een loop de ADC waarde omrekenen naar een display waarde.

Dan de display waarde optellen in een totaal.

En aan het eind het totaal delen door de counter om het gemiddelde te berekenen.
En dan omrekenen naar een ascii string.

Eerst met floats:

code:



#include <avr/io.h>
#include <stdlib.h>

   float    Counter;
   float    AdcValue;
   float    DisplayValue;
   float    Total;
   float    Average;
   float    Gain;
   float    Offset;

int main()
{
   DDRB |= (1 << PB5);

   Gain     = 0.6578;
   Offset   =   -5.8;
   AdcValue =    0.0;
   char     Buffer[20];

   for ( ;; )
   {
      Total = 0.0;
      for(Counter = 0.0; Counter < 10000.0; Counter += 1.0)
      {
         AdcValue += 1.0;
         DisplayValue = AdcValue * Gain + Offset;
         Total += DisplayValue;
      }
      Average = Total / Counter;
      dtostrf(Average, 8, 3, Buffer);

      PINB |= (1 << PB5);  //< Xor LED 
   }
   return 0;
}

Dan met ints:

code:



#include <avr/io.h>
#include <stdlib.h>

   long int Counter;
   long int AdcValue;
   long int DisplayValue;
   long int Total;
   long int Average;
   long int Gain;
   long int Offset;

int main()
{
   DDRB |= (1 << PB5);

   Gain     =    6578;
   Offset   =  -58000;
   AdcValue =       0;
   char     Buffer[20];

   for ( ;; )
   {
      Total = 0;
      for(Counter = 0; Counter < 10000; Counter += 1)
      {
         AdcValue += 1;
         DisplayValue = ((AdcValue * Gain + Offset) / 10000);
         Total += DisplayValue;
      }
      Average = Total / Counter;
      ltoa(Average, Buffer, 10);

      PINB |= (1 << PB5);  //< Xor LED 
   }
   return 0;
}

De snelheid blijkt op een Arduino Nano gelijk voor beide versies. De led knippert met ongeveer 1 Hz. Dus het programma gaat 2 keer rond per seconde (loop tijd van 0.5 seconden). De AtMega328 kan dus 20000 keer per seconde een waarde berekenen. Best wel bruikbaar.

Float kost wel iets meer ruimte:
0xC1C (3100) bytes voor de float versie.
0x402 (1026) bytes voor de integer versie.

Met compiler optimalisatie uitgeschakeld ( -O0 )

De float library funkties kosten blijkbaar ongeveer 2K bytes extra.
Maar ik ben bang dat de integer versie last heeft van overflows en dus niet goed werkt.

PS, met een extra conversie binnen de loop (DisplayValue omrekenen naar string) wordt de looptijd iets langer :
1.55 seconden voor de float versie en 1.75 seconden voor de int versie.

EricP

mét CE

Bij echt rekenen zijn floats veel handiger dan integers. Als je bijvoorbeeld een ADC waarde moet omrekenen naar een display waarde met een gain van zeg 0.6735, dan moet je je ints eerst met 6735 vermenigvuldigen en daarna delen door 10000.

Dat is natuurlijk wel rekenen 1ste klas lagere school. Om te beginnen kun je beiden zo al door 5 delen. Op die manier kun je natuurlijk alles wel te groot maken. Ook een float...

Met allerlei risico's op overflows van het tussen resultaat. Met floats is een simpele multiply met 0.6735 voldoende en dat werkt altijd.

Ja, duh... als ik mijn integer weet-ik-hoeveel-bits maak, dan werkt dat ook altijd.
Daarnaast nog de afrondingsfouten - in het verleden al diverse keren 'bijna goed' resultaten gezien met financiële software die daar dus de mist mee in ging.

Omrekenen van floats naar string gaat met een handige library funktie:

En daarmee onderschrijf je dus precies wat ik zeg: je hebt er een stuk runtime library voor nodig. Welnu, dat kan... sprintf is ook zoiets. Het kan.

Het resultaat van die testcode is trouwens wel grappig. Je zegt er gelukkig bij dat je optimization uit hebt staan. Toch ben ik wel benieuwd naar de assembly. Je zou verwachten dat alles ook berekend zou worden. Toch zou het me niet verbazen als er wat achterwege gelaten zou worden in de zin van 'resultaat wordt niet gebruikt' - alhoewel de kans daarop met alleen globals niet zo heel groot is. Daar staat tegenover dat in de mainloop alles geïnitialiseerd wordt, dus de compiler kent de scope (en het is 'main', niet volatile, dus niks anders kan er op dat moment aan komen). Verder is gcc soms onhandig slim - waar mogelijk wordt met registers gewerkt en kom je helemaal niet aan variabelen toe. Ik kan me dus goed voorstellen dat jouw loopje helemaal niet in floats berekend wordt (en de ADCvalue ook niet...). Alhoewel ik er geen garantie op geef, ben ik wel benieuwd naar wat er gebeurt als je al die variabelen als 'volatile' declareert. Daarmee zou je moeten forceren dat de boel echt naar RAM gaat en dus ook echt als float berekend wordt.

Om bovengenoemde redenen zou ik juist het resultaat met optimalisatie aan wel eens willen zien.

Maar als je er zoals deKees goed over nadenkt en weet wat je doet, vind ik het gebruik van een float al veel beter te verdedigen. Het probleem is dat steeds minder mensen weten waar ze mee bezig zijn en wat de consequenties zijn van bepaalde keuzes in je programmeerwerk.

Ik prijs me gelukkig dat ik al met computers bezig was in het tijdperk dat dingen als floats (en RAM, en CPU cycles, etc etc) nog "duur" waren zodat mijn uitgangspunt altijd eerst compacte en lichte code is.

If you want to succeed, double your failure rate.

Wat je ook zou kunnen doen is de float waarde met 10 te vermenigvuldigen als je een cijfer achter de komma wilt hebben (en maal 100 als je 2 cijfers wilt hebben). Het resultaat cast je naar een 16 bits of 32 bits variabele, afhankelijk van de maximal waarde van de oorspronkelijke waarde. Bij 16 bits unsigned is dat 6553.5 bij 1 cijfer en 655.35 bij 2 cijfers achter de komma, en bij een signed waarde is dat 3276.7 en 327.67 respectivelijk.

Daarna kun je al dan niet in een loopje de digits berekenen.

iets van:

c code:



# unsigned voorbeeld
uint16_t u16;

// een digit achter de komma nodig
float_val *= 10;

// maak er en unsinged waarde van
u16 = (uint16_t) float_val;

# de digit achter de komma krijg je door een modulo berekening
digit_achter_de_komma = u16 % 10;

# delen door tien om het deel voor de komma te krijgen.
u16 /= 10;

#desgewenst deze truuk herhalen om tientallen en honderdtallen te krijgen.
42