Modulo und Dezimalzahl

#7779110
Lesenswert?

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?

#7779117
Lesenswert?

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 #7779149
Lesenswert?

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.

Moderator (Firma: Titel) Persönliche Seite #7779162
Lesenswert?

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.

#7779165
Lesenswert?

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.

#7779192
Lesenswert?

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.

#7779196
Lesenswert?

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.

#7779272
Lesenswert?

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.

#7779296
Lesenswert?

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"

#7779301
Lesenswert?

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 #7779302
Lesenswert?

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

#7779312
Lesenswert?

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.

#7779329
Lesenswert?

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.

Moderator (Firma: Titel) Persönliche Seite #7779363
Lesenswert?

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.

#7779501
Lesenswert?

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.

#7779798
Lesenswert?

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.

#7780590
Lesenswert?

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.

#7781401
Lesenswert?

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.

#7781562
Lesenswert?

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.

#7782064
Lesenswert?

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

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren