Forum: Mikrocontroller und Digitale Elektronik sinnvoll rechnen mit floats


von Peterle A. (Firma: keine) (wanderameise)


Lesenswert?

morgen,

ich habe auf meinem at90can128 eine kleine routine implementiert, die 
mir, abhängig von den eingelesenen adc werten, etwas berechnet. die 
berechnung findet mit floatwerten statt. hier nun meine erste frage:
ist es sinnvoll, die werte, welche im bereich -30.0 bis +30.0 liegen 
können, mit 100 (2 nachkommastellen) zu multipliziern, um mit int zu 
rechnen?

das ergebnis muss in einem char buffer gespeichert werden, welcher via 
usb an den client gesendet wird. hier meine nächste frage:
meine idee, zB die zahl 22.44, mittels sprintf() in eine zeichenkette zu 
konvertieren hat ganz gut funktioniert. ich lege fest, das eine zahl aus 
max. 5 stellen besteht(zB -22.44 = -2244) und kann beim empfang einfach 
wieder durch 100 teilen. sollte theoretisch klappen oder?

die 2. "platzsparendere"- und eigentliche urpsrungsidee wäre gewesen, 
einfach den wert wieder mal 100 zu nehmen und dann durch shiften erst 
das obere, dann das untere byte zu senden. der client ver-ODERt dann 
beide(erstes byte zurückshiften); allerding weiss ich nicht, wie ich 
hier mit negativen zahlen umzugehen habe!

wie ist die übliche vorgensweise bei sowas?
danke

von Sven P. (Gast)


Lesenswert?

Peterle Anonym schrieb:
> morgen,
>
> ich habe auf meinem at90can128 eine kleine routine implementiert, die
> mir, abhängig von den eingelesenen adc werten, etwas berechnet. die
> berechnung findet mit floatwerten statt. hier nun meine erste frage:
> ist es sinnvoll, die werte, welche im bereich -30.0 bis +30.0 liegen
> können, mit 100 (2 nachkommastellen) zu multipliziern, um mit int zu
> rechnen?
Ja, du kannst sie auch mit 1000 multiplizieren, wenn das in die Integer 
hereinpasst. Das wäre weitaus sparsamer und schneller auf einem AVR, als 
Fließkommazahlen.

> das ergebnis muss in einem char buffer gespeichert werden, welcher via
> usb an den client gesendet wird. hier meine nächste frage:
> meine idee, zB die zahl 22.44, mittels sprintf() in eine zeichenkette zu
> konvertieren hat ganz gut funktioniert. ich lege fest, das eine zahl aus
> max. 5 stellen besteht(zB -22.44 = -2244) und kann beim empfang einfach
> wieder durch 100 teilen. sollte theoretisch klappen oder?
Jo. Lässt sich auf diese Art auch angenehmer debuggen, als wenn du die 
Zahlen als Binärdaten senden würdest. Alles eine Frage der Basiseinheit.


> die 2. "platzsparendere"- und eigentliche urpsrungsidee wäre gewesen,
> einfach den wert wieder mal 100 zu nehmen und dann durch shiften erst
> das obere, dann das untere byte zu senden. der client ver-ODERt dann
> beide(erstes byte zurückshiften); allerding weiss ich nicht, wie ich
> hier mit negativen zahlen umzugehen habe!
Auch richtig. Es kann Einer- oder Zweier- oder garkein Komplement sein. 
Am sichersten und 'schönsten' ist daher m.M.n. deine bisherige Variante, 
die Zahlen im Klartext zu senden. Auch umgehst du damit Probleme mit 
Endianess, also der Reihenfolge der Einzelbytes im Speicher.
Du löst das zwar arithmetisch, aber das hilft dann immer noch nicht 
weiter bei der Darstellung der negativen Beträge.


> wie ist die übliche vorgensweise bei sowas?
Vorher festlegen. Im Zweifelsfall muss dann einer der beiden umrechnen.

von Karl H. (kbuchegg)


Lesenswert?

Peterle Anonym schrieb:

> ich habe auf meinem at90can128 eine kleine routine implementiert, die
> mir, abhängig von den eingelesenen adc werten, etwas berechnet. die
> berechnung findet mit floatwerten statt. hier nun meine erste frage:
> ist es sinnvoll, die werte, welche im bereich -30.0 bis +30.0 liegen
> können, mit 100 (2 nachkommastellen) zu multipliziern, um mit int zu
> rechnen?

Kommt drauf an.
Wenn dein µC massig Zeit hat und Float Rechnerei kein Flaschenhals ist, 
kann die einfachste Variante, nämlich einefach in float zu rechnen 
sinnvoll sein.
Oder auch nicht. Wenn dann hintennach mit dem Wert noch 200 Berechnungen 
gemacht werden, kann es aus Gründen der Rundungsgenauigkeit wieder 
sinnvoll sein, auf float zu verzichten.

> das ergebnis muss in einem char buffer gespeichert werden, welcher via
> usb an den client gesendet wird. hier meine nächste frage:
> meine idee, zB die zahl 22.44, mittels sprintf() in eine zeichenkette zu
> konvertieren hat ganz gut funktioniert. ich lege fest, das eine zahl aus
> max. 5 stellen besteht(zB -22.44 = -2244) und kann beim empfang einfach
> wieder durch 100 teilen. sollte theoretisch klappen oder?

Ja. klar
Kann man so machen. Muss man aber nicht. Hängt auch davon ab was der 
Wert repräsentiert und ob es da eine 'natürliche' Erklärung für den 
Faktor 100 gibt.
Must du zb Euro übertragen, dann kann man anstelle von Euro 5.20 einfach 
520 Cent übertragen, also grundsätzlich festlegen, dass nicht Euro 
sondern Cent auf die Reise gehen.
Gibt es keine derartige 'natürliche' Erklärung für einen Faktor von zb 
100, dann sollte man auch daran denken, dass man sich bei Tricksereien 
an einer Schnittstelle durch 'unübliche' Darstellungsweisen auch schon 
mal ein Eigentor schiessen kann, wenn mehrere Leute gemeinsam an einem 
Projekt arbeiten. Die Nase hat eine Mars-Sonde verloren, weil ein Teil 
der Entwickler in Meilen und der andere in Kilometer gerechnet hat, 
obwohl eigentlich überall Kilometer vorgeschrieben gewesen wären :-)

> die 2. "platzsparendere"- und eigentliche urpsrungsidee wäre gewesen,
> einfach den wert wieder mal 100 zu nehmen

welchen Wert?
int oder float?

> und dann durch shiften erst
> das obere, dann das untere byte zu senden. der client ver-ODERt dann
> beide(erstes byte zurückshiften);

> allerding weiss ich nicht, wie ich
> hier mit negativen zahlen umzugehen habe!

brauchst du nicht speziell zu behandeln.
Solange der Empfänger die beiden Bytes wieder zu einer 2-Byte Zahl 
zusammensetzt, kommt bei ihm wieder dasselbe Bitmuster raus. Und da 
heutzutage alle Welt 2-er Komplement benutzt, kriegt er damit auch die 
richtige Zahl.

von Εrnst B. (ernst)


Lesenswert?

Je nach Rechenweg kann es auch besser sein, statt einem Faktor von 100 
z.B. 128 zu nehmen. Ist für den µC viel "natürlicher" und einfacher zu 
handhaben.
Wenn der Hauptzweck jedoch die Anzeige als Dezimalzahl auf einem LCD 
ist, macht die nötige Umrechnerrei den Vorteil evtl. wieder zunichte.

von Peterle A. (Firma: keine) (wanderameise)


Lesenswert?

die 100 als faktor habe ich mal aus der luft gegriffe, da ich 2 stellen 
nach dem komma für ausreichend halte, wobei das ganze eine frage des 
platzes ist, denn beim faktor 1000 wären es schon 6 stellen die eine 
zahl einnimmt.
Bei der shift-Methode, wären es nur 2!
vll wäre deswegen letzteres eine effizientere methode??


>> die 2. "platzsparendere"- und eigentliche urpsrungsidee wäre gewesen,
>> einfach den wert wieder mal 100 zu nehmen
>
> welchen Wert?
> int oder float?

den float, um ihn wieder als ganzzahl zu behandeln, da man floats nicht 
shiften kann!



>> allerding weiss ich nicht, wie ich
>> hier mit negativen zahlen umzugehen habe!
>
> brauchst du nicht speziell zu behandeln.
> Solange der Empfänger die beiden Bytes wieder zu einer 2-Byte Zahl
> zusammensetzt, kommt bei ihm wieder dasselbe Bitmuster raus. Und da
> heutzutage alle Welt 2-er Komplement benutzt, kriegt er damit auch die
> richtige Zahl.

nicht speziell? ich habe es mal hier simuliert, falls ich eine negative 
zahl aufsplitte in oberes und unteres byte, erhalte ich nur mist:
1
char buf[3];
2
float g = -22.44;
3
int p = (int)(100 * g);
4
buf[0] = (char)(p >> 8):
5
buf[1] = (char)(p):
6
printf("%.2f", ((float)((buf[0] << 8) | buf[1]) / 100));

das klappt mit positiven zahlen ganz gut, aber nicht mit negativen. was 
mache ich falsch?

von Karl H. (kbuchegg)


Lesenswert?

Das Problem entsteht beim Zusammenbau.
Du musst zuerst wieder auf einen int casten, damit dein 2-Bytekonstrukt 
für den Compiler wieder ein Vorzeichen erhält.

printf("%.2f", ((float)(int)((buf[0] << 8) | buf[1]) / 100));

von Tim S. (maxxie)


Lesenswert?

Karl heinz Buchegger schrieb:
> Das Problem entsteht beim Zusammenbau.
> Du musst zuerst wieder auf einen int casten, damit dein 2-Bytekonstrukt
> für den Compiler wieder ein Vorzeichen erhält.
>
> printf("%.2f", ((float)(int)((buf[0] << 8) | buf[1]) / 100));

Das muss schon eine Stelle früher geschehen.

(buf[0] << 8) ist mit 'char buf[3]' gleich 0
((int)buf[0] << 8) ist erst sinnvoll, da der Speichertyp der geshiftet 
wird ausreichend Stellen besitzt.

Das untere Byte sollte dann nach uint gecastet werden, sonst kann es dir 
ebenfalls wieder (durch den cast von char auf int werden die oberen bits 
sign-extended) Mist reinhauen. (bei Werten &0xFF > 0x7F) sind dann nach 
dem Oder alle höherwertigen Bits 1

int fpval = (int)(((int)buf[0] << 8) | (unsigned int)buf[1]) ;

Mit einem unsigned char buf[3] bräuchte es nur den inneren (int) cast.

von Peterle A. (Firma: keine) (wanderameise)


Lesenswert?

Tim Seidel schrieb:
> Karl heinz Buchegger schrieb:
>> Das Problem entsteht beim Zusammenbau.
>> Du musst zuerst wieder auf einen int casten, damit dein 2-Bytekonstrukt
>> für den Compiler wieder ein Vorzeichen erhält.
>>
>> printf("%.2f", ((float)(int)((buf[0] << 8) | buf[1]) / 100));
>
> Das muss schon eine Stelle früher geschehen.
>
> (buf[0] << 8) ist mit 'char buf[3]' gleich 0
> ((int)buf[0] << 8) ist erst sinnvoll, da der Speichertyp der geshiftet
> wird ausreichend Stellen besitzt.
>
> Das untere Byte sollte dann nach uint gecastet werden, sonst kann es dir
> ebenfalls wieder (durch den cast von char auf int werden die oberen bits
> sign-extended) Mist reinhauen. (bei Werten &0xFF > 0x7F) sind dann nach
> dem Oder alle höherwertigen Bits 1
>
> int fpval = (int)(((int)buf[0] << 8) | (unsigned int)buf[1]) ;
>
> Mit einem unsigned char buf[3] bräuchte es nur den inneren (int) cast.

weiss nicht ob ich das richtig verstanden habe, jedenfalls klappt es 
nicht so recht:
1
  float g = -49.23;
2
  int p = (int)(100*g);
3
  signed char buf[2];
4
  buf[0] =(char)(p>>8);
5
  buf[1] =(char)(p);
6
  p = (int)(((int)buf[0]<<8) | (unsigned int)buf[1]);
7
  printf("\n%f", (float)p);

buf[1] ist auch negativ bei mir, wieso das denn?

von Tim S. (maxxie)


Lesenswert?

> weiss nicht ob ich das richtig verstanden habe, jedenfalls klappt es
> nicht so recht:
>
>
1
>   float g = -49.23;
2
>   int p = (int)(100*g);
3
>   signed char buf[2];
4
>   buf[0] =(char)(p>>8);
5
>   buf[1] =(char)(p);
6
>   p = (int)(((int)buf[0]<<8) | (unsigned int)buf[1]);
7
>   printf("\n%f", (float)p);
8
> 
9
>
>
> buf[1] ist auch negativ bei mir, wieso das denn?

Gehen wir mal durch:
(int)(100*g) = -4923.
buf[0] = (char)(p >> 8) = -20 (0xEC)
buf[1] = (char)(p) = -59 (0xC5)
((int)buf[0]<<8) = (int)(char)0xEC << 8 =  0xFFEC << 8 = 0xEC00
(unsigned int)buf[1] = (unsigned int)(char)0xC5 = 0x00C5
0xEC00 | 0x00C5 = 0xECC5
(int)0xECC5 = -4923

Dein printf gibt also das 100fache von g aus.

Beachte:
Bei Wandlungen int<->float gehen Informationen verloren. Den numerischen 
Fehler für -42.23 müsste man jetzt nachrechnen. Liegt die 
float-Annäherung an diese Zahl bei -42.22999999... o.ä. wird das 
Ergebnis entsprechend der Truncate beim integer cast abweichen.

von Peterle A. (Firma: keine) (wanderameise)


Lesenswert?

mein fehler liegt hier:
1
(int)buf[0]<<8

der cast des array elements.

danke hat alles geklappt.

das würde aber bedeuten, das ich mit dieser methode 2 arrayelemente 
anstatt von 6 (1. beispiel) benötigen würden, ergo:
letzteres ist effizienter und platzsparender! aber auch sinnvoller?

welche methode würdet ihr wählen?!

von Thilo M. (Gast)


Lesenswert?

Zuerst die Frage: was ist gefordert?
Brauchst du Rechenleistung, ist die Anwendung zeitkritisch?

Ich löse das immer nach der Devise 'so schnell wie nötig, so genau wie 
möglich'.

Bei Temperaturen (PT1000) z.B., die sowieso recht träge sind, rechne ich 
alles schön in double. Die Rechenleistung reicht hierfür allemal aus, 
also warum sollte ich mir das Hirn verbiegen? ;)

Wenn der Speicherplatz begrenzt ist oder die Anwendung Geschwindigkeit 
erfordert, dann mache ich mir Gedanken, vorher nicht.

von Peterle A. (Firma: keine) (wanderameise)


Lesenswert?

guter ansatz: zeitkritische Anwendung.
Ich weiss es gar nicht genau, wo ich enden werde mit der zeit. Im moment 
führe ich berechnungen durch, die durch einen interrupt alle 4ms 
durchgeführt werden. NOCH sind diese Berechnungen im Rahmen. Was aber, 
wenn die Routine länger dauert als 4ms?wie finde ich heraus ob 40 zeilen 
float arithmetik unter x sekunden liegt? Ich denke diesen Aspekt muss 
ich mir nochmal in ruhe anschauen!

von Thilo M. (Gast)


Lesenswert?

4ms sind eine recht lange Zeit für einen µC. Kommt natürlich drauf an, 
was sonst noch läuft (LCD, RS232, ...).
Du kannst die Rechenzeit deiner Funktion im Debugger (AVR-Studio) 
messen, das geht am einfachsten.

von ameise (Gast)


Lesenswert?

danke werde es dann mal testen!

von Peterle A. (Firma: keine) (wanderameise)


Lesenswert?

habe nochmal eine frage. ich schaffe es nur positive oder negative 
zahlen zu übertragen, nicht beide zB abwechselnd:
1
  char answer[64]; 
2
  int hh, gt;
3
  (...) //answer wird gefüllt
4
              
5
  hh =(int)answer[0];
6
  hh = hh << 8;
7
  gt = (answer[1] | hh);
8
  printf("\n: %f",(float)gt/1000);
in diesem fall werden negative zahlen korrekt übertragen. positive 
jedoch verkehrt. deklariere ich answer[] als unsigned ist es umgekehrt.
wie löse ich das problem, um mit beide Zahlentypen arbeiten zu können?

danke

von Tim S. (maxxie)


Lesenswert?

Das liegt nicht an den negativen Zahlen in der Eingabe. Der Grund warum 
das passiert ist in dem Thread schon genannt.
1
 gt = (answer[1] | hh);
überschreibt die oberwertigen bits (aka hh) mit 1en, wenn (answer[1] < 
0)
Answer[1] ist immer dann < 0, wenn bit7 gesetzt ist.

Als Zahlenbeispiel: answer[0] = 0x00, answer[1] = 0x80 (Da signed ist 
das -127).
Du willst das (gt) als 128 auslesen, bzw nach dem Teilen als 0.128 es 
passiert aber Folgendes:

hh = 0b0000 0000 0000 0000 ;
gt = (0b1111 1111 1000 0000 | 0b0000 0000 0000 0000)
   =  0b1111 1111 1000 0000 = -127

von Karl H. (kbuchegg)


Lesenswert?

Prinzipiell solltest du die etwas zur Gewohnheit machen:

Wenn du irgendwelche Werte über eine Byte-Schnittstelle schleusen musst, 
dann sorge dafür, dass du überall in dieser Tunnelstrecke es immer mit 
Bytes zu tun hast. Das bedeutet keine Vorzeichen mehr, keine 
Spezialbedeutung, nichts. Einfach nur 8 Bit, die zusammen die Einheit 
Byte bilden.

Du schleust vorne in die Tunnelstrecke deine Werte (seien es double, 
seien es float, seien es Strukturen oder was auch immer) ein, dort 
werden diese Werte in einzelne Bytes auseinandergenommen (dazu gibt es 
verschiedene Techniken) und dann hast du nur noch Bytes. Die werden auf 
die Reise geschickt, kommen beim Empfänger an, und der setzt sie in der 
Umkehrung der Auseinanderpflückaktion wieder zu einer Bytesequenz 
zusammen, die dem Abholer wieder als der ursprüngliche Datentyp 
untergjubelt werden.

Aber dazu musst du dir unbedingt etwas angewöhnen:
Der Datentyp 'char' ist für dich tabu!
char benutzt man, wenn man es mit Charactern im Sinne von einzelnen 
Zeichen eines Textes zu tun hat.
Hat man es mit Bytes zu tun, dann IMMER (mit 3 Rufzeichen dahinter) 
'unsigned char' oder 'uint8_t'. Aber NIEMALS einfach nur char.
Ob char als mit oder ohne Vorzeichen aufgefasst wird, hängt von deinem 
Compiler ab und oft sogar davon, mit welchen Commandline Argumenten 
dieser aufgerufen wird. Diese Entscheidung willst du dir aber auf keinen 
Fall aufzwingen lassen. Du willst haben dass ein Byte einfach nur als 
Einheit von 8 Bit gilt. Ohen irgendwelche Sonderfälle, ohne dass beim 
Schieben irgendwelche Vorzeichenbits eine Sonderbehandleung erfahren 
oder sonst irgendetwas Spezielles passiert. Und dazu verlässt du dich 
AUF KEINEN FALL darauf, dass ein char das schon richtig machen wird, 
sondern du erzwingst mit einem 'unsigned char' die Behandlung als Bytes 
so wie der Bitgott sie erfunden hat.

von Peterle A. (Firma: keine) (wanderameise)


Lesenswert?

im konkreten fall:
device A ssendet ein datenpaket an B
 A füllt einen unsigned char mit daten:
1
unsigned char message[2];
2
uint16_t value = 2288;
3
message[0] = (value >> 8); // hier auf unsigned char casten?
4
message[1] = value;

dann werden diese daten via bulk send zB via USB an B geschickt:
1
unsigned char answer[2]
2
uint16_t hh, gt;
3
(...) // Bulk wird empfangen -> answer gefüllt 
4
  
5
hh = (int)answer[0] << 8;             //??
6
gt = (answer[1] | hh);               //??

was ändere ich beim empfangen nun konkret, um positive sowie negative 
zahlen auswerten zu können? Oder muss eine andere methoder her?

von ameise123 (Gast)


Lesenswert?

keiner ´nen tip für mich?

von Klaus W. (mfgkw)


Lesenswert?

auf welche Frage?

von Karl H. (kbuchegg)


Lesenswert?

Schreib dir Funktionen für jeden Datentyp, die dir
* den Wert im jeweiligen Datentyp in einem Bytefeld ablegen
* den Wert im jeweiligen Datentyp aus dem Bytefeld holen
1
void ConvertFromUint16( uint16_t value, uint8_t* buffer )
2
{
3
  *(uint16_t*)buffer = value;
4
}
5
6
uint16_t ConvertToUint16( uint8_t* buffer )
7
{
8
  return *(uint16_t*)buffer;
9
}
10
11
void ConvertFromInt16( int16_t value, uint8_t* buffer )
12
{
13
  *(int16_t*)buffer = value;
14
}
15
16
int16_t ConvertToInt16( uint8_t* buffer )
17
{
18
  return *(int16_t*)buffer;
19
}
20
21
void ConvertFromFloat( float value, uint8_t* buffer )
22
{
23
  *(float*)buffer = value;
24
}
25
26
float ConvertToFloat( uint8_t* buffer )
27
{
28
  return *(float*)buffer;
29
}

Je nachdem, wie der Rest aussieht, kann es sinnvoll sein oder auch 
nicht, zum buffer die jeweilige Längenangabe mit zu übergeben.

Wenn es wichtig ist, dass die Low/High Byte Reihenfolge eingehalten 
wird, dann müsste man darüber nachdenken, ob man anstelle des wilden 
Pointer-Umcastens nicht eine klassische Zerlegung mittels Shiften macht. 
Aber auch dort wird man um Casten nicht rumkommen.

Edit: Ooops
Sehe gerade, dass du eine USB Übertragung zu einem anderen Rechner 
machst, d.h. die Bytereihenfolge sollte besser explizit definiert 
werden, wobei das bei float/double sowieso so eine Sache ist.
1
void ConvertFromUint16( uint16_t value, uint8_t* buffer )
2
{
3
  buffer[0] = ( value >> 8 ) & 0xFF;
4
  buffer[1] = value & 0xFF;
5
}
6
7
uint16_t ConvertToUint16( uint8_t* buffer )
8
{
9
  return ( ((uint16_t)buffer[0]) << 8 ) | buffer[1];
10
}
11
12
void ConvertFromInt16( int16_t value, uint8_t* buffer )
13
{
14
  buffer[0] = ( (uint16_t)value >> 8 ) & 0xFF;
15
  buffer[1] = (uint16_t)value & 0xFF;
16
}
17
18
int16_t ConvertToInt16( uint8_t* buffer )
19
{
20
  return int16_t( ( ((uint16_t)buffer[0]) << 8 ) | buffer[1] ) );
21
}
22
23
void ConvertFromFloat( float value, uint8_t* buffer )
24
{
25
  uint16_t* WordPtr = (uint16_t*)value;
26
27
  ConvertFromUint16( WordPtr, buffer );
28
  ConvertFromUint16( WordPtr+1, buffer + 2 );
29
}
30
31
float ConvertToFloat( uint8_t* buffer )
32
{
33
  uint16_t WordPtr[2];
34
35
  WordPtr[0] = ConvertToUint16( buffer );
36
  WordPtr[1] = ConvertToUint16( buffer + 2 );
37
38
  return *(float*)WordPtr;
39
}

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.