Modulo und Dezimalzahl

OP #7779110
1
u_in = ADC_Value * 3.3 / 255;
2
percent = (u_in / 3.3) * 100;
3
unsigned char UART_TX[5] = {0,0,0,0,0};
4

5
UART_TX[1] = (percent/100)%10+0x30; // 100er ablegen
6
UART_TX[2] = (percent/10)%10+0x30; // 10er ablegen
7
UART_TX[3] = (percent%10)+0x30; // 1er ablegen

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.

Moderator (Firma: Titel) Persönliche Seite

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.

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.

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.

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:

1
100 × 500 ÷ 16000000 =  0,003125

0.3% einsparen.

Moderator (Firma: Titel) Persönliche Seite

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
static unsigned char percent = 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.

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.

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
unsigned int a = 50000;
2
unsigned int b = 50000;
3
unsigned long result = 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

1
unsigned int a = 50000;
2
unsigned int b = 50000;
3
unsigned int c = a + b;
4
unsigned long result = c;

durchgeführt.

Rolf schrieb:

Oh nein. Ein C-Compiler berücksichtigt IMMER den Typ der Ergebnisvariable.

Nein, das tut er eben nicht!

1
float f = 3/2;

liefert Dir in f 1.0 und nicht 1.5.

Ebenso wie Dich

1
longint l = 2000 * 2000;

eben nicht zum Millionär macht sondern magere 2304 in l ablegt.

Dagegen funktioniert

1
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.

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.

Antwort schreiben

Eine eigene Frage? Dieser Thread ist über ein Jahr alt. Stell sie in einem eigenen Thread – dort lesen mehr Leute mit, und sie wird öfter und schneller beantwortet.

Neuen Thread starten

Eine Lösung, Ergänzung oder Rückmeldung zu diesem Thread? Dann antworte hier.

Formatierung
  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] – Link zu einem Artikel
  • Verweis auf einen anderen Beitrag: Rechtsklick auf dessen Beitrags-ID (#8078628), „Adresse kopieren“, in den Text einfügen

Mehr zur Formatierung

Längeren Sourcecode als Anhang, nicht in den Text. JPG nur für Fotos und Scans; Zeichnungen und Screenshots als PNG oder GIF. Siehe Bildformate.

Schon registriert? Anmelden

Wird nicht angezeigt. Wir schicken dir einen Link; sobald du damit bestätigst, erscheint dein Beitrag.

Mit dem Abschicken legst du einen kostenlosen Account an und erkennst die Nutzungsbedingungen und die Datenschutzerklärung an.