Wie stelle ich auf portable Weise sicher, dass die folgende Berechnung
garantiert als Floatingpoint Operation ausgeführt wird und erst am
Schluss wieder auf Integer konvertiert wird? (zwecks korrekter Rundung)
uint16_t va = 64000;
uint16_t vb = 0;
vb = (uint_16_t)(4000000 / va - 0.5) //reicht das?
vb = (uint_16_t)(4000000.0 / va - 0.5) //oder so?
vb = (uint_16_t)((float)4000000 / va - 0.5) //oder sogar so?
Sind die drei Varianten identisch?
Peter schrieb:> Sind die drei Varianten identisch?
Nein, die erste funktioniert nicht, und die zweite ist äquivalent zu
vb = (uint16_t)((double)4000000 / va - 0.5).
warum willst du unbedingt das als float rechnen, wegen dem Runden? Oder
ist das ganze nur ein einfaches Beispiel?
Denn hier kann man die selbe genauigkeit auch mit int erreichen.
Peter schrieb:> ..und die floatrechnung "kostet" nichts, ich habe da einen MPC8314 mit> einem PowerPC e300c3 Core, der hat eine FPU intus!
Dann solltest du aber den Compiler entscheiden lassen, ob er float oder
double nehmen will. Würde mich nicht wundern, wenn float langsamer als
double rechnet.
Wenn dir Floating Point nichts kostet, dann willst du nämlich um float
einen Bogen machen, wie der Teufel ums Weihwasser.
>dann rechne doch anders:>vb = (4000000+(va/2)) / va;
Ob das wirklich schneller ist?
>Dann solltest du aber den Compiler entscheiden lassen, ob er float oder>double nehmen will.
Wie lasse ich das den Compiler entscheiden?
>Würde mich nicht wundern, wenn float langsamer als double rechnet.
Ich gehe schon davon aus, das ein float (4Byte) schneller ist als ein
double (8Byte), ist nämlich ein 32Bit System, kein 64Bit!
>Wenn dir Floating Point nichts kostet, dann willst du nämlich um float>einen Bogen machen, wie der Teufel ums Weihwasser.
Häää...? Was willst Du damit sagen?
Peter schrieb:>>Dann solltest du aber den Compiler entscheiden lassen, ob er float oder>>double nehmen will.> Wie lasse ich das den Compiler entscheiden?
vb = (uint_16_t)(4000000.0 / va - 0.5)
>>Würde mich nicht wundern, wenn float langsamer als double rechnet.> Ich gehe schon davon aus, das ein float (4Byte) schneller ist als ein> double (8Byte), ist nämlich ein 32Bit System, kein 64Bit!
Das hat damit nichts zu tun.
Es kommt drauf an, mit welcher Bitbreite die FPU rechnet.
Bei den Intel FPU, die zb mit dem 486 aufkamen (und dann im Pentium in
die CPU integriert wurden), rechnet die FPU grundsätzlich mit 80 Bit.
Lässt du den Compiler schalten und walten, dann lädt er dir die FPU mit
den Werten, lässt die Berechnung machen und die FPU dann alles auf int
runterbrechen. Zwingst du ihm zwischendurch ein float auf, dann muss der
Compiler das 80 Bit Ergebnis zwischendurch erst mal auf 32 Bit
runterkonvertieren, um deine Float-Vorgabe zu erfüllen, und dann erst
dieses Zwischenergebnis in int konvertieren. Die eigentliche Floating
Point Berechnung findet aber immer mit 80 Bit Floating Point Zahlen
statt.
Das muss jetzt bei deiner FPU nicht so sein, ist aber wahrscheinlich
ähnlich, da keine Chip-Schmiede in eine FPU 2 komplett gleiche
Rechenwerke nur mit unterschiedlichen Bitbreiten einbauen wird.
>>Wenn dir Floating Point nichts kostet, dann willst du nämlich um float>>einen Bogen machen, wie der Teufel ums Weihwasser.> Häää...? Was willst Du damit sagen?
Das du 'float' nicht benutzen willst, weil dich nämlich die Auflösung
umbringt. Mit Float hast du gerade mal 5 bis 6 signifikante Stellen und
das ist nicht viel.
Es gibt nur einen einzigen Grund, warum man float nehmen will: Wenn man
Millionen davon im Speicher speichern muss. Aber abgesehen davon gibt es
keinen Grund float dem double vorzuziehen. Und schon gar nicht wenn man
damit ein wenig rumrechnen muss.
Peter schrieb:> Ich gehe schon davon aus, das ein float (4Byte) schneller ist als ein> double (8Byte), ist nämlich ein 32Bit System, kein 64Bit!
Und was ist die native size der FPU?
Peter II schrieb:> ich denk schon, immerhin fehlt die ganze float int wandlung weg.
Wenn der Controller die Integerdivision in der Bibliothek machen
muss, Gleitkomma aber in der FPU, dann stimmt deine Annahme nicht
mehr. Selbst, wenn er eine Integerdivision in Hardware hat, wird
der Unterschied zwischen beiden vernachlässigbar sein, da die
Umwandlung float/int typischerweise auch in der FPU und damit
recht schnell passiert. Das konnte meiner Erinnerung nach schon
ein i8087.
>vb = (uint_16_t)(4000000.0 / va - 0.5)
Diese zweite Variante genügt: da der Divident (4000000.0) eine
Fließkommakonstante ist, wird der Divisor vor der Operation implizit
ebenfalls in einen Fließkommawert konvertiert. Dann wird die Division
durchgeführt, 0.5 subtrahiert und dann wegen des explititen casts
(uint16_t) in eine Ganzzahl konvertiert. Beachte Dabei aber bitte, dass
explizite casts nicht runden im mathematischen Sinn, sondern nur die
Nachkommastellen "abschneiden".
Jörg Wunsch schrieb:> Wenn der Controller die Integerdivision in der Bibliothek machen> muss, Gleitkomma aber in der FPU, dann stimmt deine Annahme nicht> mehr. Selbst, wenn er eine Integerdivision in Hardware hat, wird> der Unterschied zwischen beiden vernachlässigbar sein, da die> Umwandlung float/int typischerweise auch in der FPU und damit> recht schnell passiert.
gibt es wirklich eine CPU die eine FPU hat aber keine hardware division?
Da es sich hierbei eh nur um ein paar takte handelt ist klar das man da
nicht sehr viel gut machen kann. Aber ich meide aus Prinzip float/double
wenn ich kann und auch am ende das gleiche rauskommt.
High Performer schrieb:> Beachte Dabei aber bitte, dass> explizite casts nicht runden im mathematischen Sinn, sondern nur die> Nachkommastellen "abschneiden".
aus dem grund rechnet er ja -0.5 dann ist es wieder ein runden.
Peter II schrieb:> Aber ich meide aus Prinzip float/double> wenn ich kann und auch am ende das gleiche rauskommt.
Ja, eben. Gegen dieses "ich bin aus Prinzip gegen float/double" habe
ich persönlich was, da diese Abneigung in aller Regel völlig
irrational und technisch unbegründet ist.
Selbst auf einem simplen AVR ist eine Gleitkommarechnung meiner
Meinung nach in vielen Fällen sehr viel sinnvoller, als dass man sich
erstmal um Wertebereiche, Operandenreihenfolgen und dergleichen den
Kopf zerbricht (und dann möglicherweise trotzdem noch was verhaun hat
und in Überlaufprobleme oder sowas rennt), nur um auf-Teufel-komm-raus
ein paar Gleitkommarechnungen zu vermeiden, obwohl der Controller die
Ressourcen dafür locker parat hätte. Für viele ingenieurtechnische
Probleme sind Gleitkommazahlen nun einmal die am besten passende
Ausdrucksweise.
Die Umstellung derartiger Rechnungen auf Ganzzahl kann man dann unter
"Optimierung" verbuchen, getreu dem Motto: "Never start optimizing
before you have profiled it."
Jörg Wunsch schrieb:> Die Umstellung derartiger Rechnungen auf Ganzzahl kann man dann unter> "Optimierung" verbuchen, getreu dem Motto: "Never start optimizing> before you have profiled it."
dafür war es bei dem Beispiel eh schon zu spät. Entweder schreibt man
vb = round((float)4000000 / va);
dann ist es genau was man auch haben willt, wenn man es schon umstellt
kann man gleich auf float verzichten.
Und ja ich mach mir vorher gedanken über mögliche optmierungen, denn
teilweise beieinflussen sie die Programmlogik.
Ich habe leider keine gelegenheit auf verschienden CPUs zu testen, ich
bin aber immer noch überzeugt das die int lösung die schnellste ist. Und
wenn es nur einen Takt ist.
Dieser PowerPC Prozessor enthält eine sehr schnelle 64-Bit FPU, bei der
nur die Division in "float" deutlich schneller ist als in "double".
Bei solchen Prozessoren sollte man Rechnungen, die teilweise mit
Fliesskommawerten durchgeführt werden, so wenig Konvertierungen zwischen
Fliesskommadarstellung und Integerdarstellung wie möglich durchführen.
Man sollte also Rechnungen entweder konsequent im Fliesskommaformat
durchführen, oder garnicht. Denn jede Übertragung von Werten zwischen
den verschiedenen execution domains bedeutet Aufwand, u.U. sogar ein
Mehrfaches einer Rechenoperation.
>aus dem grund rechnet er ja -0.5 dann ist es wieder ein runden.
Dachte ich auch kurz, aber: nein, es wird kein Runden draus. Er müsste
0,5 addieren, nicht subtrahieren. Da er ja nicht geschrieben hat, was
die Berechnung darstellen soll, hatte ich die -0,5 einfach mal nicht
kommentiert.
High Performer schrieb:> Dachte ich auch kurz, aber: nein, es wird kein Runden draus.
dann schein er wohl einen fehler gemacht zu haben, denn er schreibt:
> Ja, wegen dem runden.
Karl Heinz Buchegger schrieb:> Das du 'float' nicht benutzen willst, weil dich nämlich die Auflösung> umbringt. Mit Float hast du gerade mal 5 bis 6 signifikante Stellen und> das ist nicht viel.
Aber nur, wenn die FPU double-precision kann - z.B. der STM32F4 (ein
Cortex-M4F) kann nur single-precision
Jörg Wunsch schrieb:> mehr. Selbst, wenn er eine Integerdivision in Hardware hat, wird> der Unterschied zwischen beiden vernachlässigbar sein, da die> Umwandlung float/int typischerweise auch in der FPU und damit> recht schnell passiert. Das konnte meiner Erinnerung nach schon> ein i8087.
Bei PC-Prozessoren ist die Konvertierung von Fliesskommadaten zu
Integers über x87 Befehle ziemlich aufwendig, da der Wert per FPU-Befehl
als Integer gespeichert und anschliessend per non-FPU Befehl wieder
geladen werden muss (oder umgekehrt). Ein direkter Datenpfad bestand im
ursprünglichen Design nicht.
Zwar gibt es SSE Befehle, die ohne sichtbare Zwischenschaltung des
Speichers auskommen, aber je nach Implementierung kann der Schein
trügen, wie man an den Laufzeiten erkennt. Bei AMDs Prozessoren sind die
execution domains so klar voneinander abgetrennt und unterschiedlich
strukturiert, dass es intern trotzdem praktisch aufs Gleiche rausläuft
wie bei einem Koprozessor.
Bei den PowerPCs dürfte das nicht wesentlich anders aussehen, denn die
Historie dieser ISA ist grob ähnlich. Diese Architektur wurde anfangs in
mehreren Chips implementiert und darauf optimiert. Sie besitzt daher 3
klar voneinander getrennte Befehlsklassen und Registersets, für
Integer-, Fliesskomma- und Branch-Unit.
Bezogen auf die hier betrachtete Operation bin ich ziemlich sicher, dass
man mit einer reinen Integer-Rechnung deutlich besser fährt, als mit
einer Div/Add Operation via FPU und allem anderen als Integerrechnung.
Peter II schrieb:> Peter schrieb:>> Ob das wirklich schneller ist?>> ich denk schon, immerhin fehlt die ganze float int wandlung weg.
Beim Software-Renderer von Quake wurde beim Rendern der Texturen eine
Perspektivkorrektur durchgeführt (beinhaltet primär eine Division).
Diese wurde statt in Integer auf der FPU durchgeführt und war dadurch
quasi kostenlos. Der Grund: Die FPU kann parallel zur CPU rechnen,
wodurch die Divison gleichzeitig mit den sonstigen Berechnungen der CPU
ausgeführt werden konnte und somit keine zusätzliche Zeit in Anspruch
genommen hat. Zugegebenermaßen war dieser Code aber auch von Hand in
Assembler geschrieben, und die Instruktionen waren genau so angeordnet,
daß man diesen Umstand möglichst gut nutzen konnte.
Rolf Magnus schrieb:> Zugegebenermaßen war dieser Code aber auch von Hand in> Assembler geschrieben, und die Instruktionen waren genau so angeordnet,> daß man diesen Umstand möglichst gut nutzen konnte.
und vermutlich wurde auch der wert dann weiter als float/double
behandelt. Hier will er aber vorher und nachher ein int haben.
>dann schein er wohl einen fehler gemacht zu haben, denn er schreibt:>> Ja, wegen dem runden.>genauer: 2 Fehler, denn es heißt wegen des Rundens :-)
Die Rechnung ist schon korrekt, die Formel lautet nämlich:
vb = runden((4000000 / va) - 1)
daraus wird:
vb = int((4000000 / va) - 0.5)
Ich verwende nun doch die Integer Berechnung, ich denke dies ist sicher
nicht langsamer als die Berechnung über float oder double.
vb = (4000000-(va/2)) / va;
Peter schrieb:> Ich verwende nun doch die Integer Berechnung, ich denke dies ist sicher> nicht langsamer als die Berechnung über float oder double.
du könntest uns ja mal den asm code für beide varienten posten
Das musst du auch sonst nicht. Eine dezimale lexikalische Konstante
kriegt automtisch den kleinsten Integer Typ mit Vorzeichen in den sie
reinpasst, mindestens int.
>vb = int((4000000UL / va) - 0.5)
Das wird nicht funktionieren, da (4000000UL / va) eine Integerdivision
darstellt. Das wolltest Du doch gerade vermeiden, oder nicht?
High Performer schrieb:> as wird nicht funktionieren, da (4000000UL / va) eine Integerdivision> darstellt. Das wolltest Du doch gerade vermeiden, oder nicht?
nein er wollte das runden geschickt gelöst bekommen. Bei einer
Integerdivision wird ja nicht gerundet und darum wurde versucht es mit
float zu lösen.
Peter II schrieb:> High Performer schrieb:>> as wird nicht funktionieren, da (4000000UL / va) eine Integerdivision>> darstellt. Das wolltest Du doch gerade vermeiden, oder nicht?>> nein er wollte das runden geschickt gelöst bekommen.
Ja eben, und da bringt es nichts, erst eine Integer-Division zu machen
und danach dann auf float zu erweitern und 0.5 abzuziehen.
> Bei einer Integerdivision wird ja nicht gerundet und darum wurde versucht es mit> float zu lösen.
Und genau das passiert beim obigen Beispiel nicht, und daher wird das
nicht wie gewünscht funktionieren.