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".
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
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... :)
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" ;-)
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?
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.
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" ...
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.
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.
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
staticinlinefloatasFloat(uint32_ti)
5
{
6
floatf;
7
memcpy(&f,&i,sizeoff);
8
returnf;
9
}
10
11
staticinlineuint32_tasUInt(floatf)
12
{
13
uint32_ti;
14
memcpy(&i,&f,sizeofi);
15
returni;
16
}
17
18
floatnextFloat(floatf)
19
{
20
returnasFloat(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
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=007FFFFFhttp://www.binaryconvert.com/result_double.html?hexadecimal=000FFFFFFFFFFFFF
Argumente gegen Subnormale:
https://en.m.wikipedia.org/wiki/Denormal_number#Performance_issues
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!.
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 :)