Forum: Mikrocontroller und Digitale Elektronik Feedback zum Artikel "Plattformunabhängige Programmierung in C"


von Fabian O. (xfr)


Lesenswert?

Hallo zusammen,

ich weiß, ich bin etwas spät dran, aber möchte Euch vor Abgabeschluss 
noch nach etwas Feedback zu meinem Artikel fragen:
http://www.mikrocontroller.net/articles/Plattformunabh%C3%A4ngige_Programmierung_in_C

Johann L. hat mir auf der Diskussionsseite schon einige Punkte 
geschrieben (Vielen Dank dafür!), die ich größtenteils eingearbeitet 
habe. Aber vielleicht gibt es ja noch weitere Verbesserungsvorschläge 
oder Aspekte, die noch erwähnt werden sollten.

Insbesondere ist mir wichtig, dass im Artikel keine groben 
Falschaussagen stehen. Ich habe zwar nach bestem Gewissen recherchiert, 
bin aber natürlich nicht unfehlbar. Ich würde mich daher sehr freuen, 
wenn die C-Experten mal einen Blick drauf werfen könnten.

Aber auch für allgemeines Feedback von jedem Interessierten wäre ich 
sehr dankbar: Ist alles verständlich formuliert, ist etwas zu kurz oder 
zu ausführlich? Vermisst Ihr irgendwo Erklärungen oder Links?

Ans Ende kommt noch ein kurzes Kapitel, wie man plattformabhängigen und 
plattformunabhängigen Code voneinander trennen kann und eine 
Zusammenfassung mit den wichtigsten Empfehlungen. Das schreibe ich 
morgen ...

Ansonsten ist es inhaltlich aus meiner Sicht jetzt ziemlich vollständig.

Also, vielen Dank schon mal fürs Lesen. :)

von Anja (Gast)


Lesenswert?

Fabian O. schrieb:
> Also, vielen Dank schon mal fürs Lesen. :)

Hallo,

ich sehe das ganze eher als Nachschlagewerk
und muß gestehen daß ich es nur überflogen habe.
Das ist wahrscheinlich einer der umfangreichsten Artikel im Wettbewerb.

Was ich noch vermisse sind so die typischen Fallstricke von C wie z.B. 
das rechts-shiften von signed Größen.
Aber möglicherweise ist das ja im Kapitel Kapselung dann enthalten.

Gruß Anja

von Passwort vergessen (Gast)


Lesenswert?

was ich nicht schön finde sind diese Codefragmente als Beispiele - okay 
man kann sich denken was man dazu basteln muß um ein lauffähiges 
Programm zu bekommen und das ist ja wohl mittlerweile gängige Praxis in 
der Erklärung, ich persönlich hasse es trotzdem.
Du solltest wenigstens ein lauffähiges Beispiel bringen, sonst wird es 
für Anfänger schwierig.
Compilierungsvorgang für unterschiedliche Platformen wäre auch noch eine 
nette Ergänzung.
Ergänzen könnte man auch noch einige andere Sachen, aber das würde den 
Rahmen sprengen - insgesamt ganz okay.

von Passwort vergessen (Gast)


Lesenswert?

noch was: ich weiß nicht wer Deine Zielgruppe ist - der erfahrene 
Programmierer, der Anfänger, beide?
Wenn Du Fachtermini einführst, sollten die immer kurz erklärt werden, 
was Du nur teilweise machst (z.B. Literale war okay).
Was ist ein Cast, eine Union, etc.?
Die Unterschiede zwischen den Standards und den Möglichkeiten von C99 
hast Du gut erklärt, dafür ein Lob ;-)

von Fabian O. (xfr)


Lesenswert?

Danke für Eure Antworten.

Anja schrieb:
> ich sehe das ganze eher als Nachschlagewerk
> und muß gestehen daß ich es nur überflogen habe.
> Das ist wahrscheinlich einer der umfangreichsten Artikel im Wettbewerb.

Ja, war aber eigentlich gar nicht mein Ziel ... Bin selber erstaunt, wie 
viel da zusammengekommen ist. Aber wenn man mal anfängt, diese ganzen 
Sachen aufzuschreiben, fallen einem eben doch eine Menge Dinge ein, die 
man erwähnen sollte. Deshalb kommt noch die Zusammenfassung ans Ende, 
dass man das wichtigste noch mal als kompakten Überblick/Erinnerung hat.

> Was ich noch vermisse sind so die typischen Fallstricke von C wie z.B.
> das rechts-shiften von signed Größen.

Stimmt, dem Thema signed/unsigned könnte man noch ein paar Sätze 
spendieren. Allerdings soll der Artikel auch nicht jeden möglichen 
Programmierfehler, der zu undefiniertem Verhalten führt, auflisten (i = 
i++ etc.), sondern nur die Dinge, die plattformspezifische Gründe haben. 
Sonst wird das noch viel viel länger ... ;)

Passwort vergessen schrieb:
> noch was: ich weiß nicht wer Deine Zielgruppe ist - der erfahrene
> Programmierer, der Anfänger, beide?

Ich glaube das ist der Punkt: Der Artikel ist kein C-Tutorial für 
Anfänger, auch wenn es ganz am Anfang vielleicht so klingt (Erklärung 
der Integer-Typen). Er richtet sich an Programmierer, die schon C 
können, aber sich noch nicht mit plattformspezifischen Eigenheiten und 
Portierung von Code beschäftigt haben, sondern bisher immer zufrieden 
waren, wenn der Code auf ihrem Mikrocontroller funktioniert hat.

Deshalb setze ich voraus, dass der Leser grundsätzlich weiß, wie Shifts, 
Casts, Zeiger, Unions usw. funktionieren. Gleiches gilt für die 
Beispiele: Ich setze voraus, dass der Leser schon den Kenntnisstand hat, 
sich vorzustellen, wie sie in einem realen Programm aussehen. Werde das 
am besten am Anfang des Artikels noch klarer erwähnen.

Ist vielleicht der Titel "Plattformunabhängige Programmierung in C" 
missverständlich? Also à la "Programmierung in C (unabhängig von einer 
Plattform erklärt)"? Denn das ist nicht gemeint, sondern es geht um 
plattformunabhängigen Code, nicht wie man C programmiert. Hmmm ... hat 
jemand einen besseren Vorschlag für den Titel?

Auf jeden Fall Danke, dass Du das angesprochen hast. :)

von W.S. (Gast)


Lesenswert?

Fabian O. schrieb:
> etwas Feedback zu meinem Artikel...

Nun ja, dein Ziel ist m.E. ein bissel zu hoch gesteckt:
"Ziel ist es, Softwaremodule ohne Änderungen auf möglichst jeder 
Hardwareplattform einsetzen zu können, vom 8-Bit-Mikrocontroller bis hin 
zum 64-Bit-PC."

Wir reden hier von Mikrocontroller-Anwendungen, ja? Die sind eigentlich 
fast immer so hardwarenah, daß die simple formale Übersetzbarkeit von 
Quellen eher in den Hintergrund tritt. Viel wichtiger ist es, die zu 
einem Projekt gehörigen Quellteile thematisch und inhaltlich so zu 
gruppieren und mit sinnvollen Schnittstellen zu versehen, daß sich 
daraus eine Weiterverwendbarkeit diverser Teile ergibt. Leider sieht man 
auch in diesem Forum Unmengen von Quellcode, der das ziemliche Gegenteil 
darstellt, obwohl deren Autoren allen "modernen" Schnickschnack a la 
uint16_t usw. benutzt haben und nun glauben, ihr Code sei damit bestens 
portabel.

Noch ein Wort zu C99 und zu den Unsäglichkeiten, die damit aufgekommen 
sind, insbesondere zu den Integer-Verrenkungen: An der eigentlichen 
Sprache ist damals überhaupt nix geändert worden. Die Compiler sehen 
nach wie vor nix anderes als char, int, long und evtl. short sowie 
eventuellen Präfixen. All die "neuen" Typen wie uint16_t und so sind 
keine echten neuen Typen, sondern lediglich Header-Akrobatik und damit 
genauso gut oder schlecht wie Typen a la U8, S16, U32 oder sonstwelche 
Eigendefinitionen, die sich jeder selbst nach eigenem Geschmack anlegen 
kann. Das sollte man mal wirklich klarstellen.

Was die portable Benutzung von char, int, short, long und int64 aka long 
long betrifft, so hast du die bereits implizit beschrieben. Man muß 
deine Tabelle lediglich mal als Logiker betrachten. Huch? Ja. Also die 
da stehende Aussage lautet z.B. "der C Standard garantiert lediglich 
folgende Mindestbreiten.."  z.B. für int 16 Bit. Das heißt im logischen 
Umkehrschluß und etwas anders formuliert so:

"Ein int ist garantiert 16 Bit breit und man darf sich keinesfalls 
darauf verlassen, daß der int auch nur ein einziges lumpiges Bit größer 
ist als 16 Bit. Für den Programmierer ist somit der int also exakt 16 
Bit breit."

Das gilt auch dann, wenn auf manchen Maschinen in der CPU automatisch 
breiter gerechnet wird, wie das z.B. bei ARM-Maschinen der Fall ist. Was 
in diesem Zusammenhang viel wichtiger ist, ist der Umstand, daß manche 
Maschinen ausgerichtete ("aligned") Daten benötigen und andere nicht. 
Das schafft beim Portieren von Strukturen viel größere Probleme als das 
alberne Herumgehacke auf den Integer-Bezeichnungen. Ähnlich sieht es mit 
Little und Big Endian Maschinen aus.

Das sind alles viel delikatere Portierungsprobleme. Dein "Einer der 
fundamentalsten Unterschiede zwischen.." muß man also etwas 
relativieren.

Ebenso muß man den Passus über Pointerdifferenzen relativieren: Die 
schiere Bitanzahl ist ja nicht unwichtig, aber gerade bei 
Mikrocontrollern haben wir sehr oft ganz andere Probleme damit, daß es 
Adreßraum-Inkompatibilitäten gibt. Bei vielen Maschinen muß man 
unterscheiden zwischen Pointern, die auf R/W-Daten zeigen und solchen, 
die auf R/O Daten zeigen, Stichwort Harvard versus v.Neumann. Da können 
2 Pointer numerisch völlig gleich sein und sachlich doch völlig 
unterschiedlich.

Zum Schluß noch ein Wort zum Casten: " Klarheit kann man, wie vorher, 
über einen Cast schaffen:... "

Nun, im Prinzip kann man es so tun, aber die meisten Leute casten falsch 
mangels Vorstellung der inneren Logik - und es sieht das Herumgecaste 
auch unästhetisch und unleserlich aus.
Beispiel: MyLong = (long)(MyByte<<8) | MyotherByte; --->ätschibäh!

Man sollte m.E. eher empfehlen, möglichst ohne casts auszukommen und 
die Optimierung dem Compiler zu überlassen:
MyLong = MyByte; MyLong = (MyLong<<8) | MyotherByte;
Funktioniert immer, braucht keine casts und gibt bei ordentlichem 
Compiler nen guten Code.

so, das waren erstmal meine Anmerkungen dazu.

W.S.

von Bronco (Gast)


Lesenswert?

Guter Artikel!
Ich würde noch auf den Aufbau der Software in Abstraktionsebenen 
eingehen, mit Verweis auf das OSI-Modell der Netzwerkschichten als gutem 
Beispiel.

von (prx) A. K. (prx)


Lesenswert?

W.S. schrieb:
> Für den Programmierer ist somit der int also exakt 16 Bit breit."

Das wär nun aber völlig daneben, da es sehr wohl grösser sein kann. Ob 
etwas in einer Datenstruktur nun 2 oder 4 Bytes beansprucht ist nicht 
immer völlig unwichtig.

Deine gediegene aber etwas einsame Aversion gegen int16_t&Co ist an 
anderer Stelle schon eingehend zur Sprache gekommen.

von (prx) A. K. (prx)


Lesenswert?

Bronco schrieb:
> Ich würde noch auf den Aufbau der Software in Abstraktionsebenen
> eingehen, mit Verweis auf das OSI-Modell der Netzwerkschichten als gutem
> Beispiel.

Wobei ich das eher als separaten Artikel sehe, da sich der Artikel auf 
Programmierung in C konzentriert, die Vorteile von Programmierung in 
Abstraktionsebenen aber - in schönster Abstraktion - völlig unabhängig 
von der Programmiersprache gelten.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Bei dem Tital "Plattformunabhängige Programmierung in C" wäre zunächst 
mal zu erläutern, was unter einer "Plattform" zu verstehen ist.

Der Artikel geht auf unterschiedliche Sprachversionen (C90, C99, C11, 
...) ein, aber unter einer "Plattform" wird wohl mehr verstenden, 
nämlich auch implementation-defined Behavior", d.h. Dinge, die im 
C-Standard zwar erwähnt sind, aber deren Definition an die 
Implementation (Compiler, Linker, LibC, Host-OS, ...) delegiert werden. 
Dazu gehören zB die Anzahl signifikanter Zeichen in Identifiern und 
Dateinamen, sizeof (int), etc.

Im GCC-Manual gibt es einen eigenen Abschnitt zu "implementation 
defined" und was GCC definiert.  I.W. delegiert GCC die Definition 
weiter: An die LibC, an die Binutils, an eine (E)ABI.

Anstatt direkt in medias res zu gehen, wäre ein Überblick zweckmäßig so 
daß man sich nicht direkt in den Details verliert.  Das Integer-Thema 
ist recht ausgewachsen, vielleicht würde ihm eine Straffung guttun so 
daß man nicht den Überblick verliert...

Beim Thema "Plattformunabhängigkeit" wäre auch ein Hinweis zu den Teilen 
eines Programms angebracht, die nicht portierbar sind.  Auch hier wäre 
ein kurzer Überblick sinnvoll, wie man diese nicht-portierbaren Teile 
möglichst praktikabel rausfaktorisiert.  Wie erkennt man zB die 
Plattform? Das Device? Die Architektur? Das Host-OS? Wie erkennt man die 
Sprachversion in der Quelle? Wie sollte man zB ISR-Implementierungen 
"Kapseln" oder SFR-Zugriffe.

Dies alles ist inheränt nicht-portierbar, d.h. die Kunst bei der 
Implementierung besteht in einer sauberen Trennung des portierbaren 
Teils und des nichtportierbaren Teils.  Hier sind auch Aspekte der 
Effizient zumindest zu erwähnen.

Die Frage ist auch, wie praxisrelevant eine komplette Portierbarkeit 
zwischen einem 64-Bit und einem 8-Bit System überhaupt ist.  Zwar gibt 
es zurzeit sowohl kleine 8-Bit µC als auch bereits homogene und 
heterogene Multicores (Freescale/ST PowerPC VLE, Infineon Aurix, ...) 
aber die Notwendigkeit einer Portierung in maximal vorstellbarer 
Bandbreite dürfte eher akademisch sein...

volatile + Bitfelder:  Das ist fürchterlich, weil es nicht spezifiziert 
ist und auch kaum spezifiziert werden kann.  Der C-Standard macht keine 
Aussage (weil er den Instruktionssatz nicht kennt) und der Compiler ist 
relativ frei, wie er das umsetzt.  Zb zwei volatile Bitfelder 
nebeneinander: Wenn ein Bitfeld zu ändern ist und es keine 
Bitinstruktion gibt, müssen zwangsläufig andere Felder 
mitgelesen/mitgeschrieben werden.

Ergo: Funger weg von volatile + Bitfelder, selbst wenn es um 
Devive-spezifische Header geht wie sie Atmel z.B. for XMEGA 
bereitstellt.  Das kann man zur Kenntnis nehmen, aber in dem Artikel 
würde ich nicht groß auf diese Schlangengrube eingehen.

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:
> Die Frage ist auch, wie praxisrelevant eine komplette Portierbarkeit
> zwischen einem 64-Bit und einem 8-Bit System überhaupt ist.

Da die Portierbarkeit zwischen 8-, 16- und 32-Bit Architekturen ein 
ziemlich aktuelles Thema ist, kommt es auf eine Vervollständigung durch 
64-Bitter nun auch nicht mehr an. Vom Adressraum abgesehen sind die dank 
dominierendem LP64/LLP64 auch nur falsche 32-Bitter, die besser mit 
64-Bit Typen umgehen können als echte 32-Bitter.

von Fabian O. (xfr)


Lesenswert?

Vielen Dank für die Kommentare. :)

Ich habe noch das Kapitel über die Aufteilung von Code geschrieben. Ist 
durch den vielen Quellcode etwas lang, aber es ist ja das letzte 
Kapitel. Wen es nicht interessiert, braucht also nicht mehr weiterlesen. 
Oder soll ich versuchen, es zu kürzen?

Ansonsten werde ich schaun, dass ich morgen noch eure Vorschläge einbaue 
hinsichtlich Überblick am Anfang geben etc. Falls ihr noch weitere 
Anregungen habt, es sind ja noch 24 Stunden Zeit ... :D

von Stefan W. (dl6dx)


Lesenswert?

Fabian O. schrieb:
> Ich habe noch das Kapitel über die Aufteilung von Code geschrieben.

Schau dir die Codebeispiele noch mal an, und zwar die Reihenfolge der 
#include-Statements.

Beispiel:
rfm70.h wird vor stdint.h eingebunden, enthält aber eine Deklaration 
mit einem stdint-Datentyp.

Grüße

Stefan

PS: Ansonsten macht der Artikel einen guten Eindruck.

von Entsetzter (Gast)


Lesenswert?

Es gibt keine "Fließkommazahlen", sondern nur "Gleitkommazahlen", auch 
wenn man der Mist oft liest...

Zur Info:

  float = gleiten, schwimmen, schweben, ...

  flow = fließen, strömen, ...

Wer "Fließkommanzahl" verwendet, übersetzt auch "bekommen" mit 
"become"...

von F. F. (foldi)


Lesenswert?

Würde das gern als PDF haben, wenn das möglich ist.

von chris (Gast)


Lesenswert?

Hallo Fabian,

es macht auf jeden Fall Sinn, eine Abstraktionsebene für die 
Platformunabhängigkeit einzuziehen. Auf professioneller Ebene gibt es 
bereichts einige Ansätze dazu. Bei den ARM-Prozessoren ist es die CSMIS:

http://www.arm.com/products/processors/cortex-m/cortex-microcontroller-software-interface-standard.php

Im Automotivebereich in Auotosar ist es die MCAL:
http://www.all-electronics.de/texte/anzeigen/24834/Erster-MCAL-gemaess-Autosar-Release-30-31

eine Diskussion zu diesem Thema habe ich hier im MC-Netz auch schon 
einmal angefangen:

Beitrag "BIOS für Mikrocontroller"

Die Antworten hierzu waren aber eher ernüchternd ...

Gruß,
chris

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ok, noch ein letztes Feedback :-)

Meterlangen Beispielcode finde ich persönlich nicht so hilfreich, der 
ist eher dazu angetan sich im Urwald zu verlieren und vom Hölzchen aufs 
Stöckchen zu kommen...

Ironischerweise ist der Code nicht gut portabel, für eine Arraylänge 
wird ein uint8_t verwendet, was aber ein size_t sein sollte.  Und es 
werden C++ Kommentare verwendet, die es erst in C99 gibt.

Daß bestimmte Compiler Konstrukte zum Packen (packed) oder zum Alignment 
(aligned, assume_aligned, alignedaccess, ...) haben, kann durchaus 
Erwähnung finden, darin baden würd ich aber nicht.  Eher angebracht sind 
hier ein paar Worte zu Serialisierung / Deserialisierung und wie diese 
abgebildet werden konnen.  Über die binärdarstellung der entsprechenden 
Objekte zu gehen ist nur eine Möglichkeit; (De)serislisierung per ASCII 
ist eine weitere.

Anstatt sich weiter in Codeschnippeln zu verlieren, wäre eine Abrundung 
des Artikels ein Blick auf die Portierbarkeit des von den Tools 
erzeugten Binärcodes.

Auf Host-Rechnern ist es zB unerlässlich, daß Code unterschiedlicher 
COmpilerhersteller (Microsoft, GCC, Intel, HP, ...) interoperabel ist, 
d.h. man kann zB mit GCC eine Anwendung erstellen, die zusammen mit DLLs 
von Microsoft operiert.

Hierzu muss natürlich das ABI passen, etwa Objektformat, Calling 
Convention, Linkage (C++ Demangling), etc.

Beim Thema "pached" von oben geht es genau um diese Binärkompatibilität, 
es geht nicht um "Quellkompatibilität".

Unter den AVR-Compilern ist es zB nicht so, daß man einfach Objekte 
(*.o) oder Bibliotheken (*.a) unterschiedlicher Hersteller mixen könnte. 
Selbst innerhalb der gleichen Tools können sich Inkompatibilitäten 
ergeben, etwa beim Wechsel von avr-gcc 4.6 zu avr-gcc 4.7.

Ein Hinweise und Anmerkungen dazu, um zumindest ein Problembewusstsein 
dafür zu schaffen, fände ich angebracht.

So geht es an vielen anderen Stellen des Artikela auch eher darum, die 
viele Fallstricke übersichtlich und in nicht-ermüdender Weise 
darzulegen, als sich im Klein-Klein zu verlieren.

von chris (Gast)


Lesenswert?

>Ironischerweise ist der Code nicht gut portabel, für eine Arraylänge
>wird ein uint8_t verwendet, was aber ein size_t sein sollte

Warum sollte man uint8_t nicht verwenden können?

von Fritz (Gast)


Lesenswert?

Habe mir den Text flüchtig angesehen und dabei folgendes gesehen:

Die interne Repräsentation von Fließkommazahlen, d.h. wie die einzelnen 
Bits bzw. Bytes im Speicher angeordnet sind, ist allerdings 
plattformabhängig! Die Typen float und double sind deshalb nicht für den 
Datenaustausch zwischen verschiedenen Geräten, sondern nur für interne 
Berechnungen geeignet. Wenn Fließkommazahlen über einen Datenbus bzw. 
Netzwerk übertragen werden sollen, empfiehlt sich die Wandlung in 
Festkommazahlen oder Strings.

Ich war bisher der Meinung, dass C-Compiler das IEEE_format verwenden 
und keinesfalls die bits irgendwie unterschiedlich verwenden. Dafür 
wurde doch das IEEE-Format geschaffen. Höchstens die endien-ness kann 
verschieden sein.
Unterschiedliche Formate von NAN oder INFINITY löst man aber nicht mit 
Wandlung in Festkommazahlen, auch hat eine Wandlung in Strings 
erhebliche Laufzeit- und Performance-Nachteile.

Eine Darstellung was wirklich unterschiedlich sein könnte, wäre sehr 
hilfreich.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

chris schrieb:
>>Ironischerweise ist der Code nicht gut portabel, für eine Arraylänge
>>wird ein uint8_t verwendet, was aber ein size_t sein sollte
>
> Warum sollte man uint8_t nicht verwenden können?

In dem Zusammenhang kann man es schon verwenden, und meine Kritik geht 
eher in Richtung defensive Programmierund als Portabilität.

Wenn man nämlich den Puffer vergrößert fliegt einem alles um die Ohren 
:-)

von Fabian O. (xfr)


Lesenswert?

@Fritz
Wikipedia sagt dazu:

> Die Anordnung der Bits einer single zeigt die nachfolgende Abbildung. Die
> bei einer Rechenanlage konkrete Anordnung der Bits im Speicher kann von
> diesem Bild abweichen und hängt von der jeweiligen Bytereihenfolge
> (little/big endian) und weiteren Rechnereigenheiten ab.

http://de.wikipedia.org/wiki/IEEE_754#Zahlenformate_und_andere_Festlegungen_des_IEEE-754-Standards

Im C-Standard ist es auf jeden Fall nicht definiert, da stehen nur die 
geforderten Mindestwertebereiche (die auch nicht unbedingt jeder 
Compiler einhält, siehe avr-gcc).

Mir fällt keine "legale" und effiziente Möglichkeit ein, an die korrekte 
Bitrepräsentation gemäß IEEE zu kommen. Es garantiert einem ja niemand, 
dass die gleiche Bytereihenfolge wie bei Integern verwendet wird, man 
also einen float in uint32_t casten kann und dann byteweise verschicken. 
Oder steht das im IEEE-Standard?

von Oliver (Gast)


Lesenswert?

Super, endlich wird mal etwas zur Integer Promotion geschrieben. Danke!

Etwas härter hätte ich den Text bei den "union"s formuliert. In den 
einen member etwas hineinzuschreiben und aus dem anderen wieder 
herauszulesen ist grober Unfug (auch wenn das in der Vergangenheit 
manche Programmier "cool" fanden). Tatsächlich genügt schon der Wechsel 
zu einem Prozessor mit einer anderen byte-Anordnung um ein 
"unerwartetes" Ergebnis zu bekommen.

Oliver

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Fabian O. schrieb:
> @Fritz
> Wikipedia sagt dazu:
>
>> Die Anordnung der Bits einer single zeigt die nachfolgende Abbildung. Die
>> bei einer Rechenanlage konkrete Anordnung der Bits im Speicher kann von
>> diesem Bild abweichen und hängt von der jeweiligen Bytereihenfolge
>> (little/big endian) und weiteren Rechnereigenheiten ab.
>
> Im C-Standard ist es auf jeden Fall nicht definiert, da stehen nur die
> geforderten Mindestwertebereiche (die auch nicht unbedingt jeder
> Compiler einhält, siehe avr-gcc).
>
> Mir fällt keine "legale" und effiziente Möglichkeit ein, an die korrekte
> Bitrepräsentation gemäß IEEE zu kommen. Es garantiert einem ja niemand,
> dass die gleiche Bytereihenfolge wie bei Integern verwendet wird, man
> also einen float in uint32_t casten kann und dann byteweise verschicken.
> Oder steht das im IEEE-Standard?

Nein, ein Cast geht auf keinen Fall, weil das den Wert verändert.

Der einzige Standard-konfirme Weg an die Bits eines Fließkomma-Wertes zu 
kommen ist memcpy.

Hilfreich beim (De)serialisieren sind ldexp, frexp, fpclassify etc.

Je nach Compiler gibt's auch Makros, die Typlayouts beschreiben. Im GCC 
etwa
1
$ echo | gcc -E -dM -x c - | grep FLT
2
#define __FLT_MIN__ 1.17549435e-38F
3
#define __FLT_EVAL_METHOD__ 2
4
#define __FLT_EPSILON__ 1.19209290e-7F
5
#define __FLT_HAS_DENORM__ 1
6
#define __FLT_MIN_EXP__ (-125)
7
#define __FLT_MANT_DIG__ 24
8
#define __FLT_RADIX__ 2
9
#define __FLT_HAS_QUIET_NAN__ 1
10
#define __FLT_MAX_10_EXP__ 38
11
#define __FLT_HAS_INFINITY__ 1
12
#define __FLT_DIG__ 6
13
#define __FLT_MAX_EXP__ 128
14
#define __FLT_DENORM_MIN__ 1.40129846e-45F
15
#define __FLT_MAX__ 3.40282347e+38F
16
#define __FLT_MIN_10_EXP__ (-37)

die aber auch nicht portabel sind, bzw. nur innerhalb verschiedener 
GCC-Inkarnationen.

GCC kennt übrigens mindestens 4 Arten von Endianess:

• Byte-Endianess: Innerhalb eines Word
• Word-Endianess: zB innerhalb eines 64-Bit Werts
• Bit-Endianess: Bitfelder
• float-Endianess

Daneben ist die Endianess / Alignment in unterschiedlichen 
Speicherklassen zu unterscheiden wie: Stack, Register, static Storage, 
etc.  IdR stimmen diese überein, können sich aber durchaus 
unterscheiden, zB wenn ein Wert auf den Stack gelegt wird (avr-gcc wegen 
byteweisem Push und weil der Stapel nach unten hin wächst).

Für den Artikel geht das aber IMO zu sehr ins Detail; Quintessenz ist 
daß aufgrund der Unterschiede eine 100% plattformübergreifende Lösung 
kaum praktikabel ist.

Zweckmäßiger erscheint es da, die Abhängigkeit explizit zu 
faktorisieren, z.B.
1
#if defined (__GNUC__) && defined (__AVR__)
2
/* avr-gcc */
3
#elif defined (__GNU__) && defined (__i386__) && __SIZEOF_DOUBLE__ == 4
4
/* GCC auf x86 mit -fshort-double
5
#else
6
#error Platform not handled yet.  Feel free to contribute
7
#endif

Allerdings gibt's auch da Probleme, etwa weil sich LLVM als GCC 
ausweist, d.h. __GNUC__  definiert...

Aber selbst wenn es nicht um Datenaustausch geht bleiben noch genügend 
arithmetische Probleme mit float et al.  Da man keine == verwenden soll 
—  etwa als Abbruchbedingung für iterative Algorithmen — stellt sich die 
Frage, wie dies unabhängig von der Plattform und der Genauigkeit bzw. 
dem Wertebereich zu formulieren ist.

Übrigens hat man Portierungsprobleme nicht nur im "reinen" C-Code, 
sondern auch im Präprozessor.  Hier können zB C99-Makros wie UINT32_C 
verwendet werden, die laut Standard in #if verwendet werden können — 
auch wenn manche libc-Implementierungen wie die AVR-LibC dem Standard 
nicht folgen indem sie die Makros als Cast bereitstellen, was natürlich 
nicht in #if verwendbar ist (Bug  #36571).

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Noch eins:  Wegen eines nicht-vorhandenen C99-Compilers Teile in C++ zu 
schreiben ist übel.

- C++ ist keine Obermenge von C

- C++ han andere Linkage, z.B Name-Mangling

Besser: Schnickschnack wie Mixen von Deklaration und Befehlen vermeiden 
oder auf C90 rückportieren.  Jede solche Mixtur kann durch Umstellung in 
gültiges C90 umgeschrieben werden.

von Fabian O. (xfr)


Lesenswert?

Hmm, Geschmackssache. Mich würde es ziemlich hart treffen, wenn ich 
Schleifenzähler nicht mehr in for-Schleifen deklarieren dürfte. Mag 
Schnickschnack sein, aber es macht den Code um einiges besser lesbarer, 
genau wie Kommentare mit zwei Schrägstrichen.

Da achte ich lieber drauf, dass mein C-Code auch C++-konform ist und 
passe ihn ggf. entsprechend an. Zugegebenermaßen habe ich bisher nicht 
darauf geachtet, weils doch recht unwahrscheinlich ist, dass mein 
AVR-Code mal unter Windows laufen soll. Aber die meisten 
Inkompatibilitäten zwischen C und C++ sehen mir recht harmlos aus bzw. 
widersprechen sowie ordentlich strukturiertem Code (z.B. Funktionen ohne 
Prototyp aufrufen).

Ich habe mal ein "zur Not" in den Satz eingefügt. Sofern der Code sich 
ohne große Änderungen in C++ kompilieren lässt, erscheint es mir 
jedenfalls als das kleineres Übel, den C++-Compiler zu verwenden, 
anstatt tausende Zeilen Quelltext auf "schlechter lesbar" umzuschreiben 
...

von Fritz (Gast)


Lesenswert?

Johann L. schrieb:
>> Mir fällt keine "legale" und effiziente Möglichkeit ein, an die korrekte
>> Bitrepräsentation gemäß IEEE zu kommen. Es garantiert einem ja niemand,
>> dass die gleiche Bytereihenfolge wie bei Integern verwendet wird, man
>> also einen float in uint32_t casten kann und dann byteweise verschicken.
>> Oder steht das im IEEE-Standard?
>
> Nein, ein Cast geht auf keinen Fall, weil das den Wert verändert.

Hallo nocheinmal.
Vielleicht bin ich zu blauäugig oder pragmatisch. Verstehe die diversen 
Warnungen nicht wirklich.

Habe mit einem STM32F4.. also mit FPU gerade eine Datenübertragung über 
ein UART programmiert, wobei ich das IEEE_format auf 6 Bytes aufteile 
und im PC wieder zusammensetze. Am PC in ein 32-bit integer und danach 
auf float gecastet. Im STM laut IEEE per Asssembler zerlegt und 
byteweise versandt. Ließe sich aber auch äquivalent in C durchführen.

Natürlich geht ein direktes casten int -> float nicht, aber sehrwohl ein 
casting über pointer:

float fl;
int32 data32;

fl = *((float*)&data32);

von Fabian O. (xfr)


Lesenswert?

Ja, so hatte ichs auch gemeint, war schlecht formuliert. Aber das bricht 
die Strict-Aliasing-Regel, steht auch im Artikel. Der Compiler darf Dir 
solche Konstrukte in C99 ggf. wegoptimieren. Wirklich legal ist es nur 
per memcpy().

Aber auch dann hast Du zwar die Bits des float, aber in relativ 
"willkürlicher" Reihenfolge. Das heißt, es hängt von den erwähnten 
Word/Byte/Bit-Reihenfolge der Plattform ab, wo welches Bit steht. Das 
kann prinzipiell auf jedem Prozessor anders aussehen.

Das heißt natürlich nicht, dass diese Vorgehensweise nie funktioniert. 
Wenn die Floats bei beiden Prozessoren mit der gleichen 
Bitrepräsentation im Speicher stehen, dann klappt es schon, keine Frage. 
Nur kannst Du Dich eben nicht drauf verlassen, dass sich der Code auch 
auf anderen Plattformen so verhält. -> Er ist nicht portabel.

Das ist ja genau der Punkt bei dem Artikel: Keines der Codebeispiele 
darin ist ein Fehler in dem Sinne, dass das nie funktioniert. Auf 
manchen Plattformen läuft das wunderbar. Nur wenn man dann mal den 
Compiler, Prozessor oder Betriebssystem wechselt, geht plötzlich nichts 
mehr und die Fehlersuche beginnt ...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Auch nicht korrekt wegen Aliasing-Rules von C denn Integer und Floating 
sind nicht kompatibel.

D.h. es kann davon ausgegangen werden, daß die Änderung eines Types 
keine Änderung eines nicht-kompatiblen Types bewirkt.

Es bedeutet zwar nicht, daß dies zu problemen führen muß aber es macht 
eben den Unterschied zwischen "robust bzw. standardkonfirm" und "Hack, 
der  in diesem Kontext funktioniert".

von Fritz (Gast)


Lesenswert?

Fabian O. schrieb:
> Aber auch dann hast Du zwar die Bits des float, aber in relativ
> "willkürlicher" Reihenfolge. Das heißt, es hängt von den erwähnten
> Word/Byte/Bit-Reihenfolge der Plattform ab, wo welches Bit steht. Das
> kann prinzipiell auf jedem Prozessor anders aussehen.

Also Word/Byte-Reihenfolge verstehe ich ja, dass sie je nach Plattform 
verschieden sein kann.

Aber Bit-Reihenfolge im Byte ist meiner Meinung nach wohl in allen 
Plattformen gleich. Sich da nicht daraf verlassen zu wollen ist etwas 
übertrieben.
4-bit uC`s gibts wohl nicht mehr wo das möglicherweise relevant wäre.

von Fabian O. (xfr)


Lesenswert?

Die Bitreihenfolge von Integern ist an sich transparent, weil man in C 
nur auf ganze Bytes zugreifen kann. Die sehen immer so aus, wie man es 
erwartet (links msb, rechts lsb), selbst wenn da ein 4- oder 
1-Bit-Prozessor hinterstecken würde. Ausnahme sind nur Bitfelder.

Nur garantiert einem niemand, dass Fließkommazahlen die gleiche 
Bitreihenfolge wie Integer verwenden (zumindest afaik, bitte korrigieren 
falls falsch). Sie könnten theoretisch also auch genau umgedreht sein, 
wenn die FPU des Prozessors des so brauchen sollte. Oder auch in 
Big-Endian statt Little-Endian im Speicher stehen, obwohl Integer auf 
dem Prozessor Little-Endian sind. In der Praxis wohl unwahrscheinlich, 
aber es wäre gemäß C-Standard.

Denn einen float hast Du legal nur in Rechenoperationen zu verwenden. Du 
kannst ihn mit einem Cast zurück in einen Integer konvertieren (also den 
ganzzahligen Wert bekommen), dann kümmert sich der Compiler um die 
nötigen Schritte, also die entsprechenden Maschinenbefehle auszuführen, 
dass Du das erwartetete Ergebnis bekommst. Aber wie er das im Speicher 
vollbringt, ist seine Sache. Auch mit memcpy bekommst Du nur Bits, die 
zu dem float gehören, aber keine garantierte Semantik dazu.

Es sei denn, der IEEE-Standard würde explizit vorschreiben, dass bei 
Interpretation der Bits als Integer gemäß der Integer-Bytereihenfolge 
der Plattform die richtige Float-Bitrepräsentation rauskommen muss. Dann 
könnte man sich bei einem IEEE-Standard-konformen Prozessor wirklich 
drauf verlassen. Ich weiß aber nicht, ob das da so drinnen steht.

von Fritz (Gast)


Lesenswert?

Fabian O. schrieb:
> Nur garantiert einem niemand, dass Fließkommazahlen die gleiche
> Bitreihenfolge wie Integer verwenden (zumindest afaik, bitte korrigieren
> falls falsch). Sie könnten theoretisch also auch genau umgedreht sein,
> wenn die FPU des Prozessors des so brauchen sollte.

Wenn man so wie ich eher von der Hardware kommt, ist diese Annahme, dass 
das tatsächlich in Silizium ausgeführt ist, ziemlich idiotisch. Jegliche 
Konvertierngen zwischen float und int wird nur zusätzlich erschweren. 
Die 23 bit Mantisse sind eben ein 24-bit int (inkl. implizite führende 
1) und das nicht an das 32-bit int format anzupassen bzw. zumindestens 
in den Bytes die bitreihenfolge gleich zu lassen, kann ich mir nicht 
vorstellen.
Da jetzt kompatibilitätsproblem heraufzubeschwören halte ich für 
übertrieben.
Rein theoretisch mag das ja stimmen.

Von Titel her erwarte ich mir wie man es wirklich plattformunabhängig 
machen kann. Die Empfehlung:

Wenn Fließkommazahlen über einen Datenbus bzw. Netzwerk übertragen 
werden sollen, empfiehlt sich die Wandlung in Festkommazahlen oder 
Strings.

ist halt nur sehr allgemein gehalten und geht nicht auf auch da mögliche 
Proble ein:

Manche Dezimalzahlen lassen sich im float format nicht exakt darstellen, 
daher kann es bei Umwandlung in Strings zu Rundungsproblemen kommen. NAN 
und INFINTY ... müssen auch richtig übertragen werden.

Also wie mache ich es plattformübergreifend?

von Fabian O. (xfr)


Lesenswert?

Fritz schrieb:
> Manche Dezimalzahlen lassen sich im float format nicht exakt darstellen,
> daher kann es bei Umwandlung in Strings zu Rundungsproblemen kommen.

Dieses Umwandlungsproblem entsteht doch bei der Umwandlung von Dezimal 
nach Float, nicht auf dem Rückweg. Wenn das ein Problem ist, darfst Du 
erst gar keinen Float verwenden. In einem String kannst Du jede 
beliebige Float-Zahl (und auch jede Dezimalzahl) darstellen.

> NAN und INFINTY ... müssen auch richtig übertragen werden.

Müssen sie das wirklich? Dann übertrag "NAN" und "INFINITY" als String. 
Oder denk Dir ein anderes Protokoll aus. Alternativ kannst Du Exponent 
und Mantisse getrennt als Integer übertragen. Johann hat ja oben die 
entsprechenden Funktionen genannt, mit denen man daran kommt. Das geht 
ohne Genauigkeitsverlust.

In der Praxis fällt mir aber ehrlich gesagt keine Anwendung ein, bei der 
man Fließkomma mit maximaler Genauigkeit zwischen eingebetteten Systemen 
übertragen muss. In der Regel hat man doch relativ klare Wertebereiche, 
in denen sich z.B. Messwerte abspielen. Die skaliert man auf einen 
passend großen Integer und gut ists.

Oder Du lebst eben damit, dass der Code für Deine Spezialanwendung nicht 
portabel ist. Aber wie gesagt, woher kommen denn die Floats? Die tauchen 
ja nicht auf natürliche Weise auf, sondern entstehen erst durch 
Berechnungen. Woher kommen die Eingabewerte? Sind die so exakt, dass man 
auf kein Mantissenbit verzichten kann?

von (prx) A. K. (prx)


Lesenswert?

Fritz schrieb:
> Wenn man so wie ich eher von der Hardware kommt, ist diese Annahme, dass
> das tatsächlich in Silizium ausgeführt ist, ziemlich idiotisch.

"Although the ubiquitous x86 of today use little-endian storage for all 
types of data (integer, floating point, BCD), there have been a few 
historical machines where floating point numbers were represented in 
big-endian form while integers were represented in little-endian 
form.[3] There are old ARM processors that have half little-endian, half 
big-endian floating point representation."

http://en.wikipedia.org/wiki/Endianness#Floating-point_and_endianness

> Also wie mache ich es plattformübergreifend?

Als ASCII-Darstellung mit einer Zweierpotenz statt 10 als Basis hat man 
Umrechnungseffekte von und zum üblichen Format vom Hals. Aber wenn C - 
was ich annehme - keine binäre Fliesskommadarstellung definiert, dann 
geht das theoretisch überhaupt nicht völlig verlustfrei, weil dann auch 
eine interne dezimale Fliesskommadarstellung und -rechnung zulässig wäre 
- sowas gibts in Hardware wirklich, auch heutzutage!

von Fabian O. (xfr)


Lesenswert?

Danke für den Link, hab ihn in den Artikel mit aufgenommen. Also ist es 
schon so, wie ich angenommen habe: Auch der IEEE-Standard sagt nicht, 
wie die Bytes intern zu speichern sind. Der C-Standard sowieso nicht, 
der spezifiziert wirklich nur dezimale Mindest-Wertebereiche.

von i-Troll (Gast)


Lesenswert?

Erstaunlich, was die Leute alles portabel haben wollen. Dabei sind die 
Anwendungen Welten voneinander weg. Lasst mich mal aufzaehlen, was man 
auf'm PC sicher nicht braucht, auf einem Controller aber eben schon.
- interrupts, Echtzeit konzepte, Ringbuffer, FIR & IIR filter, PID, 
Special function register, analog & digital IO, Tastengeschichten, 
Bootloader, ...
Dinge, die man aufm PC hat, aber auf einem Controller nicht braucht.
- Dynamisches Memory, Klassen- & Klassen bibliotheken, Betriebssystem

der Ueberlapp zwischen Controller und PC ist IMO relativ bescheiden. Zu 
bescheiden , um von Portabilitaet zu reden, resp zu verlangen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Es geht ja nicht nur um Portierbarkeit PC <-> µC-Hänfling sondern auch 
Portierbarkeit zwischen Boliden oder unterschiedlichen µCs.

Und wenn man sich High-End µCs anschaut, wo bereits fett ausgestattete 
Multicore-Maschinen (SIMD, Vektor-Instruktionen, HW-float, ...) den 
Alltag erreicht haben (Automotive), ist es zu den PC-Boliden nicht mehr 
sooo weit.

Ausserdem geht es in dem Artikel darum, mochglichst viele Facetten der 
Portierbarkeit von C Programmen zu beleuchten, hab ich zumindest so 
verstanden.

Der ganze Artikel wäre doch für die Katz, wenn bestimmte Aspekte 
willkürlich übergangen würden.

von Fritz (Gast)


Lesenswert?

Fabian O. schrieb:
>> NAN und INFINTY ... müssen auch richtig übertragen werden.
>
> Müssen sie das wirklich? Dann übertrag "NAN" und "INFINITY" als String.
> Oder denk Dir ein anderes Protokoll aus.

Wenn ich das mache, habe ich ein proprietäres Format das ich auf beiden 
Plattformen extra implementieren muß!
Da mache ich es lieber wie von mir vorher beschrieben über 
Pointercasting und implementiere ein byteumordnung falls wirklich 
notwendig.

Das funktioniert entweder auf Anhieb ohne Änderung oder das Problem 
sticht sofort ins Auge (in einem float vertauschte bits, bytes) und 
fallen so stark auf, die Byteumordnung ist dann aber nur mehr ein 
Klacks.

Plattformunabhängig ist weder das eine noch das andere.

Ich verstehe nicht warum man ein wirklich schön definiertes Format 
(IEEE) durch Zerstören (Stringumwandlung) und wieder Rekonstruieren 
besser behandelt, als wenn man sich die Unterschiede der Plattform 
anschaut und entsprechend umsetzt.

Zu meinen bisherigen Erfahrungen dazu. Mein Spezialgebiet ist die 
Strom-, Spannungs-, Leistungsmessung im Einphasen- und Drehstromnetzen.
Habe auf folgenden Plattformen mit floats gearbeitet:
68000 Kommunikation mit PC
Embedded Arm mit uLinux + TI 6713 floatingpoint DSP und Anbindung an PC

Hat aber dabei keine Plattformprobleme mit den floats gegeben, auch ohne 
UM- und Rückwandlung in von Strings.

Fabian O. schrieb:
> In der Regel hat man doch relativ klare Wertebereiche,
> in denen sich z.B. Messwerte abspielen. Die skaliert man auf einen
> passend großen Integer und gut ists.

Na ja?
Ich muss z.B. beim Strom mA bis KA behandeln, da entsprechende 
verschiedene Stromwandler/Shunts davor sitzen.
Bei der Spannung gehts auch von V bis MV (für EVUs).
Da mit einem float zu arbeiten ist wesentlich konfortabler als 2 Integer 
für Meßwert und Skalierungsfaktor. Habe beides in der Praxis gemacht und 
weiss wovon ich spreche.

Zu der von dir angesprochenen Auflösung ob 24-bit wirklich notwendig 
sind.
Wenn man einiges an Mathematik (Winkelfuntionen, Wurzel ..) auch noch 
braucht muß man doch auch bei 32-bit int verdammt aufpassen nichts an 
Genauigkeit zu verlieren.

von Fabian O. (xfr)


Lesenswert?

Fritz schrieb:
> Ich verstehe nicht warum man ein wirklich schön definiertes Format
> (IEEE) durch Zerstören (Stringumwandlung) und wieder Rekonstruieren
> besser behandelt, als wenn man sich die Unterschiede der Plattform
> anschaut und entsprechend umsetzt.

Der Artikel geht eben genau darum, was man beachten muss, dass der 
gleiche C-Code auf verschiedenen Plattformen ohne Änderung läuft. 
Nicht, wie man am effizientsten Fließkommazahlen überträgt. Beides 
gleichzeitig geht eben leider nicht. Wenn es Dir nichts ausmacht, den 
Code für einen anderen Prozessor ggf. ändern zu müssen, hält Dich ja 
niemand davon ab, es so zu machen. Außer, dass Du wie gesagt besser 
memcpy() statt dem Pointercast nehmen solltest.

Fritz schrieb:
> Da mit einem float zu arbeiten ist wesentlich konfortabler als 2 Integer
> für Meßwert und Skalierungsfaktor. Habe beides in der Praxis gemacht und
> weiss wovon ich spreche.

Dass das komfortabler ist, hat niemand bestritten. Die meisten der 
nicht-portablen Beispiele sind komfortabler zu schreiben. Unkomfortabel 
wirds erst, wenn der Code nach dem Portieren plötzlich nicht mehr wie 
erwartet funktioniert, und man vor allem zunächst nicht weiß, warum.

Fritz schrieb:
> Zu der von dir angesprochenen Auflösung ob 24-bit wirklich notwendig
> sind.
> Wenn man einiges an Mathematik (Winkelfuntionen, Wurzel ..) auch noch
> braucht muß man doch auch bei 32-bit int verdammt aufpassen nichts an
> Genauigkeit zu verlieren.

Für Berechnungen (Zwischenergebnisse) keine Frage. Aber ist es nicht 
eher so, dass entweder der Mikrocontroller mit der Messeinrichtung alle 
Berechnungen durchführt und dann die Endergebnisse zurückgibt? Oder eben 
nur die Rohwerte misst und die Berechnung z.B. an den PC delegiert. 
Zwischenergebnisse, die so hohe Präzision erfordern, über die Leitung zu 
schicken, halte ich konzeptionell für fragwürdig.

Aber mag sein, dass es auch sowas gibt. Dann muss man eben entweder den 
Aufwand treiben, das sauber zu serialisieren oder man verzichtet auf 
plattformunabhängigen Code.

von Fritz (Gast)


Lesenswert?

Fabian O. schrieb:
> Der Artikel geht eben genau darum, was man beachten muss, dass der
> gleiche C-Code auf verschiedenen Plattformen ohne Änderung läuft.
> Nicht, wie man am effizientsten Fließkommazahlen überträgt.

Du hast in deiner Gleitkommaabsatz von Übertragung zu anderen Platformen 
gesprochen mit extra zu implementierenden Stringformatierungen. Die von 
mir vorgeschlagene Pointercastoperation ist aber nur notwendig wenn man 
extern kommuniziert und nicht intern im C-code.

Nach unserer langen Diskussion bin ich zu der Erkenntnis gekommen, dass 
die Verwendung von float (IEEE definitionsgemäß) viel besser geeignet 
ist als int um portablen Code zu schreiben. Also sollte man eher float 
verwenden als int wenn es die Performance zuläßt.

Fabian O. schrieb:
> Fritz schrieb:
>> Da mit einem float zu arbeiten ist wesentlich konfortabler als 2 Integer
>> für Meßwert und Skalierungsfaktor. Habe beides in der Praxis gemacht und
>> weiss wovon ich spreche.
>
> Dass das komfortabler ist, hat niemand bestritten. Die meisten der
> nicht-portablen Beispiele sind komfortabler zu schreiben. Unkomfortabel
> wirds erst, wenn der Code nach dem Portieren plötzlich nicht mehr wie
> erwartet funktioniert, und man vor allem zunächst nicht weiß, warum.

1.) Pointercasting brauche ich im C-Programm ohne Kommunikation nicht.
2.) Alle C-Compiler die vorgeben IEEE-floats zu verwenden egal ob per SW 
oder FPU werden keine Probleme bei der Portierung machen, wenn der 
Compiler nicht fehlerhaft ist.
3.) Die Verwendung von floats erlaubt eine besseres 
Abstrahierungsvermögen als integer. Eine Trennung von Hardware zu 
Applikation ist klarer möglich.
Dazu folgendes Beispiel aus meiner Praxis die Messung einer Spannung:
Am Anfang steht der ADC mit seinen unterschiedlichen Auflösungen 8, 10, 
12 16 bit oder was auch immer. Diesn Wert mußt du in ein Format bringen, 
das am besten zur Weiterverarbeitung geeignet ist. Meist dann ein 
Fixpointformat um z.B. eine FFT zu berechnen. Dazu muß ich einen 
Skalierungsfaktor berechnen der auch die Kalibrierung mit einschließt.
Nach der FFT-Berechnung kann ich dann mittels diesen Skalierungsfaktor 
auf den physikalischen Meßwert kommen. Für verschiedene physikalische 
Messwerte wie Spannung, Strom, Leistung, .. brauche ich jeweils eigene 
Skalierungsfaktoren, die möglicherweise auch noch miteinander verknüpft 
sind. Das läßt sich alles sauber mit int programmieren, keine Frage.
Aber: wenn ich gleich nach der AD-Wandlung in einer routine den AD-Wert 
in ein float der entsprechenden physikalischen Größe umwandlen. Weitere 
Berechnungen entsprechen direkt den mathematischen Formeln (Z.B. FFT) 
und müssen nachher nicht mehr umskaliert werden.
Was ich damit sagen will ist, dass die Abstrahierung von ADC-Wert in 
einem Schritt zum physikalischem Meßwert als float sinnvoll und leicht 
verständlich ist. Die unportablen Teile der Anwendung können so klar in 
die ADC->float routine gekapselt werden, der Rest ist dann völlig 
portabel. Das habe ich mit "komfortabel" gemeint.

Fabian O. schrieb:
> Der Compiler darf Dir
> solche Konstrukte in C99 ggf. wegoptimieren. Wirklich legal ist es nur
> per memcpy().

float fl;
int32 data32;

fl = *((float*)&data32);

Was soll denn bei der Optimierung anderes herauskommen als ein memcpy?

von Performer (Gast)


Lesenswert?

Fritz schrieb:
> Also sollte man eher float
> verwenden als int wenn es die Performance zuläßt.

Soso. Und warum meidet man float auf Mikrocontrollern wie die Pest?

von (prx) A. K. (prx)


Lesenswert?

Fritz schrieb:
> fl = *((float*)&data32);
>
> Was soll denn bei der Optimierung anderes herauskommen als ein memcpy?

Wenn man per float * auf Daten zugreift, die eigentlich uint32_t sind, 
dann sieht der Compiler keinen möglichen Konflikt dazwischen und es kann 
sein, dass die aktuelle Version der Daten nur im Register und nicht im 
Speicher steht. Haben Pointer und Daten den gleichen Typ (oder 
irgendeinen char*), dann weiss er, dass es einen Konflikt geben kann.

Stell dir vor, der Compiler trennt dieses Statement in
  float *p = (float*)&data32;
  float f = *p;
und verzögert das zweite davon an eine Stelle, an der der aktuelle Wert 
von data32 im Register steht und nicht im Speicher. Ich weiss aber 
nicht, ob das bei einem solchen Beispiel realistisch vorkommen wird, 
oder ob Compiler angesichts von &data32 ohnehin auf "Vorsicht" schalten.

von Fritz (Gast)


Lesenswert?

Performer schrieb:
> Soso. Und warum meidet man float auf Mikrocontrollern wie die Pest?

War wohl eher der Zwang float nicht zu verwenden.

1.) Weil viele uCs keine FPU haben,
2.) Weil die libs für float sehr viel Platz brauchen, speziell wenn man 
auch printf verwendet, und die kleinen uCs eben nicht genung flash 
haben.
3.) Weil die Performance von SW float sehr schlecht ist.
4.) Weil ein float 4 byte braucht anstatt 2 für ein int16 (also mehr RAM 
braucht).
5.) Weil die freien abgespeckten Versionen der kommerziellen IDEs nur 
32K Programm erlauben und damit die Verwendung der flaot-libs praktisch 
unmöglich machen.

Das hat sich aber mit der Einführung des ARM Cortex M4 mit FPU doch 
stark geändert.
Jetzt spielen nur noch die 4. u. 5. eine Rolle.

von Performer (Gast)


Lesenswert?

Fritz schrieb:
> Das hat sich aber mit der Einführung des ARM Cortex M4 mit FPU doch
> stark geändert.
> Jetzt spielen nur noch die 4. u. 5. eine Rolle.

Aso. Wenn plattformübergreifend > "ARM Cortex M4 mit FPU" bedeutet ...

von Fabian O. (xfr)


Lesenswert?

Fritz schrieb:
> Du hast in deiner Gleitkommaabsatz von Übertragung zu anderen Platformen
> gesprochen mit extra zu implementierenden Stringformatierungen. Die von
> mir vorgeschlagene Pointercastoperation ist aber nur notwendig wenn man
> extern kommuniziert und nicht intern im C-code.
>
> [...]
>
> 1.) Pointercasting brauche ich im C-Programm ohne Kommunikation nicht.

Genau darum geht es doch die ganze Zeit: Wie man Fließkommazahlen nach 
draußen kommuniziert, z.B. vom Mikrocontroller zum PC. Innerhalb des 
Mikrocontrollers oder innerhalb des PCs kannst Du so viel mit float 
rechnen, wie Du lustig bist. Das gibt überhaupt keine Probleme 
hinsichtlich Portabilität.

Deshalb ist mir auch nicht so klar, was Du mit dem restlichen Text sagen 
möchtest. Klar kannst Du das innerhalb des C-Programms alles mit float 
rechnen. Dafür gibts den Datentyp ja. Das Problem entsteht erst, wenn Du 
mit Type punning (Pointer Cast) am C-Typsystem vorbei auf die interne 
Bitrepräsentation zugreifst. Da gibt es nämlich keine Garantien mehr, 
dass die zwischen Mikrocontroller und PC gleich ist.

Fritz schrieb:
> float fl;
> int32 data32;
>
> fl = *((float*)&data32);
>
> Was soll denn bei der Optimierung anderes herauskommen als ein memcpy?
1
void process(void) {
2
  float fl;
3
  int32 data32 = buffer[0]; // <- darf wegoptimiert werden
4
5
  fl = *((float*)&data32);
6
  process_data(fl);
7
}
Der Compiler darf die Zuweisung an data32 wegoptimieren, da data32 in 
der Funktion nicht mehr über seinen Namen, einen int32-Pointer oder 
char-Pointer gelesen wird. Der float* darf gemäß der Aliasing-Regel 
nicht auf einen int32 zeigen.

von (prx) A. K. (prx)


Lesenswert?

Performer schrieb:
> Soso. Und warum meidet man float auf Mikrocontrollern wie die Pest?

Das habe ich mich auch schon gelegentlich gefragt. Klar, bei 4KB Tinys 
ist das Unfug. Aber schon bei 8-Bittern mit 32K Flash kann float in der 
Programmierung wesentlich angenehmer sein als eine Umgehung durch 
Festkommaarithmetik, und vom Platzverbrauch evtl. tolerierbar. 
Insbesondere es nur um Berechnungen geht und platzraubende Ein/Ausgabe 
nicht erforderlich ist. Nicht in jedem Fall ist die Performance wirklich 
kritisch.

von Fritz (Gast)


Lesenswert?

A. K. schrieb:
> Stell dir vor, der Compiler trennt dieses Statement in
>   float *p = (float*)&data32;
>   float f = *p;
> und verzögert das zweite davon an eine Stelle, an der der aktuelle Wert
> von data32 im Register steht und nicht im Speicher. Ich weiss aber
> nicht, ob das bei einem solchen Beispiel realistisch vorkommen wird,
> oder ob Compiler angesichts von &data32 ohnehin auf "Vorsicht" schalten.

Sorry, das kann ich mir nicht vorstellen.

Mein C++Builder macht das optimiert oder nicht optimiert so wie memcpy.

Was sagen denn die C-Standard Spezialisten dazu?

fl = *((float*)&data32);

Sollte das nicht im Standard eine eindeutige Definition haben?

von Fabian O. (xfr)


Lesenswert?

Fritz schrieb:
> Sollte das nicht im Standard eine eindeutige Definition haben?

§ 6.5 Abs. 7 im C99-Standard:
http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf

An object shall have its stored value accessed only by an lvalue 
expression that has one of the following types:
— a type compatible with the effective type of the object,
— a qualified version of a type compatible with the effective type of 
the object,
— a type that is the signed or unsigned type corresponding to the 
effective type of the object,
— a type that is the signed or unsigned type corresponding to a 
qualified version of the effective type of the object,
— an aggregate or union type that includes one of the aforementioned 
types among its members (including, recursively, a member of a 
subaggregate or contained union), or
— a character type.

Ist übrigens alles im Artikel verlinkt ...

von Fritz (Gast)


Lesenswert?

Vorab möchte ich feststellen, dass Memcpy sicher die bessere Lösung ist.

Aber um das eindeutig zu klären bitte
Fabian O. schrieb:
> Der Compiler darf die Zuweisung an data32 wegoptimieren, da data32 in
> der Funktion nicht mehr über seinen Namen, einen int32-Pointer oder
> char-Pointer gelesen wird. Der float* darf gemäß der Aliasing-Regel
> nicht auf einen int32 zeigen.
Was ist jetzt genau die Aliasing-Regel, wenn das ein Fehler ist, warum 
gibt der Compiler keine Fehlermeldung aus?
Warum kann er wegoptimieren ist &data32 kein int32-Pointer?

von franz (Gast)


Lesenswert?

>Was sagen denn die C-Standard Spezialisten dazu?
>fl = *((float*)&data32);

Nur zum Verständnis: Was ist an diesem Verfahren besser, als eine Union 
zu verwenden?

von (prx) A. K. (prx)


Lesenswert?

franz schrieb:> Nur zum Verständnis: Was ist an 
diesem Verfahren besser, als eine Union
> zu verwenden?

Bei der Union darf man offiziell nur jene Komponente lesen, die man 
vorher reingeschrieben hat. Also in
 union {
   uint32_t u32;
   float    f32;
 };
nicht u32 schreiben und danach f32 lesen.

von (prx) A. K. (prx)


Lesenswert?

Fritz schrieb:
> Mein C++Builder macht das optimiert oder nicht optimiert so wie memcpy.

Reale Compiler sind hier irrelevant.

> Sorry, das kann ich mir nicht vorstellen.

Weshalb nicht? Casts sind eine recht gefährliche Ecke und hebeln einige 
Sicherheitsmechanismen aus. Ich empfehle daher, sie sparsam einzusetzen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

franz schrieb:
>>Was sagen denn die C-Standard Spezialisten dazu?
>>fl = *((float*)&data32);
>
> Nur zum Verständnis: Was ist an diesem Verfahren besser, als eine Union
> zu verwenden?

Nichts, beides ist unspecified (type punning).


Fritz schrieb:
> Was ist jetzt genau die Aliasing-Regel, wenn das ein Fehler ist, warum
> gibt der Compiler keine Fehlermeldung aus?

Also GCC kann das, etwa mit -fsyntax-only -Werror=strict-aliasing.
1
#include <stdint.h>
2
3
float fun (uint32_t i)
4
{
5
    return *(float*) &i;
6
}
1
<stdin>: In function 'fun':
2
<stdin>:5:5: error: dereferencing type-punned pointer will break strict-aliasing rules [-Werror=strict-aliasing]
3
cc1.exe: some warnings being treated as errors


Übrigens optimiert GCC mit Optimierung bei der memcpy-Version
1
#include <string.h>
2
#include <stdint.h>
3
4
float fun (uint32_t i)
5
{
6
    float f;
7
    memcpy (&f, &i, sizeof (f));
8
    return f;
9
}

den memcpy-Ausruf komplett raus.  Getestet mit GCC 4.3, 4.5, 4.6, 4.7 
und 4.8.

In meinem Umfeld wäre der float-Cast übrigens kein Problem, solcher Code 
würde schon durch kein Review kommen ;-)

von franz (Gast)


Lesenswert?

>Bei der Union darf man offiziell nur jene Komponente lesen, die man
>vorher reingeschrieben hat. Also in
> union {
>   uint32_t u32;
>   float    f32;
> };
>nicht u32 schreiben und danach f32 lesen.

Danke.

BTW: Ich habe einen Vorschlag zum Theman Datenübertragung eines Floats 
und korrekter Interpretation. Man könnte die Mantisse und den Exponent 
berechnen und dann die beiden übertragen. Ich weiß, es ist umständlich, 
aber Fehlinterpretationen sind dann ausgeschlossen.

von Fritz (Gast)


Lesenswert?

A. K. schrieb:
>> Mein C++Builder macht das optimiert oder nicht optimiert so wie memcpy.
>
> Reale Compiler sind hier irrelevant.

Bitte warum!!!!???

von (prx) A. K. (prx)


Lesenswert?

Weil "mein Compiler hat damit kein Problem" reinweg nichts über 
realistische bis theoretische Portabilitätskriterien und entsprechende 
Problemfelder aussagt.

von Fritz (Gast)


Lesenswert?

franz schrieb:
> BTW: Ich habe einen Vorschlag zum Theman Datenübertragung eines Floats
> und korrekter Interpretation. Man könnte die Mantisse und den Exponent
> berechnen und dann die beiden übertragen. Ich weiß, es ist umständlich,
> aber Fehlinterpretationen sind dann ausgeschlossen.

Halte ich auch für eine gute Lösung.

Leider geben uns ja hier die Spezialisten nur Antworten wie man es 
falsch oder nicht C-spezifikationsgerecht macht, aber nicht wie man das 
IEEE-Bitmuster korrekt von einer auf die andere Plattform überträgt.

Ich persönlich würde am liebsten 4 einzelne Bytes in einer definierten 
Reihenfolge (z.B. Mant0-Mant7, Mant8-Mant15, Mant16-Mant23-E0, E1-E7-V) 
übertragen.
Das könnte sehr wohl in jeweils einer Lese und Schreibroutine in C für 
meinen Compiler gekapselt und geschreiben werden. Wie weit das aber 
plattformübergreifend C-spzifisch OK ist kann ich nicht sagen. Haben wir 
ja oben diskutiert.

Hilfreich in dem Artikel wäre auch wenn man im Compiler per möglicher 
globale defines spezifisch auf big- bzw. littleendian oder 
integerbitlänge reagieren kann.

Gibt es dafür Konstanten?

von Adler (Gast)


Lesenswert?

A. K. (prx) schrieb:

> Weil "mein Compiler hat damit kein Problem" reinweg nichts über
> realistische bis theoretische Portabilitätskriterien und entsprechende
> Problemfelder aussagt.

Interessant. Dann ist das ganze hier wohl nur ein Trockenkurs für 
Theoretiker. Ich dachte die Artikel sollten von praktischer Relevanz 
sein, wegen

"Andreas Schwarz schrieb:
 Das Hauptkriterium ist: wie interessant und nützlich ist der Artikel 
für den Leser;"

Naja, Theorie kann ja auch ganz interessant sein. Den Nerd wird's 
freuen. Der Beamtenapparat ergötzt sich schließlich auch am 
Kleingedruckten seiner Verordnungen.

:-)

von Fritz (Gast)


Lesenswert?

A. K. schrieb:
> Weil "mein Compiler hat damit kein Problem" reinweg nichts über
> realistische bis theoretische Portabilitätskriterien und entsprechende
> Problemfelder aussagt.

Theoretisch OK!

Realistisch gibt es zumindesten Lösungen zwischen realen Plattformen die 
offensichtlich keine Portabilitäsprobleme generiren.

Aber ich warte noch immer auf eine theoretisch korrekte Lösung?

von Fabian O. (xfr)


Lesenswert?

Fritz schrieb:
> Leider geben uns ja hier die Spezialisten nur Antworten wie man es
> falsch oder nicht C-spezifikationsgerecht macht, aber nicht wie man das
> IEEE-Bitmuster korrekt von einer auf die andere Plattform überträgt.

Es gibt doch verschiedene Lösungsansätze. Nur gefallen sie Dir offenbar 
alle nicht.
- Umwandeln in String. Fehlinterpretation ausgeschlossen, sogar 
menschenlesbar.
- Umwandeln in Festkomma. Sehr effizient, sehr einfach zu definieren.
- Getrennte Übertragung von Exponent und Mantisse als Integer.

Letzteres geht mit den Funktionen aus der Standardbibliothek in einer 
Zeile:
1
#include <inttypes.h>
2
#include <math.h>
3
#include <stdio.h>
4
5
serialize(double val, int* exp, int32_t* sgn)
6
{
7
  *sgn = frexp(val, exp) * pow(2, 31);
8
}
9
10
double deserialize(int exp, int32_t sgn)
11
{
12
  return ldexp((double) sgn / pow(2, 31), exp);
13
}
14
15
void test(double original)
16
{
17
  double rebuild;
18
  int exp;
19
  int32_t sgn;
20
  
21
  serialize(original, &exp, &sgn);
22
  rebuild = deserialize(exp, sgn);
23
  
24
  printf("orginal: %f\n", original);
25
  printf("rebuild: %f\n", rebuild);
26
  printf("exp: %i\n", exp);
27
  printf("sgn: %"PRIX32"\n", sgn);
28
}
29
30
int main(void)
31
{
32
  float  original_float  = -98765.4321;
33
  double original_double = -98765.4321;
34
35
  printf("test float\n");
36
  test(original_float);
37
38
  printf("\ntest double\n");
39
  test(original_double);
40
}

Ausgabe:
1
test float
2
orginal: -98765.429688
3
rebuild: -98765.429688
4
exp: 17
5
sgn: 9F8CA480
6
7
test double
8
orginal: -98765.432100
9
rebuild: -98765.432068
10
exp: 17
11
sgn: 9F8CA459

Die Mantisse wird also als 32-Bit-Integer übertragen, als Exponent 
würden 8 Bit reichen. Serialisieren / Deserialisieren geht in einer 
Zeile mit Standardfunktionen. Die Genauigkeit ist sogar höher als ein 
einfacher float.

Wenn Dir das alles nicht gefällt, machst Du eben weiter Type Punning. 
Oder Du friemelst die Bits von Hand ins IEEE-Format, geht sicher auch. 
Sehe nur keinen wirklichen Nutzen drinnen.

Fritz schrieb:
> Hilfreich in dem Artikel wäre auch wenn man im Compiler per möglicher
> globale defines spezifisch auf big- bzw. littleendian oder
> integerbitlänge reagieren kann.
>
> Gibt es dafür Konstanten?

Ja, stehen im Artikel.

von (prx) A. K. (prx)


Lesenswert?

Adler schrieb:
> Interessant. Dann ist das ganze hier wohl nur ein Trockenkurs für
> Theoretiker. Ich dachte die Artikel sollten von praktischer Relevanz
> sein, wegen

Portabilität bezieht sich nicht auf die Portierung von C++Builder V1.2.3 
auf C++Builder V1.2.3. Daher nützt es reinweg nichts, wenn dieser 
C++Builder V1.2.3 eine bestimmte Formulierung problemlos frisst, aber 
andere Compiler damit Probleme bekommen könnten.

Ein Fokus des Artikels und des Threads liegt m.E. auf der Vermeidung von 
Konstruktionen, die bestimmte Eigenschaften von Compilern voraussetzen, 
die in der Sprache undefiniert oder unspezifiziert sind. Das hat 
notwendigerweise viel mit der Sprachdefinition zu tun, weniger mit den 
Eigenschaften von einem oder mehreren realen Compilern.

Der Thread ist allerdings mittlerweile ein wenig vom Thema Portabilität 
von Programmen abgewandert und fokussiert sich nun eher auf Portabilität 
von Datenformaten zwischen verschiedenen Systemen. Ist auch ein 
interessantes Thema, aber ein anderes.

Bei Portabilität von Programmen wie Daten sind beide Aspekte relevant. 
Sowohl die theoretische sich aus der Sprachdefinition ergebende 
Situation als auch die praktische sich aus realen Maschinen und 
Compilern ergebene.

So erlaubt die Sprachdefinition Fliesskommaformate, die nicht auf IEEE 
754 basieren, und das ist keine Theorie, denn solche Systeme existieren 
nach wie vor in wirtschaftlich relevantem Umfang. Allerdings wird man 
sich für praktische Betrachtungen derzeit eigentlich immer auf IEEE 754 
beschränken können.

Ein weiterer ähnlicher Aspekt wird im Artikel kurz angeschnitten: die 
Darstellbarkeit von -2^(N-1). Theoretisch muss man - dem Standard 
folgend - darauf verzichten. Praktisch kann man wohl davon ausgehen, 
dass für absehbare Zeit bei Ganzzahlen nur Zweierkomplementdarstellung 
existieren wird.

Dass man mit allzu enger Fixierung auf praktische Betrachtungen aber auf 
die Nase fallen kann zeigte die Jahr 2000 Umstellung. Viele Programme 
aus früheren Jahrzehnten waren nie für den Einsatz in derart langem 
Zeitraum gedacht und bekamen daher Probleme.

Das gleiche kann mit Programmen geschehen, die sich allzu sehr auf 
bestimmte praktische Eigenschaften von Maschinen und Compilern fixieren. 
Denn der Umstand, dass heutige reale Compiler und Maschinen bestimmte 
Eigenschaften haben, garantiert nicht, dass es in 20 Jahren immer noch 
so sein wird.

Ich hatte oben ein Beispiel gebracht, in dem der Optimizer ein C 
Statement aufsplittet und einen Teil davon an eine andere Stelle 
schiebt. C Compiler von anno 1980 setzten Statements ziemlich 1:1 um, da 
konnte das nie geschehen, weshalb man damals beispielsweise auch kein 
"volatile" brauchte. Heute sind solche Aktionen bei hochoptimierenden 
Compilern Alltag. Manche Probleme mit GCC rühren daher, dass die 
Optimierung dieses Compilers aus dem Highend-Sektor stammt und er daher 
aggressiver und mit anderem Ziel vorgeht, als Compiler, die nur den 
Mikrocontroller-Markt adressieren.

von (prx) A. K. (prx)


Lesenswert?

Fritz schrieb:
> Aber ich warte noch immer auf eine theoretisch korrekte Lösung?

Korrekter als ASCII mit Einschränkung auf die definierten Wertebereiche 
wirds nicht. Da Fliesskommadarstellung ohnehin nicht auf absolut exakte 
Ergebnisse abzielt muss das ausreichen. Wers noch weiter treiben will 
nimmt ASCII Darstellung auf Hex-Basis, was im Rahmen binärer 
Internformate exakt umsetzbar ist.

Eine theoretisch in jedem Fall absolut korrekte Lösung scheitert m.E. 
an der Offenheit der Spezifikation der Sprache.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Fritz schrieb:

> Leider geben uns ja hier die Spezialisten nur Antworten wie man es
> falsch oder nicht C-spezifikationsgerecht macht, aber nicht wie man das
> IEEE-Bitmuster korrekt von einer auf die andere Plattform überträgt.

Beitrag "Re: Feedback zum Artikel "Plattformunabhängige Programmierung in C""

> Hilfreich in dem Artikel wäre auch wenn man im Compiler per möglicher
> globale defines spezifisch auf big- bzw. littleendian oder
> integerbitlänge reagieren kann.
>
> Gibt es dafür Konstanten?

AFAIK nicht plattformübergreifend. Bei GCC gibt's immerhin Makros wie in 
Beitrag "Re: Feedback zum Artikel "Plattformunabhängige Programmierung in C""

Ansonsten muss man sich seine eigene hostconfig.h erzeugen, etwa
1
/* gen-hostconfig.c */
2
3
#include <stdio.h>
4
5
int main (void)
6
{
7
    printf ("#define SIZEOF_INT %d\n", (int) sizeof (int));
8
    ...
9
}
1
# Makefile
2
3
gen-hostconfig: gen-hostconfig.c
4
  $(CC) $^ -o $@
5
6
host-config.h: gen-hostconfig
7
  ./$< > $@
8
9
programm.c: hostconfig.h
1
/* program.c */
2
3
#include "hostconfig.h"
4
5
#if SIZEOF_INT == 2
6
...

Sowas hab ich bisher erst einmal gebraucht um Endianess aus einem 
Programm zu faktorisieren.  Ein Test zur Laufzeit war zu aufwändig und 
je nach Compiler nicht optimiert.

Obiger Ansatz setzt allerdings voraus, daß das Programm auf dem 
jeweiligen Host ausgeführt werden kann, was nicht ganz trivial ist, etwa 
wenn das Programm auf einem µC zu hosten ist.

Teilweise lassen sich Host-Features auch zur Compilezeit, d.h. auf der 
Build-Plattform erfragen, so dass kein Code auf dem Host ausgeführt 
werden muss.  Etwa:
1
int a[sizeof (int) == 2 ? 1 : -1];

Was je nach Host zu einem Compile-Fehler führt (int != 2 Byte) oder 
durchgeht (int = 2 Byte).

Damit ist man dann bei den Auto-Tools (autoconf, etc.) angelangt...

von Fritz (Gast)


Lesenswert?

Fabian O. schrieb:
> Wenn Dir das alles nicht gefällt, machst Du eben weiter Type Punning.
> Oder Du friemelst die Bits von Hand ins IEEE-Format, geht sicher auch.
> Sehe nur keinen wirklichen Nutzen drinnen.

Irgendwie scheine ich dir auf die Nerven zu gehen?

Aber schlußendlich hast du nun eine Lösung (serialize, deserialize von 
oben) präsentiert, die C-konform ist und bezüglich Performance und 
benötigten Bytes für die Kommunikation einer Stringum- und Rückwandlung 
überlegen ist. Auch Infinity wird (meinem kurzem Test zufolge) richtig 
übertragen.

Wenn ich deine im Artikel erwähnte Formulierung
" empfiehlt sich die Wandlung in Festkommazahlen oder Strings. "
ansehe, kommt auch ein geübter C-programmierer nicht sofort auf obige 
Lösung. Da du aber die Integerproblematik sehr ausführlich behandelt und 
mit Beispielen versehen hast, würde ich es sehr begrüßen, wenn du obige 
Funktionen Serialize und Deserialize als korrekte Umwandlung einfügen 
würdest.
Aber bitte für float und double getrennt jeweils auch mit den 
entsprechend richtigen math.h Funtionen
für float: frexpf, ldexpf, powf
für double: frexp, ldexp, pow

Ob du auch noch Stringum- und Rückwandlung als Alernative drinnen lassen 
möchtest, sei dir überlassen.

von Fabian O. (xfr)


Lesenswert?

Fritz schrieb:
> Irgendwie scheine ich dir auf die Nerven zu gehen?

Naja, es ist schon etwas mühsam, wenn Du Dich immer wieder darüber 
beschwerst, dass in einem Artikel über plattformunabhängigen Code nicht 
empfohlen wird, plattformabhängige Konstrukte zu verwenden ...

Wie gesagt, es zwingt Dich niemand, Deinen Code plattformunabhängig zu 
machen. Das meine ich wirklich völlig neutral. Es ist ja (zumindest imo) 
genau eine der Stärken von C, dass man solche extrem effizienten 
Lösungen wie die interne Repräsentation eines floats als int zu 
interpretieren, überhaupt hat. Damit kann man ja auch tatsächlich 
nützliche Dinge machen: 
http://en.wikipedia.org/wiki/Fast_inverse_square_root
In vielen anderen Hochsprachen hat man keine Chance, das hinzubekommen. 
Ebenso ist toll, dass es C für nahezu jeden Prozessor gibt. Nur kann man 
eben nicht auch noch erwarten, dass extrem effiziente Konstrukte, die 
auf maschinenabhängigen Eigenschaften basieren, auch noch portabel sind. 
Man kann nicht alles haben.

Der Artikel soll zeigen, auf was man aufpassen muss, wenn man seinen 
Code auf verschiedenen Plattformen einsetzen will und was prinzipiell 
schief gehen kann. Wenn Du sagst, mein Code wird nur auf Prozessoren mit 
gleicher Float-Repräsentation laufen (und damit hast Du ja auch ziemlich 
wahrscheinlich recht), dann bitte, mach es so. Ich benutze zum Beispiel 
auch ohne schlechtes Gewissen uint8_t statt unsigned char oder 
uint_least8_t, obwohl es uint8_t nicht auf jeder Plattform gibt. Einfach 
weil ichs leserlicher finde und nicht erwarte, dass der Code mal auf 
einem komischen DSP laufen muss ... Aber ich finde es sinnvoll, solche 
Dinge zumindest zu wissen und im Hinterkopf haben.

> Aber schlußendlich hast du nun eine Lösung (serialize, deserialize von
> oben) präsentiert, die C-konform ist und bezüglich Performance und
> benötigten Bytes für die Kommunikation einer Stringum- und Rückwandlung
> überlegen ist. Auch Infinity wird (meinem kurzem Test zufolge) richtig
> übertragen.
>
> Wenn ich deine im Artikel erwähnte Formulierung
> " empfiehlt sich die Wandlung in Festkommazahlen oder Strings. "
> ansehe, kommt auch ein geübter C-programmierer nicht sofort auf obige
> Lösung. Da du aber die Integerproblematik sehr ausführlich behandelt und
> mit Beispielen versehen hast, würde ich es sehr begrüßen, wenn du obige
> Funktionen Serialize und Deserialize als korrekte Umwandlung einfügen
> würdest.

Habe ich schon gemacht. Ich hatte die Funktionen beim Schreiben des 
Artikels zugegebenermaßen auch nicht im Kopf. Die hat erst Johann L. in 
seinem Beitrag ins Spiel gebracht. Bin aber auch nach wie vor der 
Meinung, dass man mit Festkomma oder Strings als Netzwerkformat die 
meisten Anwendungsfälle sehr gut bzw. besser abdecken kann. Aber gut, 
die Diskussion hatten wir schon ...

> Aber bitte für float und double getrennt jeweils auch mit den
> entsprechend richtigen math.h Funtionen
> für float: frexpf, ldexpf, powf
> für double: frexp, ldexp, pow

Die Varianten für float sind keine Standardfunktionen, ist also 
hinsichtlich Portabilität nicht zu empfehlen. Auf Prozessoren mit FPU 
ist es eh egal, weil der mit double genau so gut rechnen kann. Aber ich 
schreibe noch nen Satz dazu, dass es die gibt.

> Ob du auch noch Stringum- und Rückwandlung als Alernative drinnen lassen
> möchtest, sei dir überlassen.

Das IST eine Alternative, und zwar nicht die schlechteste. Warum sollte 
man die nicht mal nennen sollen? Was in der jeweiligen Anwendung am 
sinnvollsten ist, muss schon jeder selber entscheiden. Je mehr 
Alternativen man zur Auswahl hat, desto besser.

von Fabian O. (xfr)


Lesenswert?

Fabian O. schrieb:
> Die Varianten für float sind keine Standardfunktionen, ist also
> hinsichtlich Portabilität nicht zu empfehlen.

Korrektur: In C99 sind sie Teil des Standards.

von Fritz (Gast)


Lesenswert?

Fabian O. schrieb:
> Auf Prozessoren mit FPU
> ist es eh egal, weil der mit double genau so gut rechnen kann.

Nein leider nicht!

Da wir hier im Mikrokontroller Forum sind:
Es gibt seit ein paar Jahren den ARM Cortex M4 schon von mehreren 
Herstellern und auch sehr preiswert und inzwischen hier im Forum sehr 
beliebt.

Der hat aber eine FPU die nur float kann aber leider keine double.

von Fritz (Gast)


Lesenswert?

Hallo Fabian ich bins, muß nochmal lästig sein.

Du hast beide Varianten (float und double) in den Artikel gestellt, aber 
die double Variante ist nicht korrekt. Du verwendest für die Mantisse 
ein int32, was aber für ein double viel zu klein ist. Die integers für 
die double routinen müssen int64 sein!

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Weiterer Aspekt der Portierbarkeit ist das Verhalten von NANs, d.h. ob 
es eine stille NaN ist (non-signaling) oder nicht (signaling).

Wenn man die Plattform wachselt kann man sich dann wahlweise wundern, wo 
die Exceptions herkommen, oder warum keine mehr kommen und die 
Algorithmen stattdessen Käse liefern :-)

von Fabian O. (xfr)


Lesenswert?

Fritz schrieb:
> Der hat aber eine FPU die nur float kann aber leider keine double.

OK, wusste ich nicht. Dann lohnt es sich natürlich, die Float-Funktionen 
zu nutzen.

Fritz schrieb:
> Du hast beide Varianten (float und double) in den Artikel gestellt, aber
> die double Variante ist nicht korrekt. Du verwendest für die Mantisse
> ein int32, was aber für ein double viel zu klein ist. Die integers für
> die double routinen müssen int64 sein!

Naja, was heißt "korrekt". Ist halt die Frage, welche Genauigkeit man 
braucht. Denk mal, für viele Anwendungen reichen 32 Bit völlig. Man 
könnte im Prinzip jede beliebige Anzahl an Mantissenbits übertragen, 
auch 16, 24, 32, 40, 48, je nach benötigter Genauigkeit.

Glaub ich stell das wieder zurück auf eine Variante als Beispiel. Ist ja 
auch nicht dafür gedacht, 1:1 kopiert zu werden, sondern einfach zu 
zeigen, wie man die Funktionen benutzen kann.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wäre ein ldexp[f] (x, 31) nicht besser als ein x * pow[f] (2, 31) ?

Sowohl was die Effizienz als auch Rundungsfehler angeht.  Zwar wird man 
Rundungsartefakte haben, aber die müssen ja nicht größer sein als nötig.

Und was spricht eigentlich gegen ein "x * (1ul << 31)"?

von Fabian O. (xfr)


Lesenswert?

Ich finde die Variante mit pow(2, 31) am klarsten. Macht es denn einen 
Unterschied? Hätte erwartet, dass das zur Compilezeit durch die 
entsprechende Konstante ersetzt wird. Oder kann der Compiler das nicht?

von Fabian O. (xfr)


Lesenswert?

Habs mal ausprobiert.
1
int32_t split_d32(double val, int* exp)
2
{
3
  return frexp(val, exp) * pow(2, 31);
4
}
und
1
int32_t split_d32(double val, int* exp)
2
{
3
  return frexp(val, exp) * (1UL << 31);
4
}
ergeben mit GCC am PC identischen Code:
1
split_d32:
2
.LFB0:
3
  .cfi_startproc
4
  pushq  %rax
5
  .cfi_def_cfa_offset 16
6
  call  frexp
7
  mulsd  .LC0(%rip), %xmm0
8
  popq  %rdx
9
  .cfi_def_cfa_offset 8
10
  cvttsd2si  %xmm0, %eax
11
  ret
12
  .cfi_endproc
13
14
.LC0:
15
  .long  0
16
  .long  1105199104
17
  .align 8

Die Version mit ldexp()
1
int32_t split_d32(double val, int* exp)
2
{
3
  return ldexp(frexp(val, exp), 31);
4
}
ergibt:
1
split_d32:
2
.LFB0:
3
  .cfi_startproc
4
  pushq  %rax
5
  .cfi_def_cfa_offset 16
6
  call  frexp
7
  movl  $31, %edi
8
  call  ldexp
9
  popq  %rdx
10
  .cfi_def_cfa_offset 8
11
  cvttsd2si  %xmm0, %eax
12
  ret
13
  .cfi_endproc

Würde mal sagen, die Multiplikation ist schneller? Für "dümmere" 
Compiler schreibt man aber vielleicht besser (1UL << 31).

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Fabian O. schrieb:
> Ich finde die Variante mit pow(2, 31) am klarsten. Macht es denn einen
> Unterschied? Hätte erwartet, dass das zur Compilezeit durch die
> entsprechende Konstante ersetzt wird.

Jo, stimmt.  Sind ja beide Parameter bekannt.

GCC 4.8 faltet das sogar ohne Optimierung...

von (prx) A. K. (prx)


Lesenswert?

Aber da wir hier beim Thema Portabilität sind: Solche Optimierungen sind 
keine Selbstverständlichkeit.

von Fritz (Gast)


Lesenswert?

Fabian O. schrieb:
> Naja, was heißt "korrekt". Ist halt die Frage, welche Genauigkeit man
> braucht. Denk mal, für viele Anwendungen reichen 32 Bit völlig. Man
> könnte im Prinzip jede beliebige Anzahl an Mantissenbits übertragen,
> auch 16, 24, 32, 40, 48, je nach benötigter Genauigkeit.

Einerseits bist du sehr genau und pedantisch was die Umsetzung der 
Standards betrifft. Dann machst du aber Vorschläge die darauf 
hinauslaufen:

wo double draufsteht ist aber nicht double drin!!??

von Fritz (Gast)


Lesenswert?

Johann L. schrieb:
> Weiterer Aspekt der Portierbarkeit ist das Verhalten von NANs, d.h. ob
> es eine stille NaN ist (non-signaling) oder nicht (signaling).
>
> Wenn man die Plattform wachselt kann man sich dann wahlweise wundern, wo
> die Exceptions herkommen, oder warum keine mehr kommen und die
> Algorithmen stattdessen Käse liefern :-)

Da fürchte ich, dass die beiden routinen serilize und deserialize auch 
Probleme machen könnten. Aber seis drum, irgendwie habe ich das Gefühl 
wie wenn ich von Wien nach London fliegen will, aber mir die 
Fluggesellschaft vorschlägt, dann fliegen Sie von Wien nach Stockholm 
und steigen dort in die Maschine nach London um. Komme zwar auch 
dorthin, für mich aber mit manchen Nachteilen dauert länger, Umwelt! ... 
Ist halt ein Umweg.

Dabei könnte man mit einer Erweiterung der math.h - lib das Problem so 
einfach, elegant und effizient (benötigt nur 4/8 byte, ist schneller) 
lösen:

2 Funktionen:
int32_t toieebitsf(float x);
float fromieebitsf(int32_t i);
Ins integer werden die bits so hineinkopiert, wie es der IEEE Standard 
vorsieht und umgekehrt.

Dasselbe entsprechend auch für double.
Käme auf den meisten Plattformen auf ein memcpy heraus. Wäre als in32_t 
portabel. Ich dachte bisher C ist diejenige Hochsprache, die den 
schnellsten und effizientesten Code erzeugen kann. Wie man sieht, geht 
aber nicht.

Offensichtlich haben die Standardisierungsgremien ein sicher häufig 
auftretendes Problem nicht berücksichtigt.

von (prx) A. K. (prx)


Lesenswert?

Das Überlaufverhalten müsste noch definiert werden.

von (prx) A. K. (prx)


Lesenswert?

Fritz schrieb:
> Ich dachte bisher C ist diejenige Hochsprache, die den
> schnellsten und effizientesten Code erzeugen kann.

Das ist nicht der Fall. Die in C recht häufige Möglichkeit von 
zulässigem Aliasing kann ein ziemliches Problem für einen C Optimizer 
darstellen.

von Fritz (Gast)


Lesenswert?

A. K. schrieb:
> Das Überlaufverhalten müsste noch definiert werden.

Wieso? Im IEEE-Standard ist doch alles sehr gut definiert. Ich 
transportiere doch nur ein Bitmuster von der einen Plattform auf die 
andere. Beide Plattformen sollten doch dieses Bitmuster gleich nach IEEE 
behandeln. Falls nicht ist die IEEE-Implementation in einer Plattform 
halt nicht richtig, das kann aber auch bei anderer Umsetzung zu 
Problemen führen.

von Fritz (Gast)


Lesenswert?

A. K. schrieb:
> Das ist nicht der Fall. Die in C recht häufige Möglichkeit von
> zulässigem Aliasing kann ein ziemliches Problem für einen C Optimizer
> darstellen.

Ich meinte nur im Vergleich zu anderen Hochsparchen, nicht zu 
handgeschriebenen Assemblercode.

von Und noch einer (Gast)


Lesenswert?

Der Artikel geht davon aus, dass man Plattformunabhängigkeit (= 
portierbar) braucht. Meine Erfahrung in der Praxis ist, dass man das in 
nahezu allen Fällen nicht braucht.

Das bedeutet, dass sich der Aufwand (Aufwand == Kosten) den man in 
Plattformunabhängigkeit steckt nicht amortisiert. Daher sollte man es 
lassen. Im Normalfall sind die verwendeten Algorithmen nicht so 
kompliziert, dass man sie für alle Ewigkeit in eine portierbare 
Bibliothek stecken muss, damit sich der ursprüngliche 
Implementierungsaufwand über mehrere Portierungen amortisiert.

Damit ist an bei einer Grundeinstellung des agilen Entwickelns: YAGNI - 
You ain't gona need it. Mache nichts, von dem du nicht heute schon 
weißt, dass du es wirklich in der Zukunft brauchst. Bei der üblichen 
Portierungsrate von Embedded-Anwendungen, die nahe Null liegt, weiß man 
heute ziemlich sicher, dass man Portierbarkeit nicht braucht. Daher 
lohnt es nicht.

Wer mit agilen Methoden nichts am Hut hat, der kennt trotzdem vielleicht 
KISS - Keep it simple stupid. Mach es einfach. Auch das greift hier. 
Portierbarkeit wird mit zusätzlichen Abstraktionsebenen erkauft. Je mehr 
Code, desto mehr Fehler kann man einbauen, desto aufwendiger wird die 
Wartung des Codes.

Und schließlich, plattformunabhängiger Code kann bedeuten, dass man 
gegen die Architektur eines Mikrocontrollers kämpft, nur damit der Code 
plattformunabhängig ist. Damit stellt man eine Eigenschaft 
(Plattformunabhängigkeit) über eine effiziente Lösung jetzt und hier. 
Bei einer Wahrscheinlichkeit von nahe Null dass man die 
Plattformunabhängigkeit braucht ein sehr schlechter Tausch.

von Fritz (Gast)


Lesenswert?

Und noch einer schrieb:
> Der Artikel....

Du sprichst mir mit deinen Worten aus der Seele. Ich als alter 
uC-Anwender sehe das genauso.
Ich habe halt versucht, mit meinen dauernden Fragen die Theoretiker 
selbst draufkommen zu lassen wo halt der Unterschied zwischen Theorie 
und Praxis liegt.

von (prx) A. K. (prx)


Lesenswert?

Fritz schrieb:
>> Das Überlaufverhalten müsste noch definiert werden.
>
> Wieso? Im IEEE-Standard ist doch alles sehr gut definiert.

Da schon. Aber nicht in umgekehrter Richtung.

von Nicolas S. (Gast)


Lesenswert?

Klar, wenn ein Artikel "Brötchen backen für Fortgeschrittene" 
geschrieben wird, komme ich auch nicht umhin die Leute in die Richtung 
zu stupsen, daß die Praxis gezeigt hat, daß zum Frühstück Müsli zu essen 
deutlich effizienter ist.

von (prx) A. K. (prx)


Lesenswert?

Fritz schrieb:
> Ich meinte nur im Vergleich zu anderen Hochsparchen, nicht zu
> handgeschriebenen Assemblercode.

Ich auch. Aber da liegt beispielsweise FORTRAN vorne.

von (prx) A. K. (prx)


Lesenswert?

Und noch einer schrieb:
> Meine Erfahrung in der Praxis ist, dass man das in
> nahezu allen Fällen nicht braucht.

Meine Erfahrung wiederum ist, dass man das in vielen Fällen brauchen 
kann. Sowohl bei Mikrocontrollern, als auch in anderem Umfeld.

Beispiele:

- Code für 1-Wire- oder SHT11-Sensoren lässt sich weitgehend portabel 
gestalten, nur der unmittelbare Portzugriff muss angepasst werden.

- Netzwerkprotokolle alle Art, beispielsweise RS485 oder CAN lassen sich 
ausgezeichnet wiederverwenden, wenn der darunter liegende 
systemabhängige Layer gleich ist.

> Und schließlich, plattformunabhängiger Code kann bedeuten, dass man
> gegen die Architektur eines Mikrocontrollers kämpft

Der Flieskommakram geht in diese Richtung. Der Rest hingegen ist eher 
eine Frage von "gewusst wie".

von Fabian O. (xfr)


Lesenswert?

Fritz schrieb:
> Einerseits bist du sehr genau und pedantisch was die Umsetzung der
> Standards betrifft. Dann machst du aber Vorschläge die darauf
> hinauslaufen:
>
> wo double draufsteht ist aber nicht double drin!!??

Wir haben offensichtlich verschiedene Anforderungen. Du willst auf 
Biegen und Brechen einen IEEE float/double zwischen den Geräten 
verschicken. Darum gehts aber im Artikel nicht. Sondern welche 
Möglichkeit man hat, wenn der Code auf jeder Plattform laufen soll. In C 
gibt es eben leider keine Funktion float_to_ieee_754_char_array() und 
zurück. Mag sein, dass das manchmal praktisch wäre, gibts aber eben 
nicht. So gesehen bin ich da sogar sehr pragmatisch: Ich nehme die 
Möglichkeiten, die der C-Standard mitliefert.

Denn auch auf die Gefahr hin, mich zu wiederholen: Wozu muss man bitte 
einen double mit voller Genauigkeit zwischen zwei Geräten verschicken? 
Nenn mir mal eine sinnvolle Anwendung dafür.

Mir ist bewusst, dass es Algorithmen gibt, für die man gar nicht genug 
Stellen für die Zwischenergebnisse haben kann, weil sie durch die vielen 
Iterationen und große Dynamikbereiche Rundsfehler schnell stark 
auswirken. Aber solche Algorithmen rechnet man doch nicht im 
Ping-Pong-Verfahren mit zwei Geräten: Gerät A rechnet einen Schritt, 
schickt die den Wert an B, B rechnet einen Schritt, schickt den Wert 
zurück an A, A rechnet wieder einen Schritt ... Wenn man sowas baut, ja, 
dann darf man beim Übertragung keine zusätzlichen Rundungsfehler 
einbauen.

Die Situation in eingebetten Geräten ist dagegen: Datenquelle (z.B. 
Sensor) -> Gerät A -> Gerät A berechnet einen Algorithmus -> schickt 
Ergebnis an B -> B berechnet einen Algorithmus -> Datensenke (in Datei 
speichern, am Bildschirm ausgeben, an Aktor schicken). Für einen 
Datensatz hat man also einmal eine Übertragung. Wozu braucht die mehr 
32 Bit Mantisse (+ Exponent)? Was für Ausgangswerte stecken da drinnen, 
die so genau sind? Wenn es das in Spezialmessgeräten im Wert von 
Einfamilienhäusen gibt, gut, dann überträgt man halt mehr. Aber in 99,9% 
der für mich denkbaren Anwendungsfälle wäre das völliger Overkill.

Man muss sich sowieso ein Protokoll ausdenken, mit dem man seine Daten 
verschickt. Warum sollte man darin also nicht festlegen, wie viele 
Mantissenbits man für einen Datensatz braucht? Man muss das Protokoll 
immer auf Sender und Empfänger implementieren, man hat also völlige 
Wahlfreiheit. Und auf jedem Sender und Empfänger, für den es eine 
standardkonforme C-Implementierung gibt, gibt frexp() und ldexp(). 
Übrigens auch printf(), scanf(), atof(), .... Integerverarbeitung 
(Stichwort Festkomma) sowieso. Also bediene ich mich doch aus diesen 
Möglichkeiten, statt irgendeinem IEEE-Standard hinterzuhecheln, den ich 
nicht ohne Verrenkungen plattformübergreifend konvertiert bekomme. Falls 
man auf einer Seite keine Wahlfreiheit hat, erübrigt sich die Diskussion 
sowieso: Dann muss man des implementieren, was die Gegenseite will.

Und was ich auch schon mehrmals gesagt habe: Wenns Dir nicht drauf 
ankommt, dass Code plattformunabhängig ist, sondern dass er schnell und 
effizient ist, hält Dich weiterhin kein Mensch davon ab, Dir per 
memcpy() die Bitrepräsentation des floats zu holen. Aber zu erwarten, 
dass genau diese Variante, die plattformabhängig ist, im Artikel über 
Plattformunabhängigkeit empfohlen wird, ist absurd. Das ist, wie wenn 
man sich drüber beschwert, wenn in einem Artikel zu umweltbewussten 
Verhalten empfohlen wird, mit dem Fahrrad zum Bäcker zu fahren. Das Auto 
ist schließlich viel schneller und bequemer. Oder dass die 
Autohersteller gefälligst Autos bauen sollen, die die Umwelt nicht 
belasten.

von Fritz (Gast)


Lesenswert?

Fabian O. schrieb:
> Wir haben offensichtlich verschiedene Anforderungen. Du willst auf
> Biegen und Brechen einen IEEE float/double zwischen den Geräten
> verschicken.

Ja weil ich auf der versendenden Seite nicht jedes float/double 
analysieren will wieviel Genauigkeit der Wert braucht, möglicherweise 
muß ich auch einen Kollegen fragen was Sache ist. Der wird eher die 
volle Genauigkeit verlangen, um nicht der Schuldige zu sein, wenn etwas 
dann nicht funktioniert. Bei der Wiederverwendung des Codes muß man auch 
mehr aufpassen. Sollte man aber auch einmal Zwischenergebnisse zu 
Debugzwecken auf dem PC brauchen, ist es halt auch vorteilhafter die 
originalen float oder double zu verwenden.

Fabian O. schrieb:
> Denn auch auf die Gefahr hin, mich zu wiederholen: Wozu muss man bitte
> einen double mit voller Genauigkeit zwischen zwei Geräten verschicken?
> Nenn mir mal eine sinnvolle Anwendung dafür.

Mit voller Genauigkeit vielleicht nicht. Aber mit float kommt man schon 
leicht nicht aus:
1.) Agilent hat ein Digitalvoltmeter mit 8,5 Stellen Auflösung. Wenn du 
nun bedenkst, dass man intern gern mit einer Stelle mehr rechnet kommt 
man schon reichlich über das float format hinaus.
2.) Die GPS-Zeit wird in usec beginnend  5. Januar 1980 um 24:00:00 
angegeben, da kommt auch ein recht heftige Genauigkeit zusammen.

Du hast schon recht die vollen 52-bit werde ich kaum brauchen. Aber bei 
weniger bit müßte man wahrscheinlich noch die Rundung beachten.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Fritz schrieb:

> 2.) Die GPS-Zeit wird in usec beginnend  5. Januar 1980 um 24:00:00
> angegeben, da kommt auch ein recht heftige Genauigkeit zusammen.

Warum in aller Welt sollte man die in einen double wandeln?

Einen float von einer Plattform auf die andere zu transportieren hat 
doch zunächst nichts mit der plattformunanhängigkeit des Codes zu tun, 
sondern damit, dass sich beide Seiten auf ein Protokoll, d.h. das Format 
der zu übertragenden Daten festlegen.

von Anja (Gast)


Lesenswert?

Und noch einer schrieb:
> Meine Erfahrung in der Praxis ist, dass man das in
> nahezu allen Fällen nicht braucht.

Du scheinst keine Erfahrung mit nachhaltiger Entwicklung zu haben.
Im Automotive-Bereich wird alle paar Jahre die ganze Software auf die 
nächste Generation portiert.

Gruß Anja

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.