Forum: Mikrocontroller und Digitale Elektronik Zahlenwerte in Gleitkommazahl umwandeln in c


von Chris T. (chris0086)


Lesenswert?

Hallo Leute ich steh vor dem Problem eine Zahl in eine Gleitkomme zahl 
umzuwandeln.
Also der Zahlenbereich geht von -671 088,64 bis 670 760,96.
Die Auflösung ist aber auf ganze Zahlen beschränkt.
Ich habe also eine Array zur Verfügung indem die umzurechnende Zahl 
steht.
Also z.Bsp:
-27 da steht dann im Array
Ar1[0]='-';
Ar1[1]='0';
Ar1[2]='0';
Ar1[3]='0';
Ar1[4]='0';
Ar1[5]='2';
Ar1[6]='7';

bis dahin nicht neues.
Aber ich muss die Zahl jetzt in folgendes Format umwandeln:

Float Value
MEEE EMMM MMMM MMMM
Encoding:
FloatValue = (0,01*M)*2(E)
E = [0 … 15]
M = [-2 048 … 2 047], two’s complement notation
For all Datapoint Types 9.xxx, the encoded value 7FFFh shall always be 
used to denote invalid
data

Wie kann ich die Zahl -27 in das Format MEEE EMMM MMMM MMMM am 
effizientesten umwandeln?

von H.J.Seifert (Gast)


Lesenswert?

Also du bekommst die Zahl in einem ASCII-Array geliefert und willst die 
nach float konvertieren? Oder hab ich das falsch verstanden?

von Chris T. (chris0086)


Lesenswert?

ja genau aber eben in dem Format Format MEEE EMMM MMMM MMMM

von holger (Gast)


Lesenswert?

strtof()

von H.J.Seifert (Gast)


Lesenswert?

Und das soll da wieder als ASCII oder in interner (Binär)-Codierung 
stehen?

von Chris T. (chris0086)


Lesenswert?

in Binär Codierung eben in dem Format MEEE EMMM MMMM MMMM
M - Vorzeichen EEEE - Exponent mmmmmmmmmmm - Mantisse

von Karl H. (kbuchegg)


Lesenswert?

Was ist da jedes einzelne E bzw. M?
Ein Bit oder ein CHaracter.

Zunächst mal würde ich mir aus der ASCII Darstellung der Zahl, die du 
hast einen richtige Zahl machen, damit da auch gerechnet werden kann.
atof (atoi?) oder dtostrf ist hier dein Freund.

Liegt die Zahl erst mal binär im Speicher, dann wird erst mal Mantisse 
und Exponent bestimmt und davon dann die Bitdarstellung zusammengebaut. 
Du hast 11 Mantissenbits. d.h. die Mantisse kann nicht größer als 2048 
sein.

FloatValue = (0,01*M)*2(E)

Du hast den Float Value und suchst daher die beiden Zahlen M und E, so 
dass obige Gleichung erfüllt ist. Denk dir was aus! Es wird auch 
sinnvoll sein, M möglichst groß zu wählen. Auf jeden Fall aber so, dass 
E möglichst ganzzahlig wird. Und dann werden E und M in Bits 
zurechtgeschoben und das Endergebnis zusammengebaut.

von Klaus W. (mfgkw)


Lesenswert?

Ich glaube nicht, daß atof etc. hier angesagt sind.
Eher vermute ich, daß das eine reine Übungsaufgabe ist und er das
per pedes programmieren soll. Sonst würde doch niemand eine
Gleitkommadarstellung in 2 Byte machen...

(Weiterhin vermute ich, daß er das selbst machen sollte, oder
bekommen wir dann den Schein, wenn es funktioniert?)

von Peter D. (peda)


Lesenswert?

Wenn ich das richtig verstehe, will er sich ein 16-Bit float basteln.

Das muß man wohl zu Fuß machen, ich kenne jedenfalls keinen Compiler, 
der das kann.
Üblich sind 32-, 64- oder 80-Bit floats.

Manche PIC-Compiler sollen wohl auch ein 24-Bit float haben, aber das 
ist schon hart an der Grenze der Sinnhaftigkeit.


Peter

von Karl H. (kbuchegg)


Lesenswert?

Es könnte schon eine reale Aufgabe sein.
Immerhin gibt es die Beschränkung auf: nur ganzzahlige Ausgangswerte.

> Wie kann ich die Zahl -27 in das Format MEEE EMMM MMMM MMMM am
> effizientesten umwandeln?

Daher jetzt von mir die süffisante Gegenfrage:
Welche nicht effiziente Lösung hast du denn bisher?


Hint: Wenn wir das Vorzeichen einmal weglassen, so kommt mir auf Anhieb 
ein C 3 Zeiler in den Sinn, sobald die ASCII Zahl erst mal mit atoi in 
einen 4-Byte int umgewandelt worden ist.
Hint2: Er hat damit zu tun, dass bei Erhöhung des Exponenten um 1, die 
Mantisse halbiert wird um die Zahl nicht zu verändern (Im Rahmen der 
Genauigkeit). Dazu noch die Forderung, dass die Mantisse kleiner als 
2048 sein muss und schon ergibt das eine schöne kleine Schleife die im 
wesentlichen aus einem Vergleich, einem Rechtsschieben und einem 
Inkrement besteht. Dann in der 3. Zeile die Einzelteile schön 
zurechtschieben und zusammenodern.
Mit Vorzeichen wirds noch ein klein wenig komplizierter aber zur Not 
kann man ja auch 2 Fallunterscheidungen für positiv und negativ machen.

von Chris T. (chris0086)


Lesenswert?

Hallo Leute ich wollte mich mal wieder zu Wort melden, da ich diese 
Umwandlung jetzt benötige.
Danke für eure Hinweise.
Also erstens: Das Vorzeichen soll erstmal außer acht bleiben das wollt 
ich am ende lösen:
Das Vorzeichenbit auf 1 und Mantissenbits umdrehen.

Ich habe bereits einen Ansatz
1
exponent=0x3800;  // mit Exponent 7 wird angefangen
2
eis5=lux>>1;
3
eis5+=lux>>2;
4
eis5+=lux>>5;
5
while (eis5lux > 0x07FF)
6
{  // Exponent erhöhen falls Mantisse zu groß
7
 eis5lux=eis5lux>>1;
8
 exponent+=0x0800;
9
 }
10
eis5+=exponent;
Leider passt der Code noch nicht richtig. Bei der Zahl 450000 kommt nach 
der obigen Rechnung 7D5D raus, aber richtig sollte sein 46DD.
Aber das ich nach rechts shiften muss hab ich schonmal kapiert :-)

Könntet ihr mir noch ein paar Tips geben?
Das ist übrigens nicht für irgend nen Schein sondern für mich Zuhaus.
Es sollen Temperaturen über den Knx Bus gehen und die sind nunmal so 
kodiert.

von Karl H. (kbuchegg)


Lesenswert?

Christian Hohmann schrieb:

> Könntet ihr mir noch ein paar Tips geben?

Es gibt nicht DAS GLEITKOMMAFORMAT.
Sondern jeder kocht da sein eigenes Süppchen.

Solange du also keine Beschriebung lieferst, wie das Zielformat aussehen 
muss, kann dir keiner helfen.

exponent=0x3800;  // mit Exponent 7 wird angefangen

Ich würde das erst mal alles in einem unsigned char ganz normal 
abhandeln. D.h. Exponent beginnt bei 0 und wird um 1 hochgezählt / 
verringert. Um den ganzen Sonderkram, wie Versatz um 7, Shifting an die 
richtige Position etc. würde ich mich erst nachher kümmern, wenn die 
tatsächlich zu übertragenden Bytes zusammengesetzt werden.
Dann kann man sich auch die Einzelteile zb über UART ansehen und besser 
nachvollziehen, wie das Endergebnis in der Schleife entsteht.
Manchmal ist es nicht besonders schlau, gleich zu clever zu sein und 
zuviel auf einmal machen zu wollen.

  eis5lux=eis5lux>>1;

Schreib /2 wenn du /2 meinst.

Was berechnet diese Sequenz?

eis5=lux>>1;
eis5+=lux>>2;
eis5+=lux>>5;

kannst du das auch in normaler Arithmetik ausdrücken? Wie gesagt: 
Manchmal ist es nicht schlau, zu clever zu sein.

von Andreas F. (scotty1701d)


Lesenswert?

Christian Hohmann schrieb:
> Leider passt der Code noch nicht richtig. Bei der Zahl 450000 kommt nach
> der obigen Rechnung 7D5D raus, aber richtig sollte sein 46DD.

Heißt das, das Programm liefert 7D5D? Wieso sollte 46DD richtig sein?

Ich erhalte von Hand für 450000 den Wert 7D5D.
46DD entspräche dann 4497,92.

von Chris T. (chris0086)


Lesenswert?

Danke das ihr euch noch mit mir beschäftigt.
> Solange du also keine Beschriebung lieferst, wie das Zielformat aussehen
> muss, kann dir keiner helfen.

Steht ganz oben:
Zielformat
2byte aufgeteielt in :
VEEE EMMM MMMM MMMM : V=Vorzeichen; E=Exponent; M= Mantisse

Also hier nochmal worum es geht mit einem Beispiel:

To encode the value -30°C with a precision of 1/100:
  - first rewrite the value
  - 30°C = -3000.1/100°C (precision determined implicitly by the value)
  - the 11 bit mantissa allows a maximum value of 2047 to be encoded;
    in this case, we will
    have to introduce an exponent of 2^1, since 3000 > 2047:
    3000 = 1500 * 2^1
         mantissa exponent

1500 is encoded as
  0  101 1101 1100
sign mantissa
- changing the sign as of 1500 to -1500 gives:
  1  010 00100100
sign mantissa
- as the final result we get:
S E E E E M M M M M M M M M M M   Value
1 0 0 0 1 0 1 0 0 0 1 0 0 1 0 0   -3000

So soll das aussehen.
Daraus schließe ich wenn ich z.Bsp. Eine Temperatur von 110° habe, muss 
ich erstmal 2 Nullen dran hängen, also 11000 und diese Zahle dann in das 
geforderte Format umwandeln.

Joa dann hat mir jemand das so mit dem Shiften gesagt das ich das so 
machen soll. also das ein shift um 2 nach rechts die zahl halbiert weis 
ich, warum da aber auch nach 5 rechts geshiftet wird konnt ich mir auch 
nicht erklären.
Deswegen hab ichs ja geschrieben um euch zu fragen. ich hoffe jetzt wird 
klarer was ich vor hab.

Ist das schonmal richtig das geprüft wird ob die Zahl größer als 0x07FF 
ist? denn dann müsste der exponent erhöht werden und die zahl kleiner 
sein. Hofe soweit hab ich schonmal richtig gedacht.

von Chris T. (chris0086)


Lesenswert?

Achso eh ichs vergesse wie ich auf die hexwerte komme die eigentlich 
rauskommen sollten:
http://www.tapko.de/index.php/Tools/31/0/ bei dpt9 kann man seinen Wert 
eintragen und sieht dann rechts den hexwert

von Karl H. (kbuchegg)


Lesenswert?

Christian Hohmann schrieb:
> Danke das ihr euch noch mit mir beschäftigt.
>> Solange du also keine Beschriebung lieferst, wie das Zielformat aussehen
>> muss, kann dir keiner helfen.
>
> Steht ganz oben:
> Zielformat
> 2byte aufgeteielt in :
> VEEE EMMM MMMM MMMM : V=Vorzeichen; E=Exponent; M= Mantisse

Mein Fehler.

> Also hier nochmal worum es geht mit einem Beispiel:
>
> To encode the value -30°C with a precision of 1/100:
>   - first rewrite the value
>   - 30°C = -3000.1/100°C (precision determined implicitly by the value)
>   - the 11 bit mantissa allows a maximum value of 2047 to be encoded;
>     in this case, we will
>     have to introduce an exponent of 2^1, since 3000 > 2047:
>     3000 = 1500 * 2^1
>          mantissa exponent
>
> 1500 is encoded as
>   0  101 1101 1100
> sign mantissa
> - changing the sign as of 1500 to -1500 gives:
>   1  010 00100100
> sign mantissa
> - as the final result we get:
> S E E E E M M M M M M M M M M M   Value
> 1 0 0 0 1 0 1 0 0 0 1 0 0 1 0 0   -3000
>
> So soll das aussehen.
> Daraus schließe ich wenn ich z.Bsp. Eine Temperatur von 110° habe, muss
> ich erstmal 2 Nullen dran hängen,

Wegen dem hier
To encode the value -30°C with a precision of 1/100

> also 11000 und diese Zahle dann in das
> geforderte Format umwandeln.
>
> Joa dann hat mir jemand das so mit dem Shiften gesagt das ich das so
> machen soll.

Grundsätzlich.
Schreib mathematische Sachen so hin, wie DU das gut lesen kannst. Jeder 
Compiler auf diesem Planeten, der über das Prototypen Stadium hinaus 
ist, wird dir eine Division durch 2 durch einen Shift nach rechts 
ersetzen, WENN die Shift Operation auf der Zielhardware tatsächlich 
schneller ist. Das ist nichts worüber man sich Gedanken machen müsste. 
Als Programmierer wählt man die Operation so, dass sie für einen 
Menschen die klarste Ausdrucksweise darstellt und der Compiler macht den 
Rest.

> also das ein shift um 2 nach rechts die zahl halbiert weis
> ich, warum da aber auch nach 5 rechts geshiftet wird konnt ich mir auch
> nicht erklären.

das ist eine Division durch 32.

Diese Sequenz
eis5=lux>>1;
eis5+=lux>>2;
eis5+=lux>>5;

ist identisch zu

   eis5 = lux / 2  + lux / 4 + lux / 32;

nun ist das verständnismässig wahrscheinlich auch noch nicht klarer, 
warum das hier passiert, aber es ist zumindest ein Schritt in die 
Richtung, den Leser auf die Spur zu bringen. Das ist irgendeine 
Approximation einer Division, ohne tatsächlich Dividieren zu müssen.
1/2 + 1/4 + 1/32 ergibt 1 / 1.28

Jetzt musst du in deinem Datenblatt weitersuchen, wo eine Division durch 
diesen Wert (oder ein Wert der sehr nahe an dem liegt) zur Umrechnung 
von Werten gebraucht wird

> Ist das schonmal richtig das geprüft wird ob die Zahl größer als 0x07FF
> ist?

Sieht gut aus. Das steht auch hier

have to introduce an exponent of 2^1, since 3000 > 2047:

2047 ist 0x07FF

> denn dann müsste der exponent erhöht werden und die zahl kleiner
> sein.

Ganz genau.
Jedesmal wenn du den Exponent um 1 erhöhst, halbiert sich die Mantisse.

  25    * 2 hoch 0    =  25.0   * 1 = 25
ist dasselbe wie
  12.5  * 2 hoch 1    =  12.5   * 2 = 25
was wiederrum identisch ist zu
  6.25  * 2 hoch 2    =   6.25  * 4 = 25
  3.125 * 2 hoch 3    =   3.125 * 8 = 25
etc.

Wie ich weiter oben schon gesagt habe: Ich würde da nicht großartig 
künsteln, sondern die Teile einzeln berechnen ohne zunächst auf die 
Bitpositionen zu schauen.
1
   Mantisse = Wert;
2
   Exponent = 0;
3
4
   while( Mantisse > 2047 ) {
5
     Mantisse /= 2;
6
     Exponent += 1;
7
   }
8
9
   Ergebnis = ( Mantisse & 0x07FF ) |          // zur Sicherheit nochmal beschneiden
10
              ( ( Exponent & 0x0F ) << 10 ) |
11
              ( SignBit << 15 )

Ums Vorzeichen musst du dich noch kümmern.

von Andreas F. (scotty1701d)


Lesenswert?

Andreas Fritsche schrieb:
> Christian Hohmann schrieb:
>> Leider passt der Code noch nicht richtig. Bei der Zahl 450000 kommt nach
>> der obigen Rechnung 7D5D raus, aber richtig sollte sein 46DD.
>
> Heißt das, das Programm liefert 7D5D? Wieso sollte 46DD richtig sein?
>
> Ich erhalte von Hand für 450000 den Wert 7D5D.
> 46DD entspräche dann 4497,92.

Jetzt wird mir langsam alles klar:
Dein Programm tut tatsächlich das richtige. Du willst doch gar nicht 
450000 umwandeln sondern 4500. Dazu wird der Wert zuerst durch 1.28 
dividiert, d.h. mit 100 multipliziert (wie in der Spezifikation 
gefordert) und durch 128 dividiert (damit die Zahlen klein bleiben, 
sonst müsste man 32bit-Arithmetik werwenden). Wegen der Division 
(128=2^7) fängst du mit dem Exponenten 7 an.
Also ist doch alles in Ordnung.

Da die Auflösung dieses Formates in diesem Bereich 2.56 beträgt, werden 
die 4500 genau wie meine obige 4497,92 zum selben Wert 46DD quantisiert. 
Erst bei 4500.48 wird der nächste Wert 46DE angenommen.

von Andreas F. (scotty1701d)


Lesenswert?

Karl heinz Buchegger schrieb:
>
> Grundsätzlich.
> Schreib mathematische Sachen so hin, wie DU das gut lesen kannst. Jeder
> Compiler auf diesem Planeten, der über das Prototypen Stadium hinaus
> ist, wird dir eine Division durch 2 durch einen Shift nach rechts
> ersetzen, WENN die Shift Operation auf der Zielhardware tatsächlich
> schneller ist. Das ist nichts worüber man sich Gedanken machen müsste.
> Als Programmierer wählt man die Operation so, dass sie für einen
> Menschen die klarste Ausdrucksweise darstellt und der Compiler macht den
> Rest.
Nun ja, ich habe leider die leidvolle Erfahrung gemacht, dass manche 
Compiler für uC das NICHT können. Da muss man dann auch mal eine 
Berechnung in einzelne Statements zerlegen (z.B. CC5X).

>
> Ums Vorzeichen musst du dich noch kümmern.
Das ist ja kein Problem mehr:
1
...
2
while (eis5 > 0x07FF)
3
{  // Exponent erhöhen falls Mantisse zu groß
4
  eis5=eis5>>1;
5
  exponent+=0x0800;
6
}
7
if(negativ)
8
  eis5=0x8800-eis5;  // Mantisse invertieren
9
eis5+=exponent;
10
11
...
Ich habe hier die (offenbar falschen) Stellen, an denen eis5lux stand, 
geändert.

von Karl H. (kbuchegg)


Lesenswert?

Andreas Fritsche schrieb:

>> Als Programmierer wählt man die Operation so, dass sie für einen
>> Menschen die klarste Ausdrucksweise darstellt und der Compiler macht den
>> Rest.
> Nun ja, ich habe leider die leidvolle Erfahrung gemacht, dass manche
> Compiler für uC das NICHT können.

Das muss dann aber ein sehr mieser Compiler sein.
Eine derartige Umformung hat bereits der erste C-Compiler gemacht, den 
ich vor 25 Jahren benutzt habe.

> Da muss man dann auch mal eine
> Berechnung in einzelne Statements zerlegen (z.B. CC5X).

Dann ist er allerdings wirklich mies.

> Dazu wird der Wert zuerst durch 1.28
> dividiert, d.h. mit 100 multipliziert (wie in der
> Spezifikation gefordert) und durch 128 dividiert (damit die
> Zahlen klein bleiben, sonst müsste man 32bit-Arithmetik werwenden).
> Wegen der Division (128=2^7) fängst du mit dem Exponenten 7 an.

(klatsch. Flache Hand auf die Stirn)
Aber natürlich! So erklärt sich auch der Exponent von 7.
Der hat mich schon sehr verwirrt.

(Alles in allem, ist das ein Zusammenhang, der sich einen Kommentar im 
Code verdient hätte)

von Andreas F. (scotty1701d)


Lesenswert?

Karl heinz Buchegger schrieb:
>> Da muss man dann auch mal eine
>> Berechnung in einzelne Statements zerlegen (z.B. CC5X).
>
> Dann ist er allerdings wirklich mies.
Naja, ist eben die (kostenlose) Light-Version des CC5X-Compilers. Mit 
dem hatte ich meine ersten Gehversuche mit uCs (PIC12F509) gemacht. Ich 
meine mich aber zu erinnern, dass er bei Multiplikation mit Konstanten 
selber shiftet und addiert. Division durch 1.28 hätte er jedenfalls 
nicht hingekriegt (aber auch kein anderer Compiler ;-)

> (klatsch. Flache Hand auf die Stirn)
> Aber natürlich! So erklärt sich auch der Exponent von 7.
> Der hat mich schon sehr verwirrt.
Ja, aber ich habe nochmal drüber nachgedacht: Bei Zahlen unter 1310.72 
liefert das Programm ungenaue Ergebnisse, da das Programm keine 
Exponenten unter 7 erzeugen kann, wodurch am Ende Bits fehlen.
Es wäre ganz schön, wenn der OP uns sagen könnte, welchen Datentyp er 
für "lux" und "eis5" verwendet, denn der Mamimalwert von 670760 passt in 
16 Bits ja auch nicht rein.

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.