Floating point berekeningen in C

Ah! Dat lijkt best een goed idee. Ik had er niet aan gedacht, want ik gebruik eigenlijk nooit unions. Even proberen...

Dit werkt:

code:


union CharFloat {
  unsigned char Bytes[4];
  float floatvalue;
};

int main()
{
  CharFloat FloatData;

  FloatData.Bytes[0] = 0x00; // least significant byte
  FloatData.Bytes[1] = 0x00;
  FloatData.Bytes[2] = 0x28;
  FloatData.Bytes[3] = 0x42; // most significant byte

  // prints "42.000"
  printf("%f", FloatData.floatvalue);
  return 0;
}

[Bericht gewijzigd door SparkyGSX op zaterdag 4 augustus 2018 17:37:52 (63%)

Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken

Ok, mooi. Dus het kan toch.
Het leek mij niet goed gaan omdat ik dacht dat de interne datastructuren voor beide elementen van de union vervangbaar moeten zijn. Dus een int en 2 bytes zijn beide 16 bits achter elkaar. In het geheugen ziet het er hetzelfde uit. Bij een float kan dit echter niet zo zijn want een float is veel complexter door de comma en zo. Maar blijkbaar corrigeert de compiler daarvoor.

[edit] Oh wacht, je schrijft nog 28 weg? Waarvoor is dat? Wat word er nou geprint?

"We cannot solve our problems with the same thinking we used when we created them" - Albert Einstein

In een float zit geen komma, er zit een integer (de mantissa), een exponent, en een sign bit in. De binaire representatie van "42.0" is 0x42280000. Waarom precies is ingewikkeld, maar ik heb er deze calculator voor gebruikt.

Zolang je er niet mee gaat rekenen, kun je die bitjes verplaatsen zoals je wilt. De compiler "corrigeert" dus helemaal niets, je schrijft de bits achter elkaar in het geheugen, en zegt tegen de compiler "behandel dit als een floating point".

Dat is ook precies de reden dat je in dit geval niet zomaar float_var = (float) int_var; kunt schrijven; dan zal de compiler dus wel een conversie uitvoeren, en dat is precies niet wat je wilt.

EDIT: wellicht wat onhandig dat ik een geheel getal had gebruikt om mee te beginnen; als je "0x40490fdb" invult, krijg je "3.141593" als antwoord.

Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken

Ja ok. Dat betekend dus dat die methode werkt omdat je weet hoe zo'n float in het geheugen zit opgeslagen. Dat had ik uit die voorbeelden niet begrepen. En integer getal zul je die eerst moeten omzetten naar dat interne formaat en ik zie dat de TS dat in z'n post ook al meldt. Dan had die stap naar C toch niet zo moeilijk moeten zijn :-)

"We cannot solve our problems with the same thinking we used when we created them" - Albert Einstein

PICbasic lijkt die float al als een soort van union te beschouwen, aangezien je de ruwe bytes daarvan blijkbaar kunt benaderen, zoals de TS doet in de openingspost. Je moet maar net weten dat C ook een dergelijke constructie kent, en hoewel ik prima weet dat hij bestaat en hoe hij werkt, kom ik eigenlijk nooit op het idee om een union te gebruiken.

Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken

Wat de TS uiteindelijk eigenlijk doet is:

code:

 float ffc;
 ((char *) (&ffc)) [0] = fcf0; 
 ((char *) (&ffc)) [1] = fcf1; 
 ((char *) (&ffc)) [2] = fcf2; 
 ((char *) (&ffc)) [3] = fcf3;

Syntactisch heeft de basicvariant van TS daar kennelijk een truuk voor.

Sparky, Die "0x42" in je floating point getal lijkt op de "42.0" die er uiteindelijk uitkomt. Dat schept ook nog verwarring. Maar de 0x42 is volgens mij voornamelijk de exponent en heeft dus niets met het uiteindelijke getal 42.0 te maken.

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

Je kunt helemaal geen (32 bit) int omzetten naar een float door de bytes 1 op 1 over te zetten naar de bytes van de float zelf (gerotzooi met unions dus).

Het interne formaat is heel anders. Het getal bestaat uit een mantissa en een exponent. De mantissa is (meestal) genormaliseerd tussen 1.0 en 9.999....
Voor een 32 bit float is de mantissa dus 23 bits met sign en de exponent 7 bits met sign.

Het gemakkelijkst is om de bytes in een 32-bit integer te stoppen, en die vervolgens te type-casten naar een float. Het lastige is dat je de compiler zover moet krijgen dat hij juist geen conversie gaat doen.

Half goed, de 2-de zin is onzin.
Sterker nog eigenlijk is de enige manier om het juiste te doen dit:

var_float = (float)var_int;

Al het gerommel het bytes/unions werkt NIET. Puur toeval als er een keer iets uit komt wat lijkt te werken. Waarschijnlijk omdat de exponent toevallig goed is (0).

1-st law of Henri: De wet van behoud van ellende. 2-nd law of Henri: Ellende komt nooit alleen.

Hallo spuit elf! Fijn dat je ons allemaal voor idioot uitmaakt zonder het hele topic te lezen, zo lang was het toch niet?

Blijkbaar krijgt de TS de data, die een float moet voorstellen, als losse bytes binnen, waarschijnlijk over een seriele poort of zo. Dan wil je daar dus weer een float van maken ZONDER CONVERSIES.

Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken

Op 4 augustus 2018 22:12:02 schreef rew:
Sparky, Die "0x42" in je floating point getal lijkt op de "42.0"

Ja, dat was voor mij de verwarring, maar dat was snel gecorrigeerd. Waarvoor dank.

"We cannot solve our problems with the same thinking we used when we created them" - Albert Einstein

Op 5 augustus 2018 00:10:05 schreef SparkyGSX:
Hallo spuit elf! Fijn dat je ons allemaal voor idioot uitmaakt zonder het hele topic te lezen, zo lang was het toch niet?

Blijkbaar krijgt de TS de data, die een float moet voorstellen, als losse bytes binnen, waarschijnlijk over een seriele poort of zo. Dan wil je daar dus weer een float van maken ZONDER CONVERSIES.

Ik heb het wel degelijk gelezen, maar dat staat nergens:

Op 4 augustus 2018 16:41:11 schreef wouterpw:
Hallo allemaal,

...
Van de sensor krijg ik 4 bytes in hex formaat wat ik omzet naar een floating point in IEEE-754 formaat om daar verdere berekeningen mee te doen.
...

Hier staat "wat ik omzet" .... kortom nergens staat dat de bytes als IEEE-754 formaat DIRECT uit de sensor komt. Als die dat direct gezegd had.

Verder hadden die bytes ook een fixed point notatie kunnen zijn etc. wat veel vaker voorkomt.

Ik heb weinig tot geen sensoren gezien die direct een float naar buiten sturen.

1-st law of Henri: De wet van behoud van ellende. 2-nd law of Henri: Ellende komt nooit alleen.

Ik ken er anders wel een paar, al geef ik toe het dat het niet heel erg gangbaar is. De code waar het topic mee begon, die volgens de TS werkte in PICbasic, maakt toch ook duidelijk wat de bedoeling is.

Raar dat het "puur toeval" is dat het werkt als je met 0x4218000 en er, geheel correct, 42.000 uit komt. Met jouw diepgaande kennis over de interne werking van IEEE floating points had je toch ook moeten zien dat de exponent daarbij NIET 0 is.

Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken

Op 5 augustus 2018 21:55:39 schreef henri62:
[...] Ik heb het wel degelijk gelezen, maar dat staat nergens:

Heb jij op dit forum wel eens een topic-opening gezien waar WEL alle relevante info direct gegeven werd?

Aan de basic code die hij geeft, kan je zien dat kennelijk deze sensor doet wat jij (en ik!) kennelijk nog nooit gezien hebben: Data in floating point uitgeven.

Hier is de "conversie" naar float zonder de compiler voor de gek te houden dat de bytes ineens als float ge-interpreteerd moeten worden.

c code:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

union floatbyte {
  float f;
  unsigned char b[4];
  int i;
};

int main (int argc, char **argv)
{
  float f;
  union floatbyte fb;
  int i, m, e, s;

  if (strstr (argv[0], "floathex")) {
    for (i=1;i<argc;i++) {
      fb.f = atof (argv[i]);
//      for (j=0;j<4;j++) 
//         printf ("%02x", fb.b[3-j]);
//      printf (" ");
        printf ("%08x ", fb.i);
    }
    printf ("\n");
  } else { // hexfloat 
    for (i=1;i<argc;i++) {
      sscanf (argv[i], "%x", &fb.i);
//      printf ("%f ", fb.f);
      m = 0x800000 | fb.b[0] | (fb.b[1] << 8) | (fb.b[2] << 16);
      s = fb.b[3] >> 7;
      e = ((fb.b[3] & 0x7f) << 1) | ((fb.b[2] & 0x80) >> 7);
//      printf ("%d/%d/%d ", e,m, s);
      f = m;
      while (e > 127+23) {e--; f = f*2;}
      while (e < 127+23) {e++; f = f/2;}
      if(s) f= -f;
      printf ("%f ", f);
    }
    printf ("\n");
  }
  exit (0);
}

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

Ik bedacht net dat het nog "schoner" kan, als je data al in een 32-bit integer staat:

code:


FloatVar = reinterpret_cast<float&>( IntVar );

Met die "reinterpret_cast" vertel je de compiler dat hij geen code moet genereren voor de type conversie.

[Bericht gewijzigd door SparkyGSX op maandag 6 augustus 2018 12:17:57 (26%)

Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken

Sow, 15 reacties al.
Ik zat te wachten op een mailtje dat ik reactie had, maar heb het vinkje denk ik vergeten aan te vinken:)

Ten eerste bedankt voor de feedback, ga ik vanavond mee stoeien.

Voor jullie info, het programma leest een load-sensor uit.
Iedere seconde wordt een aantal keer de weerstandswaarde van de sensor uitgelezen.
Deze waarde wordt met 6 floating point waarde's vermenigvuldigt die in de EEprom van de sensor opgeslagen zijn als losse bytes.
Iedere floating point waarde bestaat uit 4 bytes uit de EEprom.

Deze waarde's in de EEprom worden aangepast na een eventuele kalibratie van de sensor.

Ik hoop dat dit iets meer duidelijk geeft in waarom ik deze conversie wil maken.

SparkyGSX:
Met deze code krijg ik de juiste waarden terug.

c code:

	unsigned char FCF1 = 0x47, FCF2 = 0x84, FCF3 = 0x29, FCF4 = 0xA9;
	float float_var;
	unsigned long tempint;

	tempint = ((((unsigned long)FCF1) << 24) | (((unsigned long)FCF2) << 16) |
                   (((unsigned long)FCF3) << 8) | ((unsigned long)FCF4));
	
	float_var = *(float *)&tempint;
	printf("Result: %f", float_var);

Nu is het alleen zo dat deze code alleen werkt als ik het programma compileer op mijn pc.
Zodra de code in de microcontroller zit komt er altijd -0.000000 als antwoord.
Kan dit te maken hebben met dat microchip een apart protocol heeft voor floating point variabelen?

Misschien is de discussie al lang geweest, maar moet je speciaal in floats werken? Rekenen in floats is uiteraard nogal omslachtig, zeker voor een microcontroller. Een + op een int is heel wat anders dan een + op een float.

"We cannot solve our problems with the same thinking we used when we created them" - Albert Einstein

Ja de floating points zijn noodzakelijk.

JoWi

Special Member

Nu is het alleen zo dat deze code alleen werkt als ik het programma compileer op mijn pc.
Zodra de code in de microcontroller zit komt er altijd -0.000000 als antwoord.
Kan dit te maken hebben met dat microchip een apart protocol heeft voor floating point variabelen?

De XC8 compiler heeft twee floating point formaten, default is 24 bit. Jij wilt de 32bit (IEEE754) variant, daarvoor moet je de --DOUBLE of --FLOAT optie gebruiken. Check de documentatie.

Ignorance is bliss

24-bit floats? Dat is voor mij dan weer nieuw. Maar goed, er is een lijst redenen dat ik bij voorkeur niets met PICs te maken heb, en die idiote architectuur is er één van.

Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken

Inderdaad,

Bedankt allemaal. Het programma werkt!

Mooi!

Ik vraag me wel af wat het nut is van 24-bit floating points; je wint nauwelijks geheugen (25% ten opzichte van standaard 32-bit floats), maar als je er arrays van maakt (de enige keer dat die winst echt helpt), staan ze niet meer op 4-byte boundary adressen. Wellicht maakt dat voor zo'n 8-bits PIC niet uit omdat hij toch alle bytes apart moet lezen of zo.

Zouden berekeningen aan dergelijke 24-bit floats significant sneller zijn met software emulatie, bij gebruik aan floating point hardware in kleinere microcontrollers?

Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken
JoWi

Special Member

24bit floats (8bit exponent, 16 bits mantissa) en 24bit integers (die heb je ook) zijn kleiner en sneller op elke 8bit micro zonder f.p. hardware. Dat het niet aligned is maakt voor een 8bit micro niet uit.

Ik probeer ze te vermijden, maar soms lukt het niet omdat er trigonio of log/exp rekenwerk nodig is.

Ignorance is bliss

Gezien je toch in "C" bezig bent had ik voor dit soort werk een STM32 genomen. Die heeft hardware floating point support, en is nauwelijks duurder dan een 8 bits uC.

It's the rule that you live by and die for It's the one thing you can't deny Even though you don't know what the price is. It is justified.

Tja, een immitatie-Arduino bordje kost iets van 3 euro, tegenover meer dan 20 euro voor een STM32 discovery. Voor hele simpele dingen wil ik ook nog wel eens een 8-bitter pakken, maar dan meestal een AVR (al dan niet zo'n fake-duino).

Overigens hebben alleen de STM32F4's en hoger een floating point unit; de F0-F3 series dus niet. Persoonlijk gebruik ik die F3 serie erg veel, omdat hij analoog beter is dan de F4.

Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken

Op 7 augustus 2018 08:33:07 schreef SparkyGSX:
tegenover meer dan 20 euro voor een STM32 discovery.

Je moet ook geen Discovery gaan kopen.

(16k flash = 0.5* arduino
4k RAM = 2*arduino
12 bit ADC = 4x nauwkeuriger dan arduino.
maar belangrijkste: 8-16x sneller bij 32-bit of FP berekeningen. )

Die '030 is wel de allergoedkoopste uit de familie: Niet eens een 32-bit counter.

De '072 vind ik een prima balans hebben tussen functionaliteit en kosten. Kost rond de 2 euro. Op een bepaald moment kostten de '328 chips dat ook, maar die zijn weer gezakt. Ik ben ondertussen "om"... :-)

code:


            stm32f072    atmega328
RAM          16k           2k
flash        64-128k       32k
eeprom       0             0.5k?
         (gebruik flash)
pins         32-100        32 (waarvan 4 niet gebruikt)
ADC          12 bit        10 bit
DAC          ja 2x 12b     nee
PWM          wat je maar   zware 
             wilt          beperkingen

Helaas geen goedkope development boards in china te krijgen voor de 072. Ik maak ze trouwens zelf.

[Bericht gewijzigd door rew op dinsdag 7 augustus 2018 09:01:41 (53%)

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