Hallo,
ich habe hier einen Fehler in der Berechnung, der vermutlich von
Rundungen / falschen Casts etc herrührt. Wenn ich es im Taschenrechner
berechne, kommt das richtige raus, allerdings nicht i Code. Das ganze
rennt auf einem AT32UC3C1128C.
hier dir Formel:
LiquLev und LiftParamR1Vol ist vom Typ uint16
LiftParamR1Vol hat zum Berechnugszeitpunkt den Wert 1300, habe das auch
im Debug gesehen -> in dem fall sollte für LiqLev ca 51 oder 52
rauskommen, es kommt aber 30 raus.
Wo ist hier der fehler?
Danke!
Werz schrieb:> LiquLev = 10 * (float)((-0,000004 * (LiftParamR1Vol/10) *> (LiftParamR1Vol/10) + 0,0405 * LiftParamR1Vol - 0,003));
Warum sollte es das nicht sein? Das ist gültiger C code, der, wenn man
die beiden Deklarationen hinzufügt, und es in eine Funktion packt,
sowohl mit SDCC als auch mit GCC ohne Warnungen kompiliert:
~~~~
#include <stdint.h>
uint16_t LiquLev, LiftParamR1Vol;
void f(void)
{
LiquLev = 10 * (float)((-0,000004 * (LiftParamR1Vol/10) *
(LiftParamR1Vol/10) + 0,0405 * LiftParamR1Vol - 0,003));
}
~~~~
Allerdings vermute auch ich, dass die dreifache Verwendung des
Kommaoperators hier vom Programmierer nicht beabsichtigt war.
P.S.: In der Vorschau funktionieren die Tilden, um Code zu markieren,
aber im Post selbst anscheinend nicht.
Ist wirklich beabsichtigt, die Division durch 10 als Ganzzahldivision
durchzuführen? Beim Testwert (130 oder 1300, also einem Vielfachen von
10) ergibt es zwar keinen Unterschied, aber wenn man eine
Gleitkommadivision eines float will (man also bei 13/10 lieber 1.3f
statt 1 erhält) sollte man die 10 durch 10.0f ersetzen.
Auch ansonsten ist mir nicht klar, welche Genauigkeit gewünscht ist. Mit
Zuweisung an float am Ende, aber 0.0405 in der Berechnung würde mit
double gerechnet, und am Ende auf float gerundet. das könnte erwünscht
sein, muss es aber nicht. Wenn es schnell gehen soll und die Genauigkeit
von float ausreicht, kann man alles als float rechnen (also mit 0.0405f
statt 0.0405).
Schon 6 Fehler in einer Programmzeile gefunden?
Ey Leute, die Frage ist doch nicht ernst gemeint. So viele Fehler in
einer Zeile - das kann kein Zufall sein. Der gute Mann hat doch
absichtlich die maximale Anzahl an Fehlern eingebaut.
Der Opa aus der Muppet Show schrieb:> Schon 6 Fehler in einer Programmzeile gefunden?>> Ey Leute, die Frage ist doch nicht ernst gemeint. So viele Fehler in> einer Zeile - das kann kein Zufall sein.
10.0 statt 10 wäre auch eine gute Idee.
Dies Anhäufung ist sicher kein Zufall sondern Ausdruck eines
pädagogischen Totalversagens. Das passiert eben, wenn man Schultrottel
an einen Compiler lässt, ohne vorher eine Schulung/Prüfung zu
durchlaufen.
Werz schrieb:> Wo ist hier der fehler?
Finde es raus. Guck dir die Werte der Teilterme an und vergleiche sie
mit dem Wert, den du jeweils erwartest. Da wo die Abweichung auftritt,
ist höchstwahrscheinlich der Fehler nicht weit.
Werz schrieb:> AT32UC3C1128C
Und um einen 32bit Prozessor mit 16bit Daten zu quälen braucht man auch
gute Gründe ...
Vielleicht hätte man doch erst mit einer Sprache ohne
Type-konvetierungen mit eingebauten "Schuß ins Knie" anfangen sollen.
Und sei es um nur den Algo vor der C-Implementierung zu verifizieren.
Wolfgang schrieb:> Werz schrieb:>> Wo ist hier der fehler?>> Finde es raus. Guck dir die Werte der Teilterme an und vergleiche sie> mit dem Wert, den du jeweils erwartest. Da wo die Abweichung auftritt,> ist höchstwahrscheinlich der Fehler nicht weit.
Genau. Prinzip "teile und herrsche". Also statt einen Monsterterm
hinzuschreiben und dann damit überfordert zu sein, dass das Endergebnis
nicht stimmt, einfach in einzelne Teile zerlegen und die separat
berechnen und ausgeben lassen.
Früher gab es noch printf.
Dann hat man die Formel in einzelne Teile zerlegt, und sich jeden
Schritt ausgeben. Dann hat man die Schritte mit seiner 'Papier'-Rechnung
verglichen.
Ich kann nicht verstehen, warum man das printf abgeschafft hat. Es hat
früher so vielen Programmierern geholfen. Die heutige Jugend kann jetzt
nur noch raten, wo die Fehler sind.
edit: nicht zu klein, aber der wertebereich von float ist recht klein
... Da wird dann nichts mehr sinnvolles rauskommen, wenn man das als
ersten term benutzt
Jörg W. schrieb:> Guest schrieb:>> -0,000004 ist vermutlich schon zu klein für float.>> [ ] Du hast das Prinzip von Gleitkommazahlen verstanden
Jau, IEE754 implementiert.
Ich weiß, was float kann und das ist schon grenzwertig.
Hallo,
Jörg W. schrieb:> Wohin "printet" dein printf auf einem UC3?
Nun ja, man könnte sprintf() nutzen und die erhaltene Zeichenkette per
serieller Schnittstelle ausgeben.
Oder man prüft die Berechnung in diesem Fall mit einem kurzen Programm
auf einem PC.
rhf
Guest schrieb:> Ich weiß, was float kann und das ist schon grenzwertig.
Ein Problem wäre es, wenn in einem Teil eines additiven Ausdrucks eine
sehr große und in einem anderen eine sehr kleine Zahl steht.
1
130 · 130 · -0.000004 = -0.0676
2
1300 · 0.0405 = 52.65
3
-0.03 / 10 = -0.003
Wenn hier was under den Tisch fällt, dann ist es der konstante Summand
in der dritten Zeile. Vomm quadratischen Summanden der ersten Zeile sind
zumindest noch zwei Stellen signifikant für float – allerdings hat der
lineare Summand der zweiten Zeile mit Abstand den größten Einfluss aufs
Ergebnis.
[Doofen Rechenfehler korrigiert]
Roland F. schrieb:> Nun ja, man könnte sprintf() nutzen und die erhaltene Zeichenkette per> serieller Schnittstelle ausgeben.
Irgendwie scheint er es ja überprüft zu haben, vielleicht auch mit'm
Debugger.
Jörg W. schrieb:> PittyJ schrieb:>> Ich kann nicht verstehen, warum man das printf abgeschafft hat.>> Wohin "printet" dein printf auf einem UC3?
Ich habe hier einem STM32H
Da mache ich snprintf() in einen char Buffer.
Danach gibt HAL_UART_Transmit() mir das auf eine serielle Schnittstelle
aus.
Irgendeine Kommunikation muss doch vorhanden sein???
Also den Leuten heute muss man wirklich alles vorkauen...
Jörg W. schrieb:> Guest schrieb:>> -0,000004 ist vermutlich schon zu klein für float.>> [ ] Du hast das Prinzip von Gleitkommazahlen verstanden
Hihi.. Naja, ein unbeabsichtigtes Stückchen Wahrheit ist schon dabei: es
ist aus meiner Sicht sinnlos, so eine Formel im Quellcode
hinzuschreiben, sowas sollte man sinnvollerweise zusammenkürzen, so daß
sie kein fast endloser Bandwurm mehr ist. Ebenfalls halte ich es für
sinnvoll, das einzige echte Argument (LiftParamR1Vol) zuvor im float
Format zu haben und auf eigentlich überflüssige typcasts zu verzichten.
Aber es scheint mir, als ob gerade in Kreisen der C-Programierer lieber
händeweise typcasts in den Quellcode geworfen würden, als zuvor mal
nachzudenken.
W.S.
Werz schrieb:> LiquLev = 10 * (float)((-0,000004 * (LiftParamR1Vol/10) *> (LiftParamR1Vol/10) + 0,0405 * LiftParamR1Vol - 0,003));
Fehler ist : )) Anfang hast du 2 und am ende auch 2. Wieso? Entwäder zu
viele, oder eins ist an einer falsche stellt.
Da ich bei C solche Sachen mir nie merken kann wie und wann eine
Variable konvertiert oder kastiert wird, mache ich lieber immer so:
float LiftParamR1Vol_f = (float) LiftParamR1Vol;
float LiftParamR1Vol_div10_f = (float) LiftParamR1Vol_f/10.0;
float A = -0.000004 LiftParamR1Vol_div10_f LiftParamR1Vol_div10_f;
float B = 0.0405 * LiftParamR1Vol_f;
LiquLev = A;
LiquLev += B;
LiquLev -= 0.003 <-- Das hier macht nicht viel Sinn. Da muss bestimmt
eine () falsch irgendwo sein
LiquLev *= 10.0;
Fpgakuechle K. schrieb:> Und Division sollte man auch aus Rechenzeitgründen vermeiden resp. in> Multiplikation mit Reziprok umwandeln.
Die Formel macht insgesamt keinen Sinn. Solange der TO nicht sagt, wo er
das her hat, oder was das soll, ist es vergebliche Mühe.
A. S. schrieb:> Die Formel macht insgesamt keinen Sinn.
Naja, ist 'ne quadratische Approximation von irgendwas. Koeffizienten
möglicherweise empirisch ermittelt, wobei nicht ganz klar ist, was die
Dividiererei und Multiplizierere mit den Zehnen soll.
Fpgakuechle K. schrieb:> also statt: (-0,000004 · (LiftParamR1Vol/10) · (LiftParamR1Vol/10)> simpler: -0.4E-7 · LiftParamR1Vol · LiftParamR1Vol
Erledigt nicht der Compiler derartige Optimierungen von selbst?
Stefan ⛄ F. schrieb:> Erledigt nicht der Compiler derartige Optimierungen von selbst?
Normalerweise schon, aber in dem Falle hat er nur beim AVR die Division
komplett eliminiert (dort wäre sie aber auch am teuersten), während er
es bei ARM und amd64 nicht getan hat.
Jörg W. schrieb:> Normalerweise schon, aber in dem Falle hat er nur beim AVR die Division> komplett eliminiert (dort wäre sie aber auch am teuersten), während er> es bei ARM und amd64 nicht getan hat.
Das wundert mich: Der Compiler eliminiert ein (uint16/10)? Das heisst,
z.B. 0.4*(9/10) werden dann nicht 0, sondern 0.36?
A. S. schrieb:> 0.4*(9/10)
Wird vom Compiler komplett weg optimiert und durch eine Konstante
ersetzte. Das macht er mit allen konstanten Ausdrücken und
Teilausdrücken.
1
#include<stdio.h>
2
intmain()
3
{
4
printf("%f",0.4*(9/10));
5
}
Erzeugt auf meinem PC die Ausgabe "0.000000".
Begründung: Der Teilausdruck 9/10 ergibt 0. Da beide Operanden Integer
sind, wird die Berechnung mit einem Algorithmus durchgeführt, der
Integer zurück liefert, also 0.
Anders sieht es aus, wenn man die Klammer weg lässt. Dann löst er
nämlich von links nach rechts auf: 0.4·9 ergibt 3.6 und der nächste
Schritt 3.6/10 ergibt dann 0.36.
Im obigen Programm sind aber Variablen beteiligt, deswegen muss der
Ausdruck zur Laufzeit berechnet werden. Meine Frage zielte darauf ab,
dass der Compiler den Ausdruck automatisch umstellt, wenn das für die
Performance gut ist.
Ich weiß zum Beispiel mit Sicherheit, dass x/4 automatisch auf eine
Shift Operation umgebaut wird, wenn x eine Integer Variable ist.
Stefan ⛄ F. schrieb:> A. S. schrieb:>> 0.4*(9/10)>> Wird vom Compiler komplett weg optimiert und durch eine Konstante> ersetzte.
Darum geht es doch hier gar nicht.
> Im obigen Programm sind aber Variablen beteiligt, deswegen muss der> Ausdruck zur Laufzeit berechnet werden. Meine Frage zielte darauf ab,> dass der Compiler den Ausdruck automatisch umstellt, wenn das für die> Performance gut ist.
Aber auch zur Laufzeit darf er das nicht tun, wenn sich das Ergebnis
dadurch ändern würde. Es ging ja um das hier:
Stefan ⛄ F. schrieb:> Fpgakuechle K. schrieb:>> also statt: (-0,000004 · (LiftParamR1Vol/10) · (LiftParamR1Vol/10)>> simpler: -0.4E-7 · LiftParamR1Vol · LiftParamR1Vol>> Erledigt nicht der Compiler derartige Optimierungen von selbst?
Und hier ist ja genau das gegeben, was du oben geschrieben hast. Wenn
LiftParamR1Vol < 10, dann muss das Ergebnis 0 sein, was durch die
Optimierung nicht mehr der Fall wäre.
Rolf M. schrieb:> Und hier ist ja genau das gegeben, was du oben geschrieben hast. Wenn> LiftParamR1Vol < 10, dann muss das Ergebnis 0 sein, was durch die> Optimierung nicht mehr der Fall wäre.
Ach ja, ich habe nicht an die Klammern gedacht. Die Optimierung die
Fpgakuechle vorschlug darf der Compiler gar nicht anwenden.
Was wäre, wenn LiftParamR1Vol ein float oder double wäre? Dürfte der
Compiler den Ausdruck dann umstellen? Das Resultat wäre vermutlich mit
einem anderen Rundungsfehler*, wäre das dem Compiler erlaubt?
*) Sorry, ich weiß nicht wie man die Fehler durch Beschränkungen des
float/double Formates richtig nennt.
A. S. schrieb:> Der Compiler eliminiert ein (uint16/10)?
Nein, ich hatte, damit es vergleichbar zum "voroptimierten" Ausdruck
ist, mal angenommen, dass der TE eigentlich wohl /10.0 gemeint hat.
Andernfalls hätte das alles eh keinen rechten Sinn.
> Erledigt nicht der Compiler derartige Optimierungen von selbst?
(Irgend-) ein Compiler macht nur die Umsetzung v. C in Binärcode
entsprechend der gerade zutreffenden Sprachdefinition resp LRM (Language
Reference Manual) (und auch dabei macht ein Compiler Fehler resp.
Eigenheiten).
Alles anderer ist Herstellerabhängiges 'Zuckerwerk', auf das man sich
nicht verlassen kann.
> Ich weiß zum Beispiel mit Sicherheit, dass x/4 automatisch auf eine> Shift Operation umgebaut wird, wenn x eine Integer Variable ist.
Das ist aber nur die halbe Wahrheit, wenn man die Vorzeichendarstellung
betrachtet. Denn dann muß bei Zahlen mit Vorzeichen, eine '1' geschiftet
werden, während bei denen ohne eine '0' eingeshiftet wird.
Und dann wäre auch noch zu klären, wie sich die Flags verhalten, eine
Division könnte mehr oder anders Flags (Zero, Sign, Carries) beeinflußen
als eine Shiftoperation (keine Flag-Änderung).
Und wenn wenn Operationen 'Automatisch' geschieht, würde ich diesen
Compiler für den Produktivbetrieb sperren.
Schön wenn der Compiler ein paar Optimierungsmöglichkeiten findet, die
sollte man aber m.E. in der nächsten Softwarerevision in den Code
händisch einpflegen und dokumentieren.
Nochmals, das TO-Problem ist weniger ein 'C' als ein Numerik-Problem.
Und dabei muß der Algorithmendesigner darauf achten, das er die
Rechenoperationen korrekt (Für Zahlendarstellung) auswählt und sich
nicht auf Architektur und Herstellerabhängige Compiler verlässt.
Es ist halt ein Problem, das es in der Schulmathematik keine Shifts gibt
sondern eben nur Multiplikation / Addition und Informatiker dazu neigen
ihre algos wie schulmathematiker zu notieren und nicht wie 'Numeriker',
die eben mehr Operationen kennen als die vier Grundrechenarten. Bei den
Crpto-Experten funktioniert das besser, die kennen
'Formelzeichen'/Notationen auch für 'Bitoperarionen' wie XOR etc. bspw.
https://commons.wikimedia.org/wiki/File:International_Data_Encryption_Algorithm_InfoBox_Diagram.svg
Stefan ⛄ F. schrieb:> Im obigen Programm sind aber Variablen beteiligt,
Ich hatte jetzt angenommen, dass das klar ist.
Stefan ⛄ F. schrieb:> Ich weiß zum Beispiel mit Sicherheit, dass x/4 automatisch auf eine> Shift Operation umgebaut wird, wenn x eine Integer Variable ist.
Das geht aber nur genau dann, wenn -1/4=-1. Ansonsten geht shift bei
signed nicht.
Jörg W. schrieb:> wobei nicht ganz klar ist, was die> Dividiererei und Multiplizierere mit den Zehnen soll.
vieleicht Runterskalieren von Eingangsgrößen um int-Übelaufe zu
vermeiden?
Oder wenn es aus einem ADC stammt, die wegen niedrigen SNR verrauschten
(und damit unbrauchbaren) lsb zu entfernen (dann wäre aber Bit-shift die
bessere Wahl)
Fpgakuechle K. schrieb:> Alles anderer ist Herstellerabhängiges 'Zuckerwerk', auf das man sich> nicht verlassen kann.
Ich verlasse mich durchaus darauf, dass jeder Compiler Ausdrücke wie
timeout = now + 3·24·60·60·1000
optimiert. Mit Compilern die das nicht tun, will ich gar nicht arbeiten.
Das ist für mich keine Frage von 'Zuckerwerk' sondern ob es sich um ein
vernünftiges Arbeitsmittel handelt, oder nicht.
Zwischen gar nicht optimieren und "alles" optimieren gibt es natürlich
eine breite Grauzone deren Existenz man kennen sollte. Man sich da (wie
du sagst) nicht automatisch auf alles verlassen, was man gerne hätte.
A. S. schrieb:> Das geht aber nur genau dann, wenn -1/4=-1.> Ansonsten geht shift bei signed nicht.
Klar, ich hatte dabei nur nicht-negative ganze Zahlen im Sinn.
Stefan ⛄ F. schrieb:> Was wäre, wenn LiftParamR1Vol ein float oder double wäre? Dürfte der> Compiler den Ausdruck dann umstellen? Das Resultat wäre vermutlich mit> einem anderen Rundungsfehler*, wäre das dem Compiler erlaubt?
Gute Frage. Vereinfacht:
1
floatfoo(floatarg)
2
{
3
returnarg/10.0f*10.0f;
4
}
Darf die Division und die Multiplikation entfernt werden oder nicht? Je
nach Wert von arg kann das Ergebnis abweichen.
> *) Sorry, ich weiß nicht wie man die Fehler durch Beschränkungen des> float/double Formates richtig nennt.
Ich kenne es auch als Rundungsfehler.
Fpgakuechle K. schrieb:>> Ich weiß zum Beispiel mit Sicherheit, dass x/4 automatisch auf eine>> Shift Operation umgebaut wird, wenn x eine Integer Variable ist.>> Das ist aber nur die halbe Wahrheit, wenn man die Vorzeichendarstellung> betrachtet. Denn dann muß bei Zahlen mit Vorzeichen, eine '1' geschiftet> werden, während bei denen ohne eine '0' eingeshiftet wird.
Dafür haben die mir bekannten Prozessorarchitekturen alle einen so
genannten arithmetischen Rechtsshift, der genau das berücksichtigt. Beim
AVR heißt der z.B. ASR. Statt von links immer 0-Bits einzufügen, kopiert
der das MSB und erhält damit das Vorzeichen.
> Und dann wäre auch noch zu klären, wie sich die Flags verhalten, eine> Division könnte mehr oder anders Flags (Zero, Sign, Carries) beeinflußen> als eine Shiftoperation (keine Flag-Änderung).
Ja, je nach CPU-Architektur muss das ggf. berücksichtigt werden.
> Und wenn wenn Operationen 'Automatisch' geschieht, würde ich diesen> Compiler für den Produktivbetrieb sperren.
Warum? Compiler führen allerhand krasses Zeug zur Optimierung durch.
Solange das Ergebnis eines korrekten Programms sich dadurch nicht
ändert, ist das auch kein Problem.
> Schön wenn der Compiler ein paar Optimierungsmöglichkeiten findet, die> sollte man aber m.E. in der nächsten Softwarerevision in den Code> händisch einpflegen und dokumentieren.
Die Grundidee ist eigentlich heutzutage, dass man seinen Code primär so
schreibt, dass er gut lesbar ist und solche Detailoptimierungen eben dem
Compiler überlässt, der das sowieso besser kann. Nicht selten macht das,
was der Programmierer für eine Optimierung hält, den Code am Ende auf
einer bestimmten Plattform sogar langsamer oder hat andere Auswirkungen,
an die der nicht gedacht hat. Und es macht den Code komplizierter und
damit unübersichtlicher.
> Nochmals, das TO-Problem ist weniger ein 'C' als ein Numerik-Problem.
Ja, wobei sich aus der Art, wie der C-Code geschrieben ist, bestimmte
potenzielle numerische Probleme erst ergeben. LiftParamR1Vol/10 ist eben
ein Integer, weil das in C so festgelegt ist. Wenn man das nicht
berücksichtigt, kommt ggf. nicht das erwartete Ergebnis raus.
> Es ist halt ein Problem, das es in der Schulmathematik keine Shifts gibt> sondern eben nur Multiplikation / Addition
An sich gibt's schon Shifts. Um mit 10 zu mulitiplizieren, muss ich nur
eine 0 anhängen bzw. das Komma um eine Stelle verschieben. Keiner fängt
da an, irgendwas zu berechnen.
> und Informatiker dazu neigen ihre algos wie schulmathematiker zu notieren> und nicht wie 'Numeriker', die eben mehr Operationen kennen als die vier> Grundrechenarten.
Das ist doch Unsinn. Gerade Informatiker haben früher "traditionell"
immer versucht, Multiplikationen und Divisionen durch Shifts zu
ersetzen. Heute sieht man davon ab, weil es die eigentliche Intention
des Code schlechter darstellt. Außerdem muss ein Shift nicht unbedingt
schneller sein als eine Mulitiplikation, wenn man da mal an einen Atmega
mit MUL-Instruktion aber ohne Barrel-Shifter denkt.
Also überlässt man solche Mikrooptimierungen lieber dem Compiler.
Fpgakuechle K. schrieb:> Jörg W. schrieb:>> wobei nicht ganz klar ist, was die>> Dividiererei und Multiplizierere mit den Zehnen soll.>> vieleicht Runterskalieren von Eingangsgrößen um int-Übelaufe zu> vermeiden?
Wenn danach aber sowieso mit Gleitkomma weiter gerechnet wird, ist das
Humbug.
Jörg W. schrieb:> Wenn danach aber sowieso mit Gleitkomma weiter gerechnet wird, ist das> Humbug.
Der TO hat in seinem Eröffnungsposting das Ergebnis mit seinem
Taschenrechner verglichen. Da ein Taschenrechner standardmäßig immer
alles mit Gleitkomma rechnet, gehe ich davon aus, dass der TO die Folgen
einer Integer-Division in C - nämlich grundsätzlich eine Abrundung -
überhaupt nicht kennt.
Daraus - und auch aufgrund der gehäuften Fehler in nur einer Zeile C -
schließe ich, dass der TO entweder ein absoluter C-Anfänger ist oder ein
Troll. Die Wahrscheinlichkeit für letzteres steigt mit jedem Tag, an dem
der TO sich nicht mehr meldet. Es kann aber auch sein, dass der TO wegen
der massiven Kritik bereits früh ausgestiegen ist und sich deshalb nicht
mehr meldet.
Ergo: Es hat überhaupt keinen Sinn mehr, über diese eine Zeile C weiter
zu sinnieren, weder syntaktisch, noch philosophisch, noch inhaltlich.
Das Ding ist kaputt, fertig. Es wurde auch schon alles nötige gesagt, um
das Ding neu zu machen.
Rolf M. schrieb:>> Fpgakuechle K. schrieb:> Dafür haben die mir bekannten Prozessorarchitekturen alle einen so> genannten arithmetischen Rechtsshift, der genau das berücksichtigt. Beim> AVR heißt der z.B. ASR. Statt von links immer 0-Bits einzufügen, kopiert> der das MSB und erhält damit das Vorzeichen.
Naja, da kennst Du beispielsweise keine FPGA-Implementierung eines
Algos, bei dem sich der FPGA-Designer selbst den Kopf um die
Vorzeichenwahrung rep. nachgenerierung machen muß ;-)
>> Und wenn wenn Operationen 'Automatisch' geschieht, würde ich diesen>> Compiler für den Produktivbetrieb sperren.>> Warum? Compiler führen allerhand krasses Zeug zur Optimierung durch.> Solange das Ergebnis eines korrekten Programms sich dadurch nicht> ändert, ist das auch kein Problem.
Naja das hängt wohl erheblich vom Bildungsniveau ab, was man als 'Krass'
oder kalten Kaffee bezeichnet. Eigentlich sind alle Optimierungen, die
ein Compiler so macht Niveau Gymnasium oder drunter. Manche sind
lediglich wegen des zugrundeliegenden 'kompletten Ausprobierens' etwas
zeitintensiv (bspw. Registerallocation), die kann man gerne automatisch
machen. Das Problem bei den Optimierungen ist, das man sich nicht 100
darauf verlassen kann, das sie tatsächlich aktiviert werden (siehe die
Klammern) oben. Deshalb wird beispielsweise im Sicherheitskritischen
Bereich (Medizintechnik; Luftfahrt) oft verlangt, das
Compiler-Optimierung deaktiviert ist:
Beitrag "Wann Compiler-Optimierung in Embedded ausschalten"Beitrag "Muss bei Misra Compiler Optimierung deaktiviert werden?"Beitrag "Wie man volatile optimiert">> Schön wenn der Compiler ein paar Optimierungsmöglichkeiten findet, die>> sollte man aber m.E. in der nächsten Softwarerevision in den Code>> händisch einpflegen und dokumentieren.>> Die Grundidee ist eigentlich heutzutage, dass man seinen Code primär so> schreibt, dass er gut lesbar ist und solche Detailoptimierungen eben dem> Compiler überlässt, der das sowieso besser kann.
Gut lesbar ist der Code, bei dem man genau sieht, wann was das
Rechenwerk mit welchen Seiteneffekten macht. Und die (Akademische)
Grundidee heute ist, das man den Code vollständig spezifiziert und durch
Tests verifiziert, das man sich um den Code dazwischen nicht kümmern muß
und ihn ggf. auch von einen Horde Lemuren in Borneo schreiben lassen
kann. Aber das nur als Nebenbemerkung.
Und eigentlich ist doch der gesamte thread ein Beispiel, das wie eine
Formel lesbarer Code suboptimal übersetzt wird, weil man der
Compiler-optimierung (C wählt automatisch den besteb Datentyp) blind
vertraut.
Wie kann man da noch ernsthaft behaupten, es genüge den Code schön
hinzuschreiben und der Compiler tut wie ein gedankenlesender
Hofalchemist den Rest bei der wundersamen Transformation von tauben Erz
in edles Gold?!
Moin,
Fpgakuechle K. schrieb:> Wie kann man da noch ernsthaft behaupten, es genüge den Code schön> hinzuschreiben und der Compiler tut wie ein gedankenlesender> Hofalchemist den Rest bei der wundersamen Transformation von tauben Erz> in edles Gold?!
Ja,aeh - Nein, ein C compiler kann das natuerlich nicht. Aber ein Rust
Compiler, deeer kann das (also zumindest, wenn er die richtige
Versionsnummer hat...)
Duck&wech
WK
Stefan ⛄ F. schrieb:> Was wäre, wenn LiftParamR1Vol ein float oder double wäre? Dürfte der> Compiler den Ausdruck dann umstellen? Das Resultat wäre vermutlich mit> einem anderen Rundungsfehler*, wäre das dem Compiler erlaubt?
Für die meisten Anwendunen ist es ok wenn double + und * assoziativ
sind, und GCC kennt dafür -fassociative-math.
Und meist ist auch -ffast-math in Ordnung, was einen ganzen Zoo von
Optionen setzt wie -fassociative-math, -ffinite-math, -fno-signed-zeros
etc. Leider hat das keinen Einfluss auf Lib-Funktionen weil es keine
Multilib-Optionen sind.
Fpgakuechle K. schrieb:>>> Und wenn wenn Operationen 'Automatisch' geschieht, würde ich diesen>>> Compiler für den Produktivbetrieb sperren.>>>> Warum? Compiler führen allerhand krasses Zeug zur Optimierung durch.>> Solange das Ergebnis eines korrekten Programms sich dadurch nicht>> ändert, ist das auch kein Problem.>> Naja das hängt wohl erheblich vom Bildungsniveau ab, was man als 'Krass'> oder kalten Kaffee bezeichnet.
Manche Optimierungen sind durchaus nicht trivial. Aber was ich damit nur
ausdrücken wollte, ist, dass da deutlich mehr gemacht wird als man
gemeinhin erwartet.
> Das Problem bei den Optimierungen ist, das man sich nicht 100> darauf verlassen kann, das sie tatsächlich aktiviert werden (siehe die> Klammern) oben.
Natürlich nicht.
>> Die Grundidee ist eigentlich heutzutage, dass man seinen Code primär so>> schreibt, dass er gut lesbar ist und solche Detailoptimierungen eben dem>> Compiler überlässt, der das sowieso besser kann.>> Gut lesbar ist der Code, bei dem man genau sieht, wann was das> Rechenwerk mit welchen Seiteneffekten macht.
Bei Assembler mag das zutreffen. Aber Hochsprachen abstrahieren das ganz
bewusst. Dort gilt Code als gut lesbar, der beschreibt, was die
Intention ist, nicht wie es nachher in der Maschine umgesetzt wird. Das
ist ja der ganze Sinn der Abstraktion. Genauso, wie es mich nicht im
Detail interessiert, wie die Mechanik-Teile und Steuergeräte in meinem
Auto genau interagieren, damit der Motor tut, was er soll. Ich trete
einfach das Gaspedal, und es beschleunigt.
> Und die (Akademische) Grundidee heute ist, das man den Code vollständig> spezifiziert und durch Tests verifiziert, das man sich um den Code> dazwischen nicht kümmern muß und ihn ggf. auch von einen Horde Lemuren in> Borneo schreiben lassen kann. Aber das nur als Nebenbemerkung.
Das funktioniert so allerdings nur, wenn man den Code nachher nicht mehr
anfassen muss.
Ob es jetzt der aktuellen akademischen Idee widerspricht, weiß ich
nicht, aber in der täglichen Praxis ist die Grundidee eher, dass Code
primär für den Menschen geschrieben wird, weil er in der Regel öfter von
Menschen gelesen als geschrieben wird. Und um das für den Computer zu
optimieren, hab ich den Compiler.
> Und eigentlich ist doch der gesamte thread ein Beispiel, das wie eine> Formel lesbarer Code suboptimal übersetzt wird, weil man der> Compiler-optimierung (C wählt automatisch den besteb Datentyp) blind> vertraut.
Nein, das ist höchstens ein falsches Sprachverständnis, denn wie schon
beschrieben wurde, darf der Compiler das gar nicht optimieren.
> Wie kann man da noch ernsthaft behaupten, es genüge den Code schön> hinzuschreiben und der Compiler tut wie ein gedankenlesender> Hofalchemist den Rest bei der wundersamen Transformation von tauben Erz> in edles Gold?!
Sowas behauptet keiner. Vielmehr schreibt man hin, was man machen will,
und der Compiler hat die Aufgabe, dann einen effizienten Weg zu finden,
das umzusetzen. Und wenn ich x * 10 ausrechnen will, schreibe ich x * 10
hin und nicht (x << 3) + (x << 1), nur weil ich mir erhoffe, dass das
auf meiner Zielarchitektur drei Taktzyklen spart. Wenn dem so ist, darf
das der Compiler gerne so umsetzen.
Fpgakuechle K. schrieb:> Deshalb wird beispielsweise im Sicherheitskritischen Bereich> (Medizintechnik; Luftfahrt) oft verlangt, das Compiler-Optimierung> deaktiviert ist
Die Behauptung in diesen Threads scheinen aber eher "Hörensagen" denn
Tatsache zu sein.
Ich kann mich übrigens auch nicht dran erinnern, dass zumindest in MISRA
irgendwas derartiges stehen würde. Gerade mit aktuellen
MCU-Architekturen wäre es völlig unsinnig, auf Compileroptimierungen
verzichten zu wollen. Das mag bei Intel-Hardware, die sowieso versucht,
den Compiler gleich mal noch überholen zu wollen (mit entsprechendem
Energieaufwand) anders gewesen sein, aber wenn ich bei einem ARM die
Optimierung weglasse, rennt das Teil nur mit einem Bruchteil der
möglichen Geschwindigkeit.
> Das Problem bei den Optimierungen ist, das man sich nicht 100 darauf> verlassen kann, das sie tatsächlich aktiviert werden (siehe die> Klammern) oben.
Du kannst dich nicht drauf verlassen, dass der Compiler irgendwas in der
von dir hingeschriebenen Reihenfolge oder Anordnung rechnen lässt. Es
gilt die "as if"-Regel: das Ergebnis darf sich nur nicht von dem
unterscheiden, welches herauskommt, wenn die abstrakte Maschine es genau
so wie geschrieben abarbeitet.
"Premature optimization is the root of all evil." (Donald Knuth)
Man sollte die Dinge so hinschreiben, wie sie ein Mensch am besten lesen
und erfassen kann, nicht so, wie man denkt, dass sie bestmöglich
"voroptimiert" sind.
Wenn es irgendwo wirklich drauf ankommt (innere Schleife, die sehr
häufig durchlaufen wird), dann kann man allemal noch versuchen, mit ein
paar derartiger Mikrooptimierungen für eine bestimmte Compilerversion
ein wenig gut zu machen – auf Kosten der Arbeit, dass man es für einen
anderen Compiler oder die nächste Version ggf. neu evaluieren muss.
Wenn man alle Optimierungen abschaltet, ist man natürlich fein raus:
dann kommt es ja ganz offensichtlich auf jegliche Art von Performance
schlicht gar nicht an. ;-)
Jörg W. schrieb:> Fpgakuechle K. schrieb:>>> Deshalb wird beispielsweise im Sicherheitskritischen Bereich>> (Medizintechnik; Luftfahrt) oft verlangt, das Compiler-Optimierung>> deaktiviert ist>> Die Behauptung in diesen Threads scheinen aber eher "Hörensagen" denn> Tatsache zu sein.
Das nehme ich auch an. Meines Erachtens erreicht man durch Abschalten
der Optimierung das Gegenteil von dem, was man möchte.
Das Abschalten der Optimierung
- unterdrückt oft Warnungen, weil der Compiler Probleme erst bei höheren
Optimierungsstufen erkennt.
- verschleiert oft Programmierfehler, weil sie in der "dummen" Version
nicht auftreten, weil das Programm zum Beispiel ein ganz anderes
Zeitverhalten zeigt oder die Datenstruktur im RAM anders anordnet.
Erst wenn man die Optimierung einschaltet, spuckt der Compiler plötzlich
Warnungen aus, an die man im Traum nicht gedacht hat. Zudem offenbaren
sich plötzlich Programmierfehler wie Race Conditions, Buffer-Overflows
oder - ganz trivial - fehlende volatile-Deklarationen von in ISRs
benutzten globalen Variablen.
Von daher sehe ich das so: Ein "gutes Programm" ist eines, welches von
der Optimierungsstufe, mit der es übersetzt wurde, komplett unabhängig
ist. Man sollte daher, wenn man sein Programm testet, dieses immer mit
einer Optimierung (z.B. -Os oder -O2) übersetzen.
Frank M. schrieb:> dass der TO entweder ein absoluter C-Anfänger ist oder ein> Troll. Die Wahrscheinlichkeit für letzteres steigt mit jedem Tag, an dem> der TO sich nicht mehr meldet.
und weil zu keiner Zeit auf ',' vs '.' eingegangen wird!
Frank M. schrieb:> Von daher sehe ich das so: Ein "gutes Programm" ist eines, welches von> der Optimierungsstufe, mit der es übersetzt wurde, komplett unabhängig> ist.
Ich würde "gutes" hier durch "korrektes" ersetzen. Denn wenn ein
Programm nach dem Einschalten irgendwelcher Optimierungen nicht mehr
funktioniert, liegt das
nahezu immer daran, dass es einen Fehler enthält.
Jörg W. schrieb:> Fpgakuechle K. schrieb:>> Deshalb wird beispielsweise im Sicherheitskritischen Bereich>> (Medizintechnik; Luftfahrt) oft verlangt, das Compiler-Optimierung>> deaktiviert ist.>>
Das ist falsch.
Aber es werden erprobte oder zugelassene Versionen der Compiler
verlangt.
Naturgemäß können das keine ganz neuen sein.
Ggf. muß auch die Optimierungsstufe erprobt sein, oder weitere
Optionen.
>>> Die Behauptung in diesen Threads scheinen aber eher "Hörensagen" denn> Tatsache zu sein.>
Das ist grossteils richtig.
>>> Ich kann mich übrigens auch nicht dran erinnern, dass zumindest in MISRA> irgendwas derartiges stehen würde.>
Lt. MISRA ist die Verwendung von float nicht zulässig.
Über der Wertebereich aller Variablen und möglichen Ergebnisse von
Berechnungen hat man sich vorher mehr Gedanken zu machen.
Insbesondere was Min und Max Werte sowie Über- oder Unterläufe betrifft.
>
Gruss
> Lt. MISRA ist die Verwendung von float nicht zulässig.
Quelle?
Den alten MISRA-C:2004 findet man im Netz, der war viel strenger als der
aktuelle MISRA-C:2021 (z.b. war goto verboten), aber selbst der sagt
nicht s gegen float im Allgemeinen (verbietet aber float als
Schleifenzähler für for - genauso wie double und long double).
Philipp Klaus K. schrieb:Erich schrieb:>> Lt. MISRA ist die Verwendung von float nicht zulässig.> Quelle?
Ok, vielleicht war das dann "im Prinzip" erlaubt, aber mit solch hohen
Einschränkungen wie...
"Use of floating-point arithmetic shall be documented."
"conforms to IEEE 754"
"preserve precision when converting integral values to floating-point
type"
"do not use object representations to compare floating-point values"
Damit wäre der Review-Aufwand quasi jeweils höher gewesen als der Nutzen
in damaligem Projekt, sodaß wir diese Vereinbarung selbst uns auferlegt
haben (keine float im Projekt zu verwenden).
Erich schrieb:> Philipp Klaus K. schrieb:> Erich schrieb:>>> Lt. MISRA ist die Verwendung von float nicht zulässig.>>> Quelle?>> Ok, vielleicht war das dann "im Prinzip" erlaubt, aber mit solch hohen> Einschränkungen wie...
Hohe Einschränkungen sind das, soweit ich sehe, nicht. Aber ich habe
nicht wirklich MISRA-C-Erfahrung.
> "Use of floating-point arithmetic shall be documented."
Aber wohl nicht jeder Einzelfall, sondern nur, dass das Programm
insgeasmt es nutzt?
> "conforms to IEEE 754"
Das erfüllen die meisten Compiler (es gibt ja auch ein Marko im
C-Standard, dass dies anzeigt). Der SDCC ist raus, und bei GCC darf man
kein -ffast-math verwenden, aber sonst ist das keine echte einschränlung
> "preserve precision when converting integral values to floating-point> type"
Damit fällt der Cast von int32_t nach float weg (aber nicht der von
int16_t nach float und auch nicht der von int32_t nach double).
> "do not use object representations to compare floating-point values"
Warum sollte man das in einem normalen Programm jemals machen wollen? C
hat == (was im Kontext von float auch nicht ganz unproblematisch ist,
aber trotzdem viel besser als memcmp).
Joachim B. schrieb:> welcher C-Compiler benutzt ',' für floats!
Guten Morgen! Wo warst du denn den Rest des Threads lang? Bereits das
Lesen des zweiten Beitrags hätte dich erleuchten können, dass das wohl
so ziemlich jedem Mitleser hier aufgefallen ist, allerdings interessiert
sich der TE gar nicht mehr dafür (oder traut sich nicht mehr raus).
Davon abgesehen, zu Beginn der ganzen i18n-Geschichte hat es IBM auf AIX
wohl tatsächlich mal geschafft, dass der C-Compiler in deutscher
Umgebung Kommas statt Punkte haben wollte. :-o
Philipp Klaus K. schrieb:> C hat == (was im Kontext von float auch nicht ganz unproblematisch ist
Daher ist der Vergleich von Gleitkommazahlen auf (Un-)Gleichheit wohl
tatsächlich in MISRA nicht statthaft.
Wobei ich persönlich dafür bin, dass man von dieser Regel abweichen
dürfen sollte, wenn ein Vergleich auf 0.0 erfolgt und diese 0.0 das
Ergebnis einer expliziten Zuweisung oder (auch impliziten)
Initialisierung ist. Die 0.0 ist immer eindeutig darstellbar und daher
auch exakt vergleichbar.
Philipp Klaus K. schrieb:> Aber wohl nicht jeder Einzelfall, sondern nur, dass das Programm> insgeasmt es nutzt?
Also wenn ich float (oder double) irgendwo bei der Berechnung von
Geldbeträgen benutzen würde, müsste ich ganz sicher jeden Fall einzeln
detailliert dokumentieren, samt Analyse der möglichen negativen Folgen.
Stefan ⛄ F. schrieb:> Also wenn ich float (oder double) irgendwo bei der Berechnung von> Geldbeträgen benutzen würde, müsste ich ganz sicher jeden Fall einzeln> detailliert dokumentieren
Oh, dein Konto hat einen Füllstand, der 14 signifikante Stellen
übersteigt?
Aber ich glaube, dann käme es auf die letzten falsch gerundeten Cents
auch nicht mehr an. :-)
Jörg W. schrieb:> Die 0.0 ist immer eindeutig darstellbar und daher> auch exakt vergleichbar.
Das stimmt nicht ganz.
Es gibt die +0.0 UND die -0.0
https://de.wikipedia.org/wiki/IEEE_754#Null
Also wären jeweils zwei Vergleiche nötig, oder Vergleich ohne das sign.
Ist das im jeweiligen Compiler / Library so umgesetzt? Wirklich?
Erich schrieb:> Es gibt die +0.0 UND die -0.0
Ich schrieb: wenn die 0.0 durch explizite Zuweisung oder (auch
implizite) Initialisierung entstanden ist, dann ist das sicher (sie hat
dann das Bitmuster, bei dem alle Bits 0 sind). Das schließt den Fall,
dass da jemand -0.0 zugewiesen hat, folglich aus. Davon abgesehen, habe
ich noch keinen gesehen, der sowas geschrieben hätte. Damit ergibt sich
der Fall -0.0 also lediglich als Ergebnis einer Berechnung, und bei dem
würde ich mich sowieso nie drauf verlassen wollen, dass exakt 0
rauskommt, da gebe ich MISRA völlig Recht.
Übrigens, per definitionem gilt, dass +0.0 == -0.0 ist. Das setzen die
Compiler auch so um.
Jörg W. schrieb:> Oh, dein Konto hat einen Füllstand, der 14 signifikante Stellen> übersteigt?
Meins nicht, aber die Kunden für die ich entwickle legen Wert darauf.
Wenn das Finanzamt nur einen Cent Rundungsfehler in der Jahresbilanz
entdeckt, werden die Leute manchmal sehr nervig.
Stefan ⛄ F. schrieb:> Meins nicht, aber die Kunden für die ich entwickle legen Wert darauf.
Billiardäre? ;-)
OK, allerdings dürfte das auch kein Fall für MISRA sein, und für sowas
gibt's eh "bignum"-Bibliotheken.
Jörg W. schrieb:> Wobei ich persönlich dafür bin, dass man von dieser Regel abweichen> dürfen sollte, wenn ein Vergleich auf 0.0 erfolgt und diese 0.0 das> Ergebnis einer expliziten Zuweisung oder (auch impliziten)> Initialisierung ist. Die 0.0 ist immer eindeutig darstellbar und daher> auch exakt vergleichbar.
Gilt das nicht an sich für jeden Wert, den man direkt (ohne Berechnung)
hinein geschrieben hat?
Rolf M. schrieb:> Gilt das nicht an sich für jeden Wert, den man direkt (ohne Berechnung)> hinein geschrieben hat?
Bin ich mir nicht so völlig sicher, während ich mir beim Bitmuster 0
sicher bin. :)
Für andere Werte ist es aber auch weniger wichtig. Wenn ich weiß, dass
irgendein Koeffizient beim ersten Programmlauf berechnet wird und dabei
auf jeden Fall etwas anderes als 0.0 bekommt, dann kann ich eben diesen
(durch die Initialisierung globaler Variablen erzeugten) Wert 0.0 als
Vergleich für "muss noch berechnet werden" benutzen, statt mir noch ein
Flag extra mitschleifen zu müssen "foo_has_been_initialized".
Jörg W. schrieb:> Guten Morgen! Wo warst du denn den Rest des Threads lang?
ich habe den ganzen Thread mehrfach gelesen, was willst du mir
mitteilen?
ging es nicht um einen 32-bit AVR flash microcontroller und C?
Ist in C nicht das ',' ein Datentrenner?
Jörg W. schrieb:> allerdings interessiert> sich der TE gar nicht mehr dafür
eben das wollte ich nur noch mal klarstellen, denn es wird immer noch
palavert!
eigentlich könnte ein Schloß an den Thread!
Joachim B. schrieb:> was willst du mir mitteilen?> ging es nicht um einen 32-bit AVR flash microcontroller und C?> Ist in C nicht das ',' ein Datentrenner?
Ja. Wie in der ersten Antwort hingewiesen:
Hi schrieb:> "." statt "," wäre schon mal ein guter Anfang ;)
Jörg W. schrieb:> dann kann ich eben diesen> (durch die Initialisierung globaler Variablen erzeugten) Wert 0.0 als> Vergleich für "muss noch berechnet werden" benutzen, statt mir noch ein> Flag extra mitschleifen zu müssen "foo_has_been_initialized".
Ja, könnte man machen. Aber wozu? Wichtig ist, dass jeder MISRA-Nutzer
das Problem kennt und versteht. Das würde durch eine Erweiterung "aber
mit 0.0 ist das dann unter den und den Bedingungen erlaubt" nicht
einfacher.
Wenn Du dann eine plausible Anwendung für 0.0 hast und sicherstellen
kannst, dass dies zu keinen Problemen führt, dann kannst Du es ja mit
kurzem Kommentar nutzen. Solange Du weißt, was Du tust.
A. S. schrieb:> Wenn Du dann eine plausible Anwendung für 0.0 hast und sicherstellen> kannst, dass dies zu keinen Problemen führt, dann kannst Du es ja mit> kurzem Kommentar nutzen. Solange Du weißt, was Du tust.
Führt dann dazu, dass irgendwann die Hälfte aller Kommentare nicht mehr
den eigentlichen Codefluss kommentiert, sondern die diversen
MISRA-Ausnahmen.
Joachim B. schrieb:> eben das wollte ich nur noch mal klarstellen, denn es wird immer noch> palavert!
Ja, aber nur du musstest nach 3 Tagen nochmal Dinge kommentieren, die
bereits ein paar Minuten nach dem Posting klar waren. Der Rest der
Diskussion dreht sich um andere Dinge (die keineswegs uninteressant
sind), und eben daher gehört da kein Schloss davor (solange die
Diskussion gesittet bleibt, aber da sehe ich gerade gar keine Gefahr).
Jörg W. schrieb:> Fpgakuechle K. schrieb:>>> Deshalb wird beispielsweise im Sicherheitskritischen Bereich>> (Medizintechnik; Luftfahrt) oft verlangt, das Compiler-Optimierung>> deaktiviert ist>> Die Behauptung in diesen Threads scheinen aber eher "Hörensagen" denn> Tatsache zu sein
Ack. Wir mussten gerade tagelang Fehler suchen, die sich zeigten, als
darauf gedrängt wurde, jede nur mögliche Optimierung zu aktivieren, die
Laufzeit spart. War eigenlich wegen der Laufzeit nicht notwendig. Es ist
für eine Payload eines Satelliten. Aber man lernt dabei jede Menge, auch
die Leute, die den Assermbler nicht beherrschten, haben ihn dann
kennengelernt ;)
900ss D. schrieb:> Wir mussten gerade tagelang Fehler suchen, die sich zeigten, als darauf> gedrängt wurde, jede nur mögliche Optimierung zu aktivieren, die> Laufzeit spart.
Worauf habt ihr denn davor optimiert?
"Jegliche mögliche Optimierung bezüglich Laufzeit" klingt ja nach sowas
wie -O3, wo massiv Code aufgebläht werden darf, wenn es nur Laufzeit
spart (loop unrolling etc.). Ansonsten nimmt man bei MCUs ja eher -Os:
"optimiere alles, was nicht mehr Code produziert" (wobei geringere
Codegröße ja typisch auch erstmal Laufzeit spart).
Ansonsten: wer mit -O0 entwickelt, muss sich nicht wundern, wenn dann
beim Einschalten der Optimierung plötzlich alle möglichen Warnungen oder
Fehler auftauchen, die zwar schon immer in der Software waren, aber
vorher noch keiner bemerkt hatte. Wurde im Thread schonmal geschrieben,
aber dieses "ich compilier erstmal mit -O0, damit ich schön im Debugger
durch steppen kann" ist leider so verbreitet, dass man das ruhig nochmal
erwähnen sollte. Wer mit -O0 entwickelt, debuggt zweimal.
Aber ja, bei einem Controller sollte man den Assemblercode zumindest
rudimentär zu lesen in der Lage sein. Insofern haben eure Entwickler ja
was Nützliches dabei gelernt. ;-)
Jörg W. schrieb:> Billiardäre? ;-)
Keine Ahnung wie hoch die Umsätze von Vodafone und der Allianz
Versicherung sind. Jedenfalls hoch genug, dass sie solche Anforderungen
stellen können.
> für sowas gibt's eh "bignum"-Bibliotheken.
Genau. Schade dass sie nicht zum Standardumfang von C gehören, das wäre
manchmal praktisch. Bisher habe ich auf µC immer versucht, sie zu
umgehen bzw. weg zu argumentieren. Aber bei Business-Server Anwendungen
kann ich das nicht machen.
Rolf M. schrieb:> Gilt das nicht an sich für jeden Wert, den man direkt (ohne Berechnung)> hinein geschrieben hat?
nur bei normalisierten float, unnormalisierte sind nicht eineindeutig.
80 beispielsweise wäre normalisiert 0.8E02, auch wenn man in den C-Code
80 schreibt. Die Frage ist ob es sowas wie 'ohne berechnung
reinschreiben' überhaupt gibt, wenn ein Compiler zwischen dem User und
der Maschine steht, dessen Umwandlungen weder vom User beherrscht noch
überprüft werden.
Stefan ⛄ F. schrieb:> Jörg W. schrieb:>> Billiardäre? ;-)>> Keine Ahnung wie hoch die Umsätze von Vodafone und der Allianz> Versicherung sind. Jedenfalls hoch genug, dass sie solche Anforderungen> stellen können.>>> für sowas gibt's eh "bignum"-Bibliotheken.>> Genau. Schade dass sie nicht zum Standardumfang von C gehören, das wäre> manchmal praktisch. Bisher habe ich auf µC immer versucht, sie zu> umgehen bzw. weg zu argumentieren. Aber bei Business-Server Anwendungen> kann ich das nicht machen.
Ich hätte jetzt eher geschätzt, dass im Finanzwesen dezimale
Gleitkommazahlen (nach ISO/IEC TS 18661-2:2015 bzw. ISO C23) verwendet
werden.
Ein bignum (im Sinne von zur Laufzeit veränderlicher Größe) wird es wohl
in C23 nicht geben, aber dafür Ganzzahltypen mit vom Programmierer zur
Compilezeit wählbarer Größe.
Philipp Klaus K. schrieb:> Ich hätte jetzt eher geschätzt, dass im Finanzwesen dezimale> Gleitkommazahlen (nach ISO/IEC TS 18661-2:2015 bzw. ISO C23) verwendet> werden.> Ein bignum (im Sinne von zur Laufzeit veränderlicher Größe) wird es> wohl in C23 nicht geben
Jetzt hast du mich abgehängt, ich weiß nicht mehr worum es geht.
In MariaDB Datenbanken benutzen wir für Geldbeträge meistens den
Datentyp decimal(10,2). In Java dementsprechend BigDecimal, in Go
Money*.
Money ist eine Struktur aus:
string currency_code
int64 units // The whole units of the amount.
int32 nanos // Number of nano (10^-9) units of the amount.
Stefan ⛄ F. schrieb:> Philipp Klaus K. schrieb:>> Ich hätte jetzt eher geschätzt, dass im Finanzwesen dezimale>> Gleitkommazahlen (nach ISO/IEC TS 18661-2:2015 bzw. ISO C23) verwendet>> werden.>> Ein bignum (im Sinne von zur Laufzeit veränderlicher Größe) wird es>> wohl in C23 nicht geben>> Jetzt hast du mich abgehängt, ich weiß nicht mehr worum es geht.
Die Datentypen _Decimal32, _Decimal64, und _Decimal128. Diese
implementieren dezimale Gleitkommazahlen (während float, double und long
double üblicherweise binäre Gleitkommazahlen implementieren).
Bei dezimalen Gleitkommazahlne ist üblicherweise der Exponent eine
Binärzahl, die Mantisse aber eine Dezimalzahl. Für die Mantisse wird oft
DPD verwendet, da es etwas speicherplatzeffizienter als BCD ist (10 Bits
je 3 Dezimalstellen vs. 4 Bits je Dezimalstelle).
Wobei wir hier langsam off-topic werden, da ich vermute, dass diese
Formate auf µC kaum eingesetzt werden (z.B. SDCC implementiert sie
nicht).
Unter bignum dagegen verstand ich einen Typ, der beliebig große Zahlen
speichern kann, wobei dann unter Umständen dynamisch Speicher allokiert
werden muss.
Philipp Klaus K. schrieb:> Wobei wir hier langsam off-topic werden, da ich vermute, dass diese> Formate auf µC kaum eingesetzt werden (z.B. SDCC implementiert sie> nicht).
Ich glaube auch nicht, dass jemand mit Billionenbeträgen auf einer MCU
jongliert. ;-)
Jörg W. schrieb:> Ich glaube auch nicht, dass jemand mit Billionenbeträgen auf einer MCU> jongliert. ;-)
ich glaube aber das es genügend User gibt die billionen von µs auf einer
MCU handeln wollen als Wartezeit!
Joachim B. schrieb:> ich glaube aber das es genügend User gibt die billionen von µs auf einer> MCU handeln wollen als Wartezeit!
10^6 s? Das sind knapp 2 Wochen, und ob's dabei auf die letzte µs
ankommt?
Jörg W. schrieb:> Joachim B. schrieb:>> ich glaube aber das es genügend User gibt die billionen von µs auf einer>> MCU handeln wollen als Wartezeit!>> 10^6 s? Das sind knapp 2 Wochen, und ob's dabei auf die letzte µs> ankommt?
Wenn man z.B. einen Zähler hat, der mit einem 1 kHz-Interrupt
hochgezählt wird, braucht man die letzte ms, damit überhaupt immer
weitergezählt wird.
Daher (und weil es effizienter ist) würde ich eher einen unsigned long
long nehmen, statt einer Gleitkommazahl.
Fpgakuechle K. schrieb:> Rolf M. schrieb:>>> Gilt das nicht an sich für jeden Wert, den man direkt (ohne Berechnung)>> hinein geschrieben hat?>> nur bei normalisierten float, unnormalisierte sind nicht eineindeutig.
Kann man das bei der Eingabe überhaupt irgendwie wählen?
> 80 beispielsweise wäre normalisiert 0.8E02, auch wenn man in den C-Code> 80 schreibt. Die Frage ist ob es sowas wie 'ohne berechnung> reinschreiben' überhaupt gibt, wenn ein Compiler zwischen dem User und> der Maschine steht, dessen Umwandlungen weder vom User beherrscht noch> überprüft werden.
Allerdings würde ich davon ausgehen, dass die selbe Umwandlung auch zum
selben Ergebnis führt. Also bei einem:
1
x=123.456;
und einem
1
if(x==123.456)
die Wandlung des eingegebenen Literals "123.456" in den Float-Typ in den
beiden Fällen nicht unterschiedliche Ergebnisse produziert. Und ich
würde davon ausgehen, dass das nicht nur für 123.456, sondern für jeden
Wert gilt. Die Frage ist eben nur, ob das garantiert ist.
Jörg W. schrieb:> Worauf habt ihr denn davor optimiert?
Auf -O0 :) Es war wirklich viel der Debugger nötig. Und den Code erst
auf dem PC zu testen war zu aufwendig da sehr viel HW Zugriffe vorhanden
sind. Ich hatte in meinem Teil nur eine fiese Stelle. Ein memcpy(x,y,4)
wurde durch eine 32-bit load Instruktion ersetzt. Peng! Die Adressen
waren nicht aligned. Nachdem der Pointer auf uint_8 zeigte anstatt auf
uint32_t hat er das memcpy nicht mehr ersetzt ;) Klar mein Fehler durch
falschen Pointertyp.
Jörg W. schrieb:> Ansonsten nimmt man bei MCUs ja eher -Os:
Es ist ein 32-Bit "Bolide" :)
Jörg W. schrieb:> Aber ja, bei einem Controller sollte man den Assemblercode zumindest> rudimentär zu lesen in der Lage sein. Insofern haben eure Entwickler ja> was Nützliches dabei gelernt. ;-)
Ich fürchte, dass sie das schon wieder vergessen haben. In der Regel
interessiert es sie nicht :(
900ss D. schrieb:> Jörg W. schrieb:>> Worauf habt ihr denn davor optimiert?>> Auf -O0 :)
Uff.
> Es war wirklich viel der Debugger nötig. Und den Code erst> auf dem PC zu testen war zu aufwendig da sehr viel HW Zugriffe vorhanden> sind.
Mein Tipp: lernen, wie man optimierten Code debuggt. Das geht. Als
Embedded Programmer muss man sich halt kleine Hilfen einfallen lassen.
Wenn man unbedingt an einer ganz bestimmten Stelle einen Breakpoint
setzen können will, tut's ein
1
__asmvolatile("nop":::);
Noch krasser wäre
1
__asmvolatile("nop":::"memory");
Damit zwingt man den Compiler, die gemäß der abstrakten Maschine an
dieser Stelle abzuarbeitenden Speicherzugriffe auch wirklich alle vorher
zu generieren, statt irgendwas darüber hinweg zu cachen.
Auf so einen NOP kann man halt immer einen Breakpoint werfen.
Zwischenvariablen zum Angucken im Debugger kann man sich temporär als
volatile reinnageln.
Wenn man eine ganze Serie von Zwischenwerten ablegen lassen will, um
sich im Debugger so eine Art "value history" ansehen zu können, tut's
ein einfacher Ringbuffer:
1
#define DD_CNT 32
2
uint8_tdebugdata[DD_CNT];
3
intdebugidx;
4
5
...
6
debugdata[debugidx]=my_interesting_value;
7
if(++debugidx==DD_CNT)debugidx=0;
8
...
Das Ganze als globale Variablen hinterlegt, wird es auch nicht
wegoptimiert (ok, zumindest nicht ohne -flto).
So kann man sich noch weitere Tricks bauen. Was wir auch stets in all
unseren MCU-Projekten hatten: ein paar GPIOs (4 sind nett, ansonsten so
viele, wie gerade entbehrlich sind), um irgendwelche Traces auf dem
Logikanalysator rauszuwerfen. Bei 4 Bit kannst du sogar ganze
Nachrichten in Form von Nibbels rauspusten ;-) und das, ohne nennenswert
an Ausführungsgeschwindigkeit einzubüßen.
> Jörg W. schrieb:>> Ansonsten nimmt man bei MCUs ja eher -Os:> Es ist ein 32-Bit "Bolide" :)
Hatte ich die letzten 6 Jahre lang auch. Trotzdem war -Os bei uns
bewährter Default. -g3 ist auch hilfreich, mittlerweile werden da sogar
Makro-Werte in den GDB weiter gereicht.
Jörg W. schrieb:> Mein Tipp: lernen, wie man optimierten Code debuggt
Die Hilfen die du beschreibst, kenne ich auch. Aber ich benutze lieber
-O0 anstatt mich immer mit workarounds rumzuquälen. Dass ich später bei
-O2 auf die Klappe fliege, hab ich relativ selten erlebt. Die oben
beschriebene Stelle war die einzige (in meinem Teil) in diesem Projekt.
Es ist auch eine sehr effiziente Logschnittstelle (Spacewire) vorhanden.
Die schafft ca. 300 Msg/ms. Ja, superschnell und die Daten mit einer Art
"Komprimierung" versehen. Das geht zu einem PC der das Log dann
schreibt. Inzwischen wird der Debugger nur noch sehr selten genutzt.
Jörg W. schrieb:> ein paar GPIOs
Hmmm.... ja. Ich sitze in Norddeutschland und das Target steht in
Süddeutschland. Da fehlt dafür die Videoübertragung ;)
Als Analyzer hab ich jahrelang auch einen PCI-Bus Analyzer genutzt. Da
hab ich Variablen, States u.s.w. einfach in ein PCI-Target geschrieben
wo es nicht störte und das passiert dann ja quasi in Echtzeit. Der
Analyzer hat brav alles aufgezeichnet bzw. konnte sogar triggern auf
gewünschte Zustände. Und man kann auch nach das Timing analysieren. Das
war sehr komfortabel. Die Kollegen haben sich immer gwundert, dass ich
beharrlich darauf bestand, die Analyzer auf meinem Tisch zu haben :)
Rolf M. schrieb:> ilt das nicht an sich für jeden Wert, den man direkt (ohne Berechnung)> hinein geschrieben hat?
Nein, schon allein weil ein Verlgeich auf nan immer false ergibt, also
nan != nan.
Rolf M. schrieb:> Und ich> würde davon ausgehen, dass das nicht nur für 123.456, sondern für jeden> Wert gilt. Die Frage ist eben nur, ob das garantiert ist.
Als "Nichthochsprachler" hat mich besonders dieses Ansinnen doch sehr
verwundert. Nach meinem bescheidenen Verständnis kann ich "x" doch jeden
beliebigen, zulässigen Wert (im Sinne von Wert ist möglich und im
Wertevorrat der Sprache vorhanden) zuweisen und dann auch prüfen, ob x
gleich diesem Wert ist, der Vergleich also ein "True" liefert. Sollte es
also Sprachen geben, wo das nicht so ist?? Oder hat der Rolf nur
"Paranoia"...
Gruß Rainer
Rainer V. schrieb:> Sollte es also Sprachen geben, wo das nicht so ist?
Das hat nichts mit Sprache zu tun, sondern mit Zahlendarstellung.
In C könnte man mittlerweile sogar exakte (garantierbare)
Gleitkommawerte festlegen, allerdings in einer menschlich nicht lesbaren
Form. ;-)
Rate beispielsweise mal, was hier als Zahl ausgegeben wird. :-)
1
#include<stdio.h>
2
3
int
4
main(void)
5
{
6
printf("%f\n",0X1.921FB4D12D84AP+1);
7
return0;
8
}
Aber: hier ist eben auch das exakte Bitmuster der Zahl garantiert.
Jörg W. schrieb:> Rate beispielsweise mal, was hier als Zahl ausgegeben wird. :-)Jörg W. schrieb:> 0X1.921FB4D12D84AP+1
Na raten ist ja Quatsch :-) Ich habe aber nach was anderem gefragt,
nachdem der Jörg eben Sorge hat, dass die Zuweisung und der Test auf
Zuweisung "False" liefern könnte! Für deine ominöse Zahl gilt also auf
jeden Fall:
x = 0X1.921FB4D12D84AP+1 und if (x == 0X1.921FB4D12D84AP+1) = "True".
Ich habe ja gerade vorausgesetzt, dass eine zulässige Zahlendarstellung
vorliegt. Also ist Rolfs Bedenken Blödsinn??
Gruß Rainer
Rainer V. schrieb:> Ich habe ja gerade vorausgesetzt, dass eine zulässige Zahlendarstellung> vorliegt.
Das kannst du aber für eine x-beliebige dezimale Gleitkommazahl nicht
garantieren, denn es kann sein, dass sie intern nicht exakt darstellbar
ist.
Aus dem Grunde hat man diese hexadezimale Darstellung zusätzlich ins
Spiel gebracht.
Es wird übrigens 3.141593 ausgegeben. ;-)
Jörg W. schrieb:> 3.141593
Ok, Pi wird also "irgendwie" dargestellt...aber wenn meine Zuweisung x =
a.bc zulässig ist (egal wie halt), dann liefert die if () immer True.
Ich habe mich - wie gesagt - über die Sorge von Rolf gewundert, dass das
irgendwo, irgenwie, irgendwann nicht so sein könnte! Also noch mal
genauer...die Sorge, dass x = a.bc beim Kompilieren "was Anderes" sein
könnte, als x == a.bc , ist das denkbar?! Ist es das oder verstehe ich
nur dummes Zeug?
Gruß Rainer
Walter T. schrieb:> Ok, Pi wird also "irgendwie" dargestellt...aber wenn meine Zuweisung x => a.bc zulässig ist (egal wie halt), dann liefert die if () immer True.> Ich habe mich - wie gesagt - über die Sorge von Rolf gewundert, dass das> irgendwo, irgenwie, irgendwann nicht so sein könnte!
Mich würde es sehr wundern, wenn es nicht so wäre, aber ich schreibe
meinen Code ungern basierend auf irgendwelchen Annahmen, von denen ich
nicht weiß, ob sie stimmen.
Letztendlich hatte Jörg ja geschrieben, dass das nur für 0.0 gelte, bzw.
er sich für andere Werte nicht sicher ist.
Walter T. schrieb:> Rolf M. schrieb:>> ilt das nicht an sich für jeden Wert, den man direkt (ohne Berechnung)>> hinein geschrieben hat?>> Nein, schon allein weil ein Verlgeich auf nan immer false ergibt, also> nan != nan.
nan ist natürlich ein Sonderfall, denn da ergibt der Vergleich aus einem
ganz anderen Grund false. Meine Frage war auch nicht, ob der Vergleich
immer true zurückliefert, sondern ob es bei zwei Vorkommen des selben
float-literals dazu kommen kann, dass die zu unterschiedlichen Werten im
Programm führen.
Rolf M. schrieb:> sondern ob es bei zwei Vorkommen des selben> float-literals dazu kommen kann, dass die zu unterschiedlichen Werten im> Programm führen.
Ok...habe das jetzt für mich mal dahingehend gelöst, dass beliebig
genaue Zahlen immer noch nicht gleich, sondern unterschiedlich sind. Da
muß man natürlich gnadenlos ein "Epsilon" einführen oder die ganze
Geschichte auf einen Datentyp bringen, der für den fraglichen Fall
vergleichbar ist. Sorgen hat man...
Gruß Rainer
Rolf M. schrieb:> Meine Frage war auch nicht, ob der Vergleich> immer true zurückliefert, sondern ob es bei zwei Vorkommen des selben> float-literals dazu kommen kann, dass die zu unterschiedlichen Werten im> Programm führen.
Im Grunde war das natürlich genau deine Frage..aber wahrscheinlich hast
du gar nicht verstanden, was du wirklich gefragt hast. Höchstenfalls
hast du was geahnt...sorry, wenn das eine harte Unterstellung sein
sollte, ist für mich aber genau so :-)
Rainer
Jörg W. schrieb:> 10^6 s? Das sind knapp 2 Wochen, und ob's dabei auf die letzte µs> ankommt?
ich sag ja nicht das es sinnvoll ist, aber zu oft wird unsinniges
gemacht oder gefordert!
Rolf M. schrieb:> sondern ob es bei zwei Vorkommen des selben> float-literals dazu kommen kann, dass die zu unterschiedlichen Werten im> Programm führen.
fängt ja schon damit an, dass die meisten dezimal-Werte in float nicht
repräsentiert werden können UND ein Vergleich in double erfolgt.
Also ähnlich wie int i = 28.1; auch später nicht auf Gleichheit geprüft
werden kann.
A. S. schrieb:> Rolf M. schrieb:>> sondern ob es bei zwei Vorkommen des selben>> float-literals dazu kommen kann, dass die zu unterschiedlichen Werten im>> Programm führen.>> fängt ja schon damit an, dass die meisten dezimal-Werte in float nicht> repräsentiert werden können UND ein Vergleich in double erfolgt.
Ja, die Konvertierung nach double könnte da dazwischenfunken. Allerdings
kann man mit dem Suffix F das Literal explizit als float angeben. Dann
wird der Vergleich zwar in double gemacht, aber wenn zwei floats mit dem
gleichen Wert nach double konvertiert werden, sollte die Eigenschaft der
Gleichheit ja bestehen bleiben.
Folgender Code
1
floatf=1.0000000001;
2
if(f==1.0000000001)
3
{
4
printf("f: gleich\n");
5
}
6
7
floatg=1.0000000001F;
8
9
if(g==1.0000000001F)
10
{
11
printf("g: gleich\n");
12
}
13
14
if(f==g)
15
{
16
printf("f==g\n");
17
}
gibt bei mir aus
1
g: gleich
2
f==g
Wie man sieht, schlägt der erste Vergleich aufgrund der höheren
Präzision von double fehl.
> Also ähnlich wie int i = 28.1; auch später nicht auf Gleichheit geprüft> werden kann.
Wenn ich es mit (int)28.1 vergleiche, würde ich aber durchaus Gleichheit
erwarten.