Moin! Also, ich habe im externen Speicher feste Variablen hinterlegt. Über 4 Bytes liegt dort ein float (0.007964 bzw. 0x3c027b70). Meine Lese-Funktion übergibt mir das ganze als uint32_t. Wie bekomme ich das nun in einem float gespeichert ohne, dass das Ding umgerechnet bzw. gecastet wird? Pseudocode: float myFloat = lese_speicher(adresse); //lese_speicher übergibt uint32_t danach steht in myFloat 1006795632. gibt es da eine Möglichkeit, oder muss ich mir dafür eine neue Lesefunktion basteln?
Ralf schrieb: > Meine > Lese-Funktion übergibt mir das ganze als uint32_t. Warum tut sie das? Vielleicht suchst du "UNION" Ralf schrieb: > gibt es da eine Möglichkeit, oder muss ich mir dafür eine neue > Lesefunktion basteln? Würde ich dir empfehlen.
Weil ich bis auf eben diese eine Variable ausschließlich Integer auf dem Speicher liegen habe. Es ist natürlich kein Problem eine neue Funktion zu erstellen, aber ich dachte es geht irgendwie "einfacher".
1 | float *f = &var_uint_32; |
2 | |
3 | printf("float = %f\n", *f); |
(ungetestet)
CC schrieb: > float *f = &var_uint_32; > > printf("float = %f\n", *f); > (ungetestet) ja, so gehts natürlich auch! :D
Ralf schrieb: > ja, so gehts natürlich auch! :D Außen wenn das Alignment für float anders ist als für int32. Dann knallt es bei zugriff auf das float.
Geht eigentlich nur über einen Cast, wenn Du keine zweite Lesefunktion erstellen willst. Ich weiß leider nicht, inwieweit man mit C generische Funktionen* entwickeln kann; das wäre nämlich eine zweite Möglichkeit. *https://de.wikipedia.org/wiki/Generische_Programmierung
CC schrieb: > float *f = &var_uint_32; > > printf("float = %f\n", *f); > (ungetestet) Das ergibt undefiniertes Verhalten laut C-Standard.
Von John Carmack, dem Meister aller Tricksereien stammt: y = * ( float * ) &i; https://www.heise.de/newsticker/meldung/Zahlen-bitte-0x5f3759df-merkwuerdige-Mathematik-im-Egoshooter-3208927.html
Ich habe mal, weil mir die ganzen Hacks nicht gefallen haben, folgende Library geschrieben: https://github.com/Daniel-Abrecht/IEEE754_binary_encoder
danke noch mal! also, das mit dem pointer funktioniert´s zwar, aber es gibt eine warnung! es könnte also probleme geben. habe endgültig mit memcpy eine lesefunktion geschrieben, die mir dann auch einfach ein float zurück gibt. feddisch... :)
Noch einer schrieb: > Von John Carmack, dem Meister aller Tricksereien stammt: Pfusch. Das ist ganz sicher nicht das gewünschte.
Rufus Τ. F. schrieb: > Noch einer schrieb: >> Von John Carmack, dem Meister aller Tricksereien stammt: > > Pfusch. Das ist ganz sicher nicht das gewünschte. "Trickserei" ist eine Obermenge von "Undefined Behaviour" ;-)
> y = * ( float * ) &i; > Pfusch. Das ist ganz sicher nicht das gewünschte. Warum nicht? Ich finde diese Lösung total naheliegend.
Stefan U. schrieb: > Warum nicht? Ich finde diese Lösung total naheliegend. Sehe ich auch so... Wenn ich 4 Byte im Speicher habe kann ich diese doch interpretieren wie ich möchte. Ob als float, int oder sogar struct. Oder spricht etwas gegen diese Ansicht?
Adam P. schrieb: > Oder spricht etwas gegen diese Ansicht? das es Systeme gibt, die eine Ausrichtung der Daten im Speicher brauchen. Sie können nicht ein float an beliebiger Adresse verarbeiten.
Peter II schrieb: > das es Systeme gibt, die eine Ausrichtung der Daten im Speicher > brauchen. Sie können nicht ein float an beliebiger Adresse verarbeiten. Zum Beispiel? Natürlich nur Plattformen bei denen ein float auch wirklich 32 Bit hat.
Adam P. schrieb: > Oder spricht etwas gegen diese Ansicht? Gegen diese Ansicht spricht nichts, wir haben ja Meinungsfreiheit. Ein geneigter C-Compiler könnte sich aber weigern das zu tun, was du erwartest, weil die Strict-Aliasing-Rules verletzt werden. Einfach gesprochen muss der Compiler den zugriff nicht ausführen, wenn Du an eine Stelle zuletzt nicht den Typ geschrieben hast, den Du lesen möchtest. Eine Union funktioniert hier auch nicht (garantiert). Ausnahme sind alle Arten von char. Sprich: Eine Union von uint32_t nach uint8_t[] und zurück wäre erlaubt, von uint32_t nach float aber nicht. Das Konstrukt mit memcpy ist eine weithin akzeptierte Lösung, die der Compiler normalerweise laufzeitneutral wegoptimiert.
Ja OK, das der Compiler da vllt. dann nicht mitspielt ist eine andere Geschichte, ebenfalls das mit dem wegoptimieren. Aber wenn der Compiler brav macht, was man in C geschrieben hat, sollte es doch kein Problem geben. (Hab es mit dem gcc ausprobiert - embedded und linux) -> soweit alles OK.
Markus F. schrieb: > Natürlich nur Plattformen bei denen ein float auch wirklich 32 Bit hat. das Floats nicht an ungeraden Adressen liegen dürfen gibt es sehr oft. Aber ob es Systeme gibt, wo es unterschiedliche Ausrichtungen für int und float gibt kann ich nicht sagen.
Daniel A. schrieb: > Ich habe mal, weil mir die ganzen Hacks nicht gefallen haben, folgende > Library geschrieben: > https://github.com/Daniel-Abrecht/IEEE754_binary_encoder Ööööh ... wofür soll das gut sein? 32 und 64Bit Float-Zahlen haben doch eine eindeutige binäre Repräsentation ... Was spricht gegen casten?
beim gcc braucht's u.U. ein -fno-strict-aliasing (nach dem -O2/-Ox, sonst schaltet das wieder ein). memcpy() ist aber die sauberere Lösung.
Stefan U. schrieb: > Kann man das strict aliasing nicht durch einen void* umgehen? klar kann man das, aber es sind ja nicht ohne Grund drin. Es kann dann zu Problemen kommen.
Stefan U. schrieb: >> y = * ( float * ) &i; >> Pfusch. Das ist ganz sicher nicht das gewünschte. > > Warum nicht? Ich finde diese Lösung total naheliegend. Type Punning. https://blog.regehr.org/archives/959 http://mail-index.netbsd.org/tech-kern/2003/08/11/0001.html
Adam P. schrieb: > Aber wenn der Compiler brav macht, was man in C geschrieben hat Das ist aber kein C, also darf der Compiler damit tun, was immer er will. Wenn nicht jetzt, dann nach dem nächsten Compiler-Update. Das ist dann ein Spaß mit der Fehlersuche.
Strict aliasing in C ist, als wenn man nen Lichtschalter unter Strom setzt und nen Schild drüber hängt "Nur mit Gummihandschuhen betätigen!". Glücklicherweise gibt es "-fno-strict-aliasing" ...
Quake hat ja auch funktioniert. Muss man halt immer sehen wo ich das genau wie einsetzen will. https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overview_of_the_code
In der Arduinowelt könnte man den Mist auch so schreiben:
1 | float uint32ToFloat(uint32_t value) |
2 | {
|
3 | union
|
4 | {
|
5 | uint32_t asUint; |
6 | float asFloat; |
7 | } konverter; |
8 | konverter.asUint=value; |
9 | return konverter.asFloat; |
10 | }
|
11 | |
12 | void setup() |
13 | {
|
14 | Serial.begin(9600); |
15 | uint32_t testwert = 0x3c027b70UL; |
16 | Serial.println(uint32ToFloat(testwert),6); |
17 | }
|
18 | |
19 | void loop() |
20 | {
|
21 | }
|
In der Hoffnung, dass man das niemals sonst noch braucht, habe ich die Vereinigung lokal und anonym gemacht.
asdfasd schrieb: > Strict aliasing in C ist, als wenn man nen Lichtschalter unter Strom > setzt und nen Schild drüber hängt "Nur mit Gummihandschuhen betätigen!". Hat aber einen guten Grund, weil der Compiler sonst nach jedem Schreibzugriff über irgendeinen Pointer sämtliche anderen Variablen neu laden müßte - sie könnten sich ja geändert haben. So betrifft diese Misere nur Variablen eines zum Pointer kompatiblen Typen, was ein wichtiger Grund ist, wieso C bei Numerik langsamer als Fortran ist, welches gar kein Aliasing erlaubt. Das kann man aber seit C99 auch abstellen, indem man den Pointer mit restrict behaftet. Ob das allerdings überhaupt Auswirkungen auf die Performance hat, kommt sehr auf den konkreten Code an. Er muß dazu pointer- und rechenintensiv sein, und er muß die Rechenergebnisse auch in die Pointerbereiche schreiben und wieder lesen. Wenn man hingegen eher Idiome verwendet, bei denen man aus Pointerbereichen liest, mit funktionslokalen Variablen rechnet und das am Ende zurückschreibt, wird man keine Auswirkungen bemerken.
Mampf F. schrieb: > Daniel A. schrieb: >> Ich habe mal, weil mir die ganzen Hacks nicht gefallen haben, folgende >> Library geschrieben: >> https://github.com/Daniel-Abrecht/IEEE754_binary_encoder > > Ööööh ... wofür soll das gut sein? > > 32 und 64Bit Float-Zahlen haben doch eine eindeutige binäre > Repräsentation ... Was spricht gegen casten? Eine CPU Architektur muss nicht zwingend IEEE 754 für floating point Operationen, verwenden, und ein float muss nicht zwingend 32bit gross sein. Auch bit und byte order können unterschiedlich sein. Solange man keine Daten zwischen unterschiedlichen Systemen austauscht wäre zumindest memcpy akzeptabel, falls in ein uint32_t aber nur, wenn man noch ein static_assert(sizeof(float)==sizeof(uint32_t),"Float wasn't 4 bytes big"), einbaut. Bei einem cast ist nur nach char* in Ordnung, wegen aliasing. Wenn man jedoch zwischen verschiedenen Geräten Daten austauschen will, muss man sicherstellen, dass diese auf allen möglichen Plattformen identisch gelesen und geschrieben werden, und das ist wofür man diese Library brauchen kann. Wenn du z.B. auf einem x86 little endian System einen float einfach casten würdest, hättest du, obwohl x86 IEEE 754 verwendet, die bytes in little endian statt in network byte order aka big endian. Ich versuche meinen Code immer so zu schreiben, dass er Programmunabhängig ist. Ich versuche möglichst nie annahmen zu Sachen zu machen, die nicht garantiert sind zu funktionieren, so etwas ist einfach Murks, und falls es doch mal nicht anders geht, füge ich zumindest einen compile time check zur Überprüfung der Annahme hinzu. Plattform spezifisches sollte nur an einer möglichst kleinen stelle verwendet werden, sofern man das Plattform spezifisches unbedingt verwenden muss, den Rest kann man abstrahieren. Damit erspart man sich auch später das suchen von scheinbar unerklärlichen Fehlern.
Beitrag #5111357 wurde vom Autor gelöscht.
Amüsanter akademischer Ansatz, aber leicht an der Realität vorbei, denke ich :) Für meinen Geschmack ist die Komplexität für ein triviales Problem zu hoch :) Nicht IEEE754-Floats kommen in der Praxis doch so gut wie nicht vor und die Little- und Big-Endian Problematik wurde doch schon mehrfach gelöst (zB Network-Endianess und die passenden Konvertierungsfunktionen). Die Lib unterstützt auch garnichts anderes außer einer Endian-Art und nur IEEE754. Und Buggy ist sie auch: > Subnormal floating point values (values really close to zero) are > truncated to zero for simplicity reasons. I may change this someday. Da kann von allgemeingültiger Ansatz eigentlich keine Rede sein. In wie weit das besser sein soll als ein float f = *((float*) &u); erschließt sich mir daher nicht.
:
Bearbeitet durch User
Mampf F. schrieb: > Für meinen Geschmack ist die Komplexität für ein triviales Problem zu > hoch :) > > Die Lib unterstützt auch garnichts anderes außer einer Endian-Art und > nur IEEE754. > > Und Buggy ist sie auch: Das ist folgendes aber auch, lies einfach oben: > float f = *((float*) &u); Ich versteh echt nicht warum einige hier so an einer offenbar falschen Lösung kleben wie am Fliegenleim, wo der korrekte Ansatz via memcpy doch bereits gegeben wurde — und gefühlt mindestens 1x pro Monat hier wieder und wieder aufs Tablett kommt. Was ist das Problem bei memcpy? Die 6 zu tippenden Buchstaben wohl kaum... Arduino F. schrieb: > union > { > uint32_t asUint; > float asFloat; > } konverter; > konverter.asUint=value; > return konverter.asFloat; Auch falsch. Beim Lesen von .asFloat kann der Compiler davon ausgtehen, das Schreiben auf .asUint die inkompatible Komponente .asFloat nicht verändert. Dieses "Type Punning per Union" wird zwar von GCC unterstützt (steht irgendwo im Kleingedruckten), konformes C ist es aber nicht. Nochmal: Was ist so schlimm an memcpy? Ist hier sogar weniger zu tippen. Aus z.B.
1 | #include <stdint.h> |
2 | #include <string.h> |
3 | |
4 | static inline float asFloat (uint32_t i) |
5 | {
|
6 | float f; |
7 | memcpy (&f, &i, sizeof f); |
8 | return f; |
9 | }
|
10 | |
11 | static inline uint32_t asUInt (float f) |
12 | {
|
13 | uint32_t i; |
14 | memcpy (&i, &f, sizeof i); |
15 | return i; |
16 | }
|
17 | |
18 | float nextFloat (float f) |
19 | {
|
20 | return asFloat (1 + asUInt (f)); |
21 | }
|
macht avr-gcc -Os
1 | nextFloat: |
2 | subi r22,-1 |
3 | sbci r23,-1 |
4 | sbci r24,-1 |
5 | sbci r25,-1 |
6 | ret |
getestet mit v8, v7, v6, v5, v4.9, v4.8. v4.7, v4.6 und WinAVR-20100110.
Peter II schrieb: > Johann L. schrieb: >> macht avr-gcc -Os > > man sollte die Funktionen aber schon aufrufen. Man sollte den Quellcode schon lesen: Johann L. schrieb: > float nextFloat (float f) > { > return asFloat (1 + asUInt (f)); > }
Ich verstehe nicht, wieso memcpy() besser sein soll, als einen Pointer zu casten. Im gegenteil, es ist unnötiger Overhead. Wenn ich davon ausgehe, daß die vier Bytes einem Float entsprechen dann ist das Casten des Pointers effektiver. Durch den memcpy() Ansatz kopiere ich letztendlich den Speicher UND caste des Datentyp. Was soll daran besser sein? Die Annahme, daß die vier Bytes ein Float seien, ist fragwürdig. Nur mach memcpy() die Sache kein bisschen sicherer.
Stefan U. schrieb: > Ich verstehe nicht, wieso memcpy() besser sein soll, als einen Pointer > zu casten. Im gegenteil, es ist unnötiger Overhead. es stellt das Alignment sicher. Und der Overhead verschwindet, wenn der Compiler sinnvoll optimiert.
Johann L. schrieb: > Dieses "Type Punning per Union" wird zwar von GCC unterstützt (steht > irgendwo im Kleingedruckten), konformes C ist es aber nicht. Seit C99 schon, und das ist doch default beim GCC.
Stefan U. schrieb: > Wenn ich davon ausgehe, daß die vier Bytes einem Float entsprechen dann > ist das Casten des Pointers effektiver. Was nutzt effektiver Code wenn er falsch ist. > Ich verstehe nicht, wieso memcpy() besser sein soll, als einen Pointer > zu casten. Im gegenteil, es ist unnötiger Overhead. Hast du ein konkretes Beispiel?
Stefan U. schrieb: > Durch den memcpy() Ansatz kopiere ich letztendlich den Speicher Nur, wenn Du keine Compiler-Optimierungen aktiv hast, und dann ist Performance offensichtlich sowieso egal. Ansonsten erlaubt die generelle as-if-Philosophie dem Compiler, das memcpy komplett wegzuoptimieren. Da wird dann keine Funktion aufgerufen und kein Speicher kopiert.
Stefan U. schrieb: > Ich verstehe nicht, wieso memcpy() besser sein soll, als einen Pointer > zu casten. Im gegenteil, es ist unnötiger Overhead. Für Leute, die aus der "Assemblerwelt" kommen, ist das natürlich Blödsinn. Ich habe 4 Bytes im Speicher und ob ich die als Ganz- oder Gleitkommzahl verwende ist meine Sache. C führt eine Abstraktionsebene ein. Ein Variable ist nicht einfach eine typisierte Adresse im Speicher die ein paar Bytes belegt. Es sind auch verschiedene Regeln mit dieser Variablen verbunden. Eine davon ist das (verbotene) Aliasing. http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html
Mach einfach einen cast auf float und fertig. Sehe da für Deine Anwendung 0 Probleme.
Johnny B. schrieb: > Mach einfach einen cast auf float und fertig. Sehe da für Deine > Anwendung 0 Probleme. und warum nicht gleich richtig machen? ein memcpy macht auch 0 Probleme.
Moin, Peter II schrieb: > und warum nicht gleich richtig machen? ein memcpy macht auch 0 Probleme. ich bin erst sehr kurz beim GCC. Hab das grad mal ausprobiert, und bin regelrecht begeistert, dass der GCC das memcpy tatsaechlich ganz rausschmeist und durch was sinnvolles ersetzt. Bisher hatte ich immer andere Target Compiler. Und da hatte ich das bisher nicht erlebt. Allerdings hatte ich vorher noch nie alle Optionen selbst unter Kontrolle. Die waren haeufig vorgegeben (und scheinbar in Stein gemeisselt). Wenn das memcpy nicht ersetzt wird, ist das schon ziemlich unperformant. Jedenfalls auf kleineren cores. Ja, die haben meistens keine floats in HW, aber das gilt ja allgemein fuer verschiedene Datentypen. Und auf kleinen Cores ist memcpy einfach extrem lahm und somit "Teufelszeug". Gut zu wissen. Danke
Wenn der Compiler memcpy() weg optimiert, dann ändert sich auch das alignment nicht. Außerdem sehe dann keinen Unterschied zum gecasteten Pointer, der letztendlich auch keine Code erzeugt.
Stefan U. schrieb: > Wenn der Compiler memcpy() weg optimiert, dann ändert sich auch das > alignment nicht. Außerdem sehe dann keinen Unterschied zum gecasteten > Pointer, der letztendlich auch keine Code erzeugt. es gibt halt Tricks wie der Compiler 4 Byte ohne memcpy übertragen kann. Es werden aber immer noch 4 Byte an die Adresse vom float übertragen. Damit stimmt dann das alignment. Wenn man hart ein int auf float castet, dann muss die Adresse vom int auch als float lesbar sein. Auch mit dem cast werden 4 Byte umkopiert.
Mampf F. schrieb: > Die Lib unterstützt auch garnichts anderes außer einer Endian-Art und > nur IEEE754. IEEE 754 ist der standard, und convertier Funktionen zwischen Big und Little endian sind nun wirklich trivial, warum sollte ich denn irgendetwas anderes als IEEE 754 in Big Endian implementieren? Little endian wird doch sowieso nur in einigen MS Formaten und Pseudostandards verwendet, und eventuell noch in einigen Freedesktop "Standards", und von denen halte ich nicht besonders viel. Die C standard liabrary hat auch kein Equivalent zu hton für little endian, bei all den MS Dateiformaten haben die Entwickler das doch direkt ohne Konvertierung rausgeschrieben, jedes MS Programm ist in der hinsicht garantiert kaputt Programiert, aber Windows läuft ja sowieso nur auf x86 und amd64 CPUs. > Und Buggy ist sie auch: > >> Subnormal floating point values (values really close to zero) are >> truncated to zero for simplicity reasons. I may change this someday. Nein, nicht buggy, nur etwas weniger genau für Zahlen mit 35 Nullen hinter dem Komma bei 32bit, oder 307 nullen hinter dem Komma für 64bit. Auf der Logarithmischen Scalala existierten die nummern garnicht mehr, also hat man sie linear verteilt, sinvoll gerundete Resultate in dem bereich hat man also keine. Niemand rechnet absichtlich mit Subnormalen zahlen, einige CPU architekturen die IEEE 754 implementieren haben subnormale ebenfalls einfach ignoriert. Der einzige Zweck von Subnormalen war, dass bei Substraktionen von 2 Subnormalen nummern die Mathematisch nicht 0 ergeben das Ergebnis wirklich nicht 0 ist, aber wegen Ungenauigkeiten bei Floats vergleicht diese sowieso niemand dierekt, auch nicht mit 0, statdessen verwenden bei float vergleichen alle einen Threshold für deren Delta, womit Subnormale wieder absolut sinnlos werden. Es gibt sogar Situationen in denen Subnormale Probleme machen. Wenn dir die Subnormalen aber wirklich so wichtig sind, kannst du ja einfach die Librarie erweitern, und einen Pull request machen. Solange keine Hacks oder Annahmen zur Implementation der Floats auf Hardwareebene enthalten sind, nehme ich diese gerne an. Grösste subnormalen: http://www.binaryconvert.com/result_float.html?hexadecimal=007FFFFF http://www.binaryconvert.com/result_double.html?hexadecimal=000FFFFFFFFFFFFF Argumente gegen Subnormale: https://en.m.wikipedia.org/wiki/Denormal_number#Performance_issues
> es gibt halt Tricks wie der Compiler 4 Byte ohne memcpy übertragen kann.
Wie kopiert man denn 4 Bytes ohne Code?
Ich verstehe gar nicht, warum ihr euch so über das float Format ereifert. Ralfs Programm legt einen float Wert im Speicher ab, um ihn später wieder zu lesen. Er weiß, daß seine float Werte 4 Bytes groß sind. Was hat das nun mit dem konkreten IEEE Format zu tun? Nichts! Sein Programm wird immer imstande sein, die float Werte zu lesen, die es vorher geschrieben hat. Ganz egal, wie sein Compiler oder die CPU das intern handhabt.
Stefan U. schrieb: > Wie kopiert man denn 4 Bytes ohne Code? ich habe nicht geschrieben ohne code sondern ohne Funktionsaufruf. Wenn man 4 Byte in einer float variable braucht muss immer kopiert werden!.
Beitrag #5111886 wurde von einem Moderator gelöscht.
Arduino F. schrieb im Beitrag #5111886: > Nöö... > > Die Daten, bei meiner Union Geschichte, werden komplett in den Registern > gehalten, wenn man die Funktion inline macht. willst du die ganze zeit die Variable in der Union behalten? Oder irgendwann mal in eine float variable? Spätestens dann wird kopiert. wenn es nur als return verwendet wird, wird es auch mit memcpy auf das gleiche optimiert. Wenn man es richtig macht muss man memcyp verwenden, alles andere sind Hacks die ihre Einschränkung haben.
Daniel A. schrieb: > IEEE 754 ist der standard, und convertier Funktionen zwischen Big und > Little endian sind nun wirklich trivial, warum sollte ich denn > irgendetwas anderes als IEEE 754 in Big Endian implementieren? und Daniel A. schrieb: > Eine CPU Architektur muss nicht zwingend IEEE 754 für floating point > Operationen, verwenden, und ein float muss nicht zwingend 32bit gross > sein. und Daniel A. schrieb: > Auch bit und byte order können unterschiedlich sein. Solange man > keine Daten zwischen unterschiedlichen Systemen austauscht wäre > zumindest memcpy akzeptabel ... Wenn man jedoch zwischen verschiedenen > Geräten Daten austauschen will,muss man sicherstellen, dass diese auf > allen möglichen Plattformen > identisch gelesen und geschrieben werden, und das ist wofür man diese > Library brauchen kann. > Ich versuche meinen Code immer so zu schreiben, dass er > Programmunabhängig ist. Wüsste nicht, wie deine Aussagen irgendwie zusammen passen ... So oder so, deine Lösung ist nichts, was ich mir bookmarken werde, weil ich den Sinn dahinter nicht erkennen kann :)
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.