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?
Helene M. schrieb:> 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.
Helene M. schrieb:> z.B. percent = 428> (428/100)%10=(4,28)%10 >> 4,28%10 also 4,28/10 ist 0 Rest 4,28
Nein, (428/100) ist eine Integerdivision und als Ergebnis kommt folglich
4 heraus.
Helene M. schrieb:> u_in = ADC_Value * 3.3 / 255;
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.
Rolf M. schrieb:> Norbert schrieb:>> Ist das ein 7,9944 Bit ADC oder ein 8 Bit ADC?>> Ich komme auf 7,96875.
Mag sein. Aber wie?
ln(255)/ln(2) = 7,99435343685885793758
Wolfgang R. schrieb:> Norbert schrieb:>> Mag sein. Aber wie?>> 255/256*8
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
Helene M. schrieb:> static unsigned char percent = 0;
1. Wozu static?
2. In einen unsigend char passen die beispielhaften 428 gar nicht
rein...
Helene M. schrieb:
1
u_in=ADC_Value*3.3/255;
2
percent=(u_in/3.3)*100;
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.
Lothar M. schrieb:> 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.
Und zur schöneren Rundung vielleicht noch:
Norbert schrieb:> 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.
Bernd schrieb:> Wie kann ein Bit 0.99609375 sein?> Entweder 0 oder 1
Wie kann ein ADC 7,9944 Bit haben?
Es geht hier um eine rein hypothetische Betrachtung warum die Division
durch 255 (oder alle 2^n-1) falsch ist.
Edit: Rolf war schneller!
Bernd schrieb:> Wie kann ein Bit 0.99609375 sein?> Entweder 0 oder 1
Die "1" hat einen Dezimalen-Wert von 0.99609375.
Erst wenn du da wieder alle "0" dazu zählst, wird wieder ne 1 draus! :D
Rolf M. schrieb:> Bernd schrieb:>> Wie kann ein Bit 0.99609375 sein?>> Aber 7,9944 Bits sind kein Problem für dich?
Doch damit habe ich auch ein Problem.
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.
Helene M. schrieb:> also 4,28/10 ist 0 Rest 4,28
Nein. In Programmiersprachen hängt die Art der Division oft vom Typ der
Variablen ab:
https://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/c_modulo_de (erste
beiden Sätze)
float a; int b;
float c1; int c2;
a = 5.28; b = 10;
c1 = a/b; // ergibt 0.528;
c2 = a/b; // ergibt 0, es wird also abgeschnitten, nicht gerundet.
Bernd schrieb:> Wie kann ein Bit 0.99609375 sein?
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"
Jasson J. schrieb:> 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:
Jasson J. schrieb:> Den Faktor, der bei 3.3 / 256 herauskommt
... 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.
Norbert schrieb:> könnte man ... 0.3% einsparen.
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...
Lothar M. schrieb:> 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. ;-)
Daher ist der Einwand natürlich berechtigt.
Rolf schrieb:> 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.
Jasson J. schrieb:> Den Faktor, der bei 3.3 / 256 herauskommt mit der 10´er Potenz> multiplizieren, dass der Faktor keine Nachkommastelle mehr hat.
Das wird dir nicht gelingen.
Normalerweise reicht es, wenn der Restfehler bei der Skalierung
insgesamt deutlich kleiner als 1/2 LSB ist. Dann geht er in den
Wandlerfehlern unter.
Rainer W. schrieb:> Das wird dir nicht gelingen.
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.
Lothar M. schrieb:> ... 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.
Rainer W. schrieb:> Es ist sinnlos
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.
Flunder schrieb:> 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.
https://www.tutorialspoint.com/what-are-implicit-and-explicit-type-conversions-in-c-language
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.
Rolf schrieb:> Flunder schrieb:>> 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
Rolf schrieb:> 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.
Michi S. schrieb:> 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.
Rolf M. schrieb:>> 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.
Rolf schrieb:> 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.