Also was in den einzelnen Zeilen passiert, steht ja bereits dahinter, nur wenn ich mir das an einem konkreten Beispiel anschaue, ist es mir noch nicht ganz klar:
z.B. percent = 428
(428/100)%10=(4,28)%10 >> 4,28%10 also 4,28/10 ist 0 Rest 4,28
Ich nehme an, dass nur die "4" in UART_TX[1] gespeichert wird, aber wie funktioniert das genau?
Also was in den einzelnen Zeilen passiert, steht ja bereits dahinter,
nur wenn ich mir das an einem konkreten Beispiel anschaue, ist es mir
noch nicht ganz klar:
z.B. percent = 428
(428/100)%10=(4,28)%10 >> 4,28%10 also 4,28/10 ist 0 Rest 4,28
Ich nehme an, dass nur die "4" in UART_TX[1] gespeichert wird, aber wie
funktioniert das genau?
Du hast leider vergessen, dazuzuschreiben, von welchem Typ percent ist. Ich vermute, dass es ein Integer-Typ ist. Dann ist 428/100 = 4.
Den %-Operator gibt es für floating-Point-Zahlen auch gar nicht. 4,28%10 ist also nicht 4,28, sondern ein Fehler.
Diese Rechnung ist übrigens falsch. Es muss "/256" heißen, wenn es technisch korrekt sein soll.
Nur aus Interesse: programmierst du Arduino? Auffallend viele Beispiele aus dieser Ecke verwenden solche falschen Faktoren 2^n-1 (wie 255, 1023, 2047, 4095, usw) beim Rechnen mit dem ADC.
Aber um genau diese ›8‹ geht's doch.
Die im Eröffnungsbeitrag genannte Berechnung stimmt nur dann, wenn man einen 7,9944 Bit ADC hat. Jede der 7,9944 Stellen kann entweder ›0‹ oder ›1‹ annehmen. Also zwei Zustände.
Diese erstaunlich umständliche Berechnung ginge übrigens ganz ohne rechnerischen Umweg (wozu hier unnötigerweise eine Spannung ausrechnen?) und ohne rechenintensives float viel einfacher so:
1
percent=((int)ADC_Value*100)/256;
Das geht sogar ganz ohne Division, weil hier das untere Byte des 16-Bit-Integers einfach ignoriert wird. Probiers aus.
percent = ((int)ADC_Value*100)/256;
Das geht sogar ganz ohne Division, weil hier das untere Byte des
16-Bit-Integers einfach ignoriert wird. Probiers aus.
Aber um genau diese ›8‹ geht's doch.
Die im Eröffnungsbeitrag genannte Berechnung stimmt nur dann, wenn man
einen 7,9944 Bit ADC hat. Jede der 7,9944 Stellen kann entweder ›0‹ oder
›1‹ annehmen. Also zwei Zustände.
2**7,9944 == 255
Stimmt. Bei meiner Rechnung wären es quasi immer noch 8 Bit, aber jedes Bit könnte die Werte 0 und 0.99609375 annehmen.
Aber ob nun 255 oder 256 geht doch in der Menge der Fehler zunächst mal unter.
Die Berechnung an sich stimmt doch schon nicht bzw. macht nicht das was sie machen soll und ist viel zu kompliziert. Aber das wurde doch schon alles gesagt.
Ein Bit kann sein, was immer du willst. Zunächst einmal ist ein Bit ja nur eine binäre Information, kennt also zwei mögliche Zustände. Was diese beiden Zustände bedeuten, legt man bei der Verwendeung fest. Das kann z.B. true und false, 1 und 0, an und aus, links und rechts oder Asterix und Obelix sein - oder auch 0,99609375 und 0. Ob das sinnvoll ist, steht aber natürlich auf einem anderen Blatt.
Wenn das Ganze auf einer Platform ohne Floating-Point-Unit läuft, wäre folgender Ansatz effizienter mit Blick auf die Laufzeit
Den Faktor, der bei 3.3 / 256 herauskommt mit der 10´er Potenz multiplizieren, dass der Faktor keine Nachkommastelle mehr hat.
Den ADC-Wert mit diesem Faktor multiplizieren - die Zielvariable muss natürlich ein ausreichend großer Typ sein.
Dann stimmen die Ziffern in der Zahl zumindest schon mal.
Unter dem Wissen, was der maximale Wert ist, der entstehen kann und um wie viele Kommastellen (10´er Potenzen) man zu groß ist, kann man die Zahl manuell in einem Schleifen-basierten Ansatz manuell zerlegen.
Das kann man mit 1-If + 8 If-else und einem else machen, oder laufzeiteffizienter nach dem Teile-Und-Herrsche Ansatz.
Vielleicht kann ChatGpt etwas brauchbares oder wenigstens eine Idee erzeugen mit etwas wie
"Schreibe eine Schleife, welche eine 16 bit unsigned integer zahl in ihre einzelne Ziffern zerlegt und die einzelnen Ziffern in einem Array von char typen speichert"
Wenn das Ganze auf einer Platform ohne Floating-Point-Unit läuft, wäre
folgender Ansatz effizienter mit Blick auf die Laufzeit
Das stimmt. Wenn man bedenkt das eine FP-Div zB. bei einem AVR maximal 500 Taktzyklen braucht, könnte man bei einhundert Berechnungen pro Sekunde und 16MHz locker:
... ist hier völlig uninteressant, weil das Ziel ja eine Prozentzahl des ADC-Werts ist.
Den Faktor, der bei 3.3 / 256 herauskommt mit der 10´er Potenz
multiplizieren, dass der Faktor keine Nachkommastelle mehr hat
Das hast du aber nicht auch mal nachgerechnet? Denn 3,3/256 ist 0,012890625. Denn nach deiner Vorgehensweise müsste man also mit 1000000000 multiplizieren. Da bräuchte dieser Faktor allein schon 26 Bits.
Vor solcher umständlicher und unverständlicher Software graut mir. Wie die Rechnung ganz einfach mit einer Multiplikation und ganz ohne Division von vorne weg mit reinen 16-Bit Integerzahlen geht, habe ich schon gezeigt.
Auch wenn das sicher ein wenig sarkastisch gemeint ist: wenn man das eben nicht beachtet und sich die Denkweise "wird schon schnell genug sein" zu eigen macht, dann geht einem eben trotzdem mal die Rechenzeit aus...
Auch wenn das sicher ein wenig sarkastisch gemeint ist:
War es…
wenn man das
eben nicht beachtet und sich die Denkweise "wird schon schnell genug
sein" zu eigen macht, dann geht einem eben trotzdem mal die Rechenzeit
aus...
Ich gebe zu, dass ich des Öfteren den Fehler begehe davon auszugehen, dass man sich bei der Software-Entwicklung im Allgemeinen und der µC-Entwicklung im Speziellen vorher Gedanken macht. ;-)
Nein. In Programmiersprachen hängt die Art der Division oft vom Typ der
Variablen ab:
Und in C meint das : vom Typ der Ausgangswerte/-variablen. Wo man das Ergebnis rein lädt ist dem Compiler erstmal schnuppe. Das macht erst was aus, wenn das konkrete Ergebnis nicht rein passt.
So wie der Versuch in eine so
1
staticunsignedcharpercent=0;
angelegte Variable eine 428 rein zu quetschen auf sehr vielen Systemen schief gehen dürfte. char muss IMHO mindestens nur 8 Bit haben und hat auf allem was nicht UTF16 oder UTF32 für Zeichen verwendet vermultich auch nicht mehr.
Ich konnte das, denn es geht hier nicht darum, den endlosen Bruch 10/3 zu erweitern, sondern lediglich um die Division 33/2560, die dann halt wie oben vorgerechnet 9 Nachkommastellen hat.
... die dann halt wie oben vorgerechnet 9 Nachkommastellen hat.
Das ist völlig übertrieben. Es ist sinnlos, wesentlich genauer zu rechnen als die Genauigkeit der Eingangsdaten her gibt.
Auch hier gilt: Garbage in, Garbage out.
Wenn der Wandler 8 Bit hat, sind 4 gültige Stellen deutlich genug.
Das ist mir durchaus klar. Natürlich würde ich in so einem Fall auch nur so weit gehen, dass nicht auf 12890625 erweitert wird, sondern nur bis 1289, weil der Rest dann nur noch 1/5 LSB sein kann.
Aber wie wiederholt gesagt: hier ist der ganze Rechenweg über die 3,3 völlig sinnlos.
Angenommen, das Ergebnis hat – mathematisch gesehen – den Wert 35. Wenn die Ergebnisvariable vom Typ uint8 ist, generiert er einen Code, der 8 Bit abspeichert. Ist die Ergebnisvariable vom Typ float, dann generiert er einen Code, der 32 Bit abspeichert.
In Programmiersprachen hängt die Art der Division oft vom Typ der
Variablen ab:
Und in C meint das : vom Typ der Ausgangswerte/-variablen. Wo man das
Ergebnis rein lädt ist dem Compiler erstmal schnuppe.
Oh nein. Ein C-Compiler berücksichtigt IMMER den Typ der
Ergebnisvariable.
Nein. Der Typ, mit dem eine Rechenoperation durchgeführt wird (und damit auch der Typ des Ergebnisses), hängt immer nur von den Operanden ab, niemals von dem Typ, wo der Ergebniswert im Anschluss reingeschrieben wird. Erst nach der Rechenoperation wird der Wert vom Ergebnistyp in den Zieltyp konvertiert.
Angenommen, das Ergebnis hat – mathematisch gesehen – den Wert 35. Wenn
die Ergebnisvariable vom Typ uint8 ist, generiert er einen Code, der 8
Bit abspeichert.
Nein. Der kleinste Typ, mit dem Rechenoperationen durchgeführt werden, ist int. Das nennt sich "integer promotion". Natürlich kann es passieren, dass der Optimizer hier Vereinfachungen vornimmt, aber diese dürfen das Ergebnis nicht mehr verändern. Es muss das gleiche rauskommen, als wäre die Berechnung mit Typ int durchgeführt worden.
Ist die Ergebnisvariable vom Typ float, dann generiert
er einen Code, der 32 Bit abspeichert.
Nein, er berechnet erst ein Ergebnis vom Typ int, danach wird dieser Wert dann nach float konvertiert.
Beispiel: Angenommen, wir haben eine Plattform, wo int 16 Bit breit ist.
1
unsignedinta=50000;
2
unsignedintb=50000;
3
unsignedlongresult=a+b;
In result steht nachher nicht etwa 100000, sondern 34464, denn die Berechnung wird mit Typ unsigned int durchgeführt, der bei 65536 überläuft. Dass das Ergebnis nachher in eine long-Variable gespeichert wird, die genug Platz hätte, spielt keine Rolle. Die Berechnung wird äquivalent zu
Oh nein. Ein C-Compiler berücksichtigt IMMER den Typ der
Ergebnisvariable.
Nein, das tut er eben nicht!
1
floatf=3/2;
liefert Dir in f 1.0 und nicht 1.5.
Ebenso wie Dich
1
longintl=2000*2000;
eben nicht zum Millionär macht sondern magere 2304 in l ablegt.
Dagegen funktioniert
1
inti=100*100;
wie erwartet; i wird auf 10000 gesetzt, aber das ist eine ganz andere Geschichte mit dem Titel Integer-Promotion. Denn alle Operanden deren Typ kleiner als int ist, werden als erstes mal nach int gewandelt und der größte der Operanden bestimmt dann den Typ des Ergebnisses.
Ebenso wie Dich
longint l = 2000 * 2000;
eben nicht zum Millionär macht sondern magere 2304 in l ablegt.
Obacht: Das Überlaufverhalten vorzeichenbehafteter Typen ist undefiniert.
Dagegen funktioniert
int i = 100 * 100;
wie erwartet; i wird auf 10000 gesetzt, aber das ist eine ganz andere
Geschichte mit dem Titel Integer-Promotion. Denn alle Operanden deren
Typ kleiner als int ist, werden als erstes mal nach int gewandelt und
der größte der Operanden bestimmt dann den Typ des Ergebnisses.
Das passiert hier aber gar nicht, denn 100 ist sowieso schon vom Typ int.
Oh nein. Ein C-Compiler berücksichtigt IMMER den Typ der
Ergebnisvariable.
Nein.
Es ging um das am Ende aus Programmierersicht abgespeicherte Ergebnis. Ich zitiere: "Wo man das Ergebnis rein lädt ist dem Compiler erstmal schnuppe."
float A;
A = 3/2; // liefert 1.0, abgespeichert werden 32 Bit
uint8 A1;
A1 = 3/2; // liefert 1, abgespeichert werden (zumindest bei einem 8-Bit-µP) 8 Bit
Hat der Compiler nun den Typ von A bzw. A1 berücksichtigt oder nicht?
Würde er ihn nicht berücksichtigen, dann würde er im ersten Fall Müll in A abspeichern.
Der kleinste Typ, mit dem Rechenoperationen durchgeführt werden,
ist int. Das nennt sich "integer promotion".
Richtig. Aber das gilt nicht für das Abspeichern der Ergebnisse. Eine Variable vom Typ uint8 belegt im RAM je nach CPU nur 8 Bit. Aus Optimierungsgründen auch mal 16 Bit oder wieviel Bit auch immer der Typ int hat.
Es ging um das am Ende aus Programmierersicht abgespeicherte Ergebnis.
Ich zitiere: "Wo man das Ergebnis rein lädt ist dem Compiler erstmal
schnuppe."
Mit "erstmal" ist hier die Berechnung gemeint, und für die ist der Typ tatsächlich Schnuppe.
float A;
A = 3/2; // liefert 1.0, abgespeichert werden 32 Bit
Da sind zwei komplett getrennte Operationen im Spiel:
3/2 liefert ein temporäres Objekt vom Typ int mit dem Wert 1
A = 1 konvertiert diese 1 in einen float und schreibt den nach A.
uint8 A1;
A1 = 3/2; // liefert 1, abgespeichert werden (zumindest bei einem
8-Bit-µP) 8 Bit
Äquivalent zu oben:
3/2 liefert einen int mit dem Wert 1
A1 = 1 kopiert den Wert nach A1 und konvertiert ihn dazu nach uint8
Hat der Compiler nun den Typ von A bzw. A1 berücksichtigt oder nicht?
Bei der Berechnung hat er ihn nicht berücksichtigt. Bei der anschließenden Konvertierung in den Zieltyp wird der Zieltyp selbstverständlich berücksichtigt. Wie sollte diese Konvertierung denn auch sonst funktionieren?
Der kleinste Typ, mit dem Rechenoperationen durchgeführt werden,
ist int. Das nennt sich "integer promotion".
Richtig. Aber das gilt nicht für das Abspeichern der Ergebnisse.
Das Abspeichern des Ergebnisses kommt wie gesagt nach der Berechnung. Erst wird mit int gerechnet, dabei entsprechend ein int produziert, später dann konvertiert.
Eine Variable vom Typ uint8 belegt im RAM je nach CPU nur 8 Bit.
Wenn du uint8_t meinst, belegt der immer nur 8 Bit.
Aus Optimierungsgründen auch mal 16 Bit oder wieviel Bit auch immer der
Typ int hat.
Nein, das ist nicht erlaubt. uint8_t muss exakt 8 Bit breit sein.