Forum: Mikrocontroller und Digitale Elektronik Berechnung mit DEFINEs klappt nicht korrekt


von Sefco (Gast)


Lesenswert?

Guten Abend,

ich führe in einer Konstanten per #define eine Berechnung durch:

#define MYVARIABLE 3500/81

Diese Konstante nutze ich, um in einer Funktion eine Berechnung 
durchzuführen:

uint16_t result = 0;

result = 69*MYVARIABLE;

Das Ergebnis dieser Berechnung ist 65282, was FALSCH IST!
Richtig wäre 3500/81 * 69 = 2967.

Das korrekte Ergebnis erhalte ich, wenn ich folgendes tue:

uint16_t result = 0;
uint16_t myVar = MYVARIABLE;

result = 69*myVar;

In MYVARIABLE steckt die korrekte Zahl drin, doch bei der Multiplikation 
läuft irgendwas falsch. Auch folgende Lösung klappt nicht:

uint16_t result = 0;

result = 69*(uint16_t)MYVARIABLE;

Das Ergebnis ist jetzt 554.

Wieso funktioniert die Berechnung result = 69*MYVARIABLE nicht richtig?

__

Ich lasse mir die Werte per UART anzeigen und habe einen Atmega2560.

von ui (Gast)


Lesenswert?

Tja, du machst hier einige Fehler (neben der nicht richtigen 
Formatierung)

1. 3500/81 = 43.2 -> da du nicht explizit castest, castet der Compiler 
offensichtlich, also steht da 43. Das ist dein erstes Problem.

Sefco schrieb:
> Richtig wäre 3500/81 * 69 = 2967.

Das schreibst du aber nicht hin!
Du schreibst
1
result = 69*MYVARIABLE;
was ausgerollt
1
result = 69*3500/81;
ist.
Und jetzt kommt dein engültiges Problem. 69*3500 passt nicht in eine 
16bit Variable. Da das Ergebnis aber eine 16 Bit Variable ist, wird der 
Compiler hier schon implizit casten und dir Stellen wegschmeissen. 
Deswegen passt das Ergebnis auch überhaupt nicht zusammen.

Also für dich:
1. Formatierung benutzen!
2. KEINE IMPLIZITEN CASTS!
3. Das was du so programmiert hast, auch so hinschreiben!
4. Über die Größe von Variableninhalten nachdenken!
5. Versuchen vernünftig zu "debuggen", d.h. man rollt die Rechnung halt 
dann mal per Hand auf und gibt sich Zwischenwerte aus.
6. Das Problem wäre auch aufm PC passiert, da kann man sowas auch 
debuggen und testen.

von Sefco (Gast)


Lesenswert?

Viele Dank für die Erklärung!

Mir war nicht klar, dass er zuerst 69*3500 bestimmt. Das ist mein 
Problem. Das er mir 43 aus 43,... macht ist i.O. und mir auch klar.

Kannst du mir bitte noch erklären, was

1. Formatierung benutzen!
2. KEINE IMPLIZITEN CASTS!

bedeuten soll?

von ui (Gast)


Lesenswert?

Sefco schrieb:
> 1. Formatierung benutzen!

steht direkt bevor du nen post schreibst da.
1
c code

Sefco schrieb:
> KEINE IMPLIZITEN CASTS!
1
uint16_t a = 69*3500;
ist ein impliziter cast auf ne 16 Bit Variable (ist natürlich ein 
einfaches Beispiel, bei dem das jeder sieht). Offensichtlicher für jeden 
ist allerdings, wenn man explizit castet. Für das Makro ist es noch 
offensichtlicher
1
#define MYVARIABLE (uint32_t)(3500/81)
2
3
wird beim einsetzen zu
4
uint16_t a = MYVARIABLE * 69;
5
6
<=>
7
8
uint16_t a = (uint32_t)(3500/81) * 69;

Dann hat man zwischendurch nen cast auf ne 32 Bit Variable, dann wird 
geteilt und dann wieder auf nen 16 Bit gecastet.

von Einer K. (Gast)


Lesenswert?

1
uint16_t a = (uint16_t) (69UL * MYVARIABLE);

von A. S. (Gast)


Lesenswert?

Sefco schrieb:
> #define MYVARIABLE 3500/81

Die beiden schlimmsten Fehler sind:

a) den Ausdruck nicht geklammert: So
1
#define MYVARIABLE (3500/81)
Das sollte man immer tun bei Zahlenwerten, muss man bei Rechnungen. 
Probier mal Spaßeshalber x=MYVARIABLE^2;

a) Warnmeldungen aus. Bei Deinem Konstrukt warnt der Compiler (oder wenn 
der zu alt ist, LINT).

von Einer K. (Gast)


Lesenswert?

Achim S. schrieb:
> a) den Ausdruck nicht geklammert: So

In diesem Fall ist das Ergebnis ohne Klammer genauer.
Bedenke: Erst Multiplizieren, dann Dividieren.


Es ist also insgesamt etwas "unglücklich" :-)

von Wilhelm M. (wimalopaan)


Lesenswert?

Der Cpp ist ein nicht-interaktiver Editor - so ähnlich wie m4 oder sed. 
D.h. dass der Compiler etwas kompiliert, was wir nicht sehen. Das führt 
dann bei Macros, die aussehen wie Funktionen, zu solchen Fehlern, wo 
scheinbar auch Punkt- vor Strichrechnung verletzt wird.

Der Präprozessor gehört eigentlich auf den Müll der Geschichte. 
Natürlich muss er aus Kompatibilitätsgründen erhalten bleiben. Aber 
etwas anderes als #include (und allerhöchstens bedingte Compilation in 
C) sollte er nicht mehr machen.

Konstanten sind const, in C++ evtl. als compile-time konstant dann 
constexpr. In C++ hat man insgesamt mehr Möglichkeiten. Und in Zukunft 
brauchen wir acuh kein #include in C++ mehr ...

von Bernd K. (prof7bit)


Lesenswert?

Sefco schrieb:
> #define MYVARIABLE 3500/81
>
> [...]
>
> In MYVARIABLE steckt die korrekte Zahl drin, doch bei der Multiplikation
> läuft irgendwas falsch. Auch folgende Lösung klappt nicht:

***STOP***!

Hier liegt der Hund bereits im Pfeffer!

Bereits Deine Annahme in MYVARIABLE stecke eine Zahl drin ist falsch! Ja 
sogar die Namensgebung lässt stark vermuten daß Du irrtümlicherweise 
annimmst daß es überhaupt sowas wie eine Variable sei, das ist jedoch 
falsch und führt Dich auf die falsche Fährte.

1
#define MYVARIABLE 3500/81

MYVARIABLE ist jetzt stellvertretend für die Zeichenfolge 3500/81 im 
Quelltext und der Preprocessor macht nichts anderes als Textersetzung! 
Und es ist NICHT sowas wie eine Variable, es ist ein Fetzen Quelltext 
(text!) der einen bequemen Namen erhalten hat. Der Preprozessor wird den 
text einsetzen und dann erst übergibt er es an den Compiler.
1
result = 69*MYVARIABLE;

der Preprozessor macht daraus:
1
result = 69*3500/81;

Und nann gibt er es an den Compiler.

Du verwendest einen Atmega, dort ist ein int 16 Bit breit. Per Standard 
werden alle arithmetischen Ausdrücke vor der Rechnung auf int erweitert, 
es sei denn einer davon wäre vorher bereits größer, ist hier aber nicht 
der Fall, sowohl 69 als auch 3500 als auch 81 passen in ein int, also 
wird die Rechnung von links nach rechts mit int ausgeführt und weil auf 
dem AVR ein int nur 16 Bit hat läuft die Multiplikation über.

Du könntest aber schreiben:
1
#define MYVARIABLE 3500UL/81

dann wäre der Compiler gezwungen den ganzen Ausdruck in unsigned long 
auszuwerten und erst am Schluss das Ergebnis wieder passend zum Typ von 
result zu casten. Da das alles Konstanten sind wird die ganze Rechnung 
bereits zur Compilezeit ausgeführt, ein Performancenachteil ergibt sich 
dadurch also nicht.

Jedoch was bereits zuvor gesagt wurde: sei Dir bewußt daß da im ersten 
Schritt erstmal nur eine reine Textersetzung stattfinden wird, wenn da 
jetzt auch noch Addition und Multiplikation in unglücklicher Reihenfolge 
vorkämen greift Punkt vor Strich und das Ergebnis wäre nicht was Du 
erwartest.

Also mach um solche Ausdrücke lieber grundsätzlich Klammern drum:
1
#define MYVARIABLE (3500/81)

oder wenn Du den Rundungsfehler vermeiden willst lass die Klammern weg 
und zwinge es auf long aber sei Dir der Tatsache bewußt in welcher 
Reihenfolge das dann da steht und gerechnet wird nachdem der 
Preprozessor alle Makros aufgelöst haben wird

von Simon K. (simon) Benutzerseite


Lesenswert?

Achim S. schrieb:
> a) den Ausdruck nicht geklammert: So#define MYVARIABLE (3500/81)
> Das sollte man immer tun bei Zahlenwerten, muss man bei Rechnungen.
> Probier mal Spaßeshalber x=MYVARIABLE^2;

Das Beispiel ist natürlich nur begrenzt sinnvoll. ^ ist in C der XOR 
Operator.

von Bernd K. (prof7bit)


Lesenswert?

Wilhelm M. schrieb:
> Der Präprozessor gehört eigentlich auf den Müll der Geschichte.

Nein. Aber ich würde mir wünschen daß er ein paar mehr Features bekommt, 
evtl sogar Turing-vollständig wird. Das könnte im Gegenzug einige 
Gehirnakrobatik-Würgarounds entschärfen wenn man die einfacher und 
klarer hinschreiben könnte.

von Wilhelm M. (wimalopaan)


Lesenswert?

Wie gesagt: #include sollte in c++17 durch modules abgelöst werden - 
wird nun leider erst c++20.

Bedingte Compilation ist eigentlich nur wegen den üblichen C-APIs 
notwendig.

Und Macros braucht man in C++ eben nicht, da haben wir templates und das 
ist Turing-vollständig. Deswwegen ist C++ das besser C, man muss ja kein 
OOP mit C++ machen ...

von Achim (Gast)


Lesenswert?

Arduino F. schrieb:
> In diesem Fall ist das Ergebnis ohne Klammer genauer.

Das kann aber hier kein Argument sein. Wenn er es genauer haben will, 
kann er floatingpoint nehmen oder den Multiplikator ins Makro ziehen.

von ui (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Und Macros braucht man in C++ eben nicht, da haben wir templates und das
> ist Turing-vollständig. Deswwegen ist C++ das besser C, man muss ja kein
> OOP mit C++ machen ...

Ich wollte mich eigentlich nicht triggern lassen, aber es hilft nix wenn 
sowas ein Angemeldeter user sagt.
C++ ist nicht besser als C, da es alle (auch die ganzen beschissenen) 
Konstrukte, übernimmt. Also ist es wenn dann eine Erweiterung von 
beschissenen Konstrukten um noch mehr Konstrukte. Soviel dazu.
Bedingte Kompilation ist genau das, bedingte Kompilation. Und das ist 
etwas wirklich tolles, wenn man Makros nicht für Berechnungen 
missbraucht. Sondern eben für z.B. bedingte Kompilierung.
Beispiel: Festlegung von Arraygrößen am Headeranfang und nicht an 
tausend stellen im Code. Als Beispiel. Oder verschiedene includes für 
verschiedene Plattformen, bestes Beispiel ist doch dafür
1
#include <windows.h>

von Wilhelm M. (wimalopaan)


Lesenswert?

ui schrieb:
> Wilhelm M. schrieb:
>> Und Macros braucht man in C++ eben nicht, da haben wir templates und das
>> ist Turing-vollständig. Deswwegen ist C++ das besser C, man muss ja kein
>> OOP mit C++ machen ...
>
> Ich wollte mich eigentlich nicht triggern lassen, aber es hilft nix wenn
> sowas ein Angemeldeter user sagt.

C++ ist deswegen besser, weil es eine ganze Menge Merkmale hat (bei der 
uC Entwicklung eben hauptsächlich templates und compile-time 
expressions) die Arbeit ersparen und gleichzeitig viele Fehler bei der 
Compilation auftreten lassen.

> C++ ist nicht besser als C, da es alle (auch die ganzen beschissenen)
> Konstrukte, übernimmt. Also ist es wenn dann eine Erweiterung von
> beschissenen Konstrukten um noch mehr Konstrukte.

Ja, und dazu gehört leider auch der Cpp.

> Soviel dazu.
> Bedingte Kompilation ist genau das, bedingte Kompilation. Und das ist
> etwas wirklich tolles, wenn man Makros nicht für Berechnungen
> missbraucht. Sondern eben für z.B. bedingte Kompilierung.
> Beispiel: Festlegung von Arraygrößen am Headeranfang und nicht an
> tausend stellen im Code. Als Beispiel.

Dafür braucht es doch keinen Cpp, das ist jetzt aber ein ganz 
unpassendes Beispiel!

> Oder verschiedene includes für
> verschiedene Plattformen, bestes Beispiel ist doch dafür

>
1
> #include <windows.h>
2
>

Wie gesagt: solange wir noch #includes haben, ja. (s.o.)

von ui (Gast)


Lesenswert?

Wilhelm M. schrieb:
> C++ ist deswegen besser, weil es eine ganze Menge Merkmale hat (bei der
> uC Entwicklung eben hauptsächlich templates und compile-time
> expressions) die Arbeit ersparen und gleichzeitig viele Fehler bei der
> Compilation auftreten lassen.

Dazu muss ich leider sagen: 
https://de.wikipedia.org/wiki/Embedded_C%2B%2B

Und mir sind inzwischen doch schon ein paar Plattformen übern Weg 
gelaufen, die zwar prinzipiell C++ unterstüzten, aber eben z.B. keine 
Templates. Fand ich auch schade, ist aber so.

von Bernd K. (prof7bit)


Lesenswert?

Oder zur Codegenerierung:


1
#define PIN_OUTPUT_LIST                                                 \
2
    X(VALVE_HYDROGEN,   B, 19)                                          \
3
    X(VALVE_OXYGEN,     B, 18)                                          \
4
    X(IGNITION,         D, 1)                                           \
5
6
/////////////////////////////////////////////////////////////////////////
7
8
#define X(NAME, LETTER, NUMBER)                                         \
9
                                                                        \
10
    static inline void NAME ## _on() {                                  \
11
        FPT##LETTER->PSOR = (1UL << NUMBER);                            \
12
    }                                                                   \
13
                                                                        \
14
    static inline void NAME ## _off() {                                 \
15
        FPT##LETTER->PCOR = (1UL << NUMBER);                            \
16
    }                                                                   \
17
18
    PIN_OUTPUT_LIST
19
20
#undef X

*duck*und*weg

von Wilhelm M. (wimalopaan)


Lesenswert?

ui schrieb:
> Wilhelm M. schrieb:
>> C++ ist deswegen besser, weil es eine ganze Menge Merkmale hat (bei der
>> uC Entwicklung eben hauptsächlich templates und compile-time
>> expressions) die Arbeit ersparen und gleichzeitig viele Fehler bei der
>> Compilation auftreten lassen.
>
> Dazu muss ich leider sagen:
> https://de.wikipedia.org/wiki/Embedded_C%2B%2B

Das ist doch fast 20 Jahre alt ...

von ui (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Das ist doch fast 20 Jahre alt ...

Machts ja nicht besser, wenn es immer noch Compiler gibt, bei denen nur 
sehr eingeschränkt C++ verfügbar ist. IMHO sind die besten Features von 
C++
- templates
- Funktionsüberladung

von Wilhelm M. (wimalopaan)


Lesenswert?

ui schrieb:
> Wilhelm M. schrieb:
>> Das ist doch fast 20 Jahre alt ...
>
> Machts ja nicht besser, wenn es immer noch Compiler gibt, bei denen nur
> sehr eingeschränkt C++ verfügbar ist. IMHO sind die besten Features von
> C++
> - templates
> - Funktionsüberladung

Meine persönlichen Favoriten sind:
- variadic templates
- constexpr functions
- constexpr-if
- const-correctness

und viele Kleinigkeiten:
- fold expressions
- using template alias
- ranged-based for

Aber die Liste ist unvollständig!!! Das ist nur das, an was ich icm 
Moment auf die Schnelle denke (und bezieht sich nur auf uC).

Meistens fällt das mir erst wieder auf, wenn ich gezwungen bin rein 
prozedural in C zu programmieren ...

von Einer K. (Gast)


Lesenswert?

ui schrieb:
> Machts ja nicht besser, wenn es immer noch Compiler gibt, bei denen nur
> sehr eingeschränkt C++ verfügbar ist.

Naja...
Der AVR übliche gcc ....
Solltest evtl. mal schauen, was der alles kann....
Templates, kein Problem.

von Wilhelm M. (wimalopaan)


Lesenswert?

Arduino F. schrieb:
> ui schrieb:
>> Machts ja nicht besser, wenn es immer noch Compiler gibt, bei denen nur
>> sehr eingeschränkt C++ verfügbar ist.
>
> Naja...
> Der AVR übliche gcc ....
> Solltest evtl. mal schauen, was der alles kann....
> Templates, kein Problem.

AVR ist ja nicht alles!

Es war wohl so gemeint, dass der g++ aber nicht alle Plattformen kann, 
und clang++ auch nicht.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> In C++ hat man insgesamt mehr Möglichkeiten.

Ja, das wissen wir.

Ist aber kein Grund, in jedem, aber auch wirklich jedem Thread, der 
NIX mit C++ zu tun hat, immer wieder den gleichen Sermon runterzuleiern 
und dein C++ hochzupreisen...

von Dussel (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Wie gesagt: #include sollte in c++17 durch modules abgelöst werden
Hieße das, dass man nicht mehr mehrere Dateien, h und cpp aktuell halten 
muss, sondern der Compiler sich selber raussucht, welche Klassen es 
gibt? Das ist für mich einer der größten Vorteile von Java, weil es mich 
stört, für jede Änderung extra nochmal die Headerdatei anpassen zu 
müssen.

von Achim (Gast)


Lesenswert?

Dussel schrieb:
> Hieße das, dass man nicht mehr mehrere Dateien, h und cpp aktuell halten
> muss, sondern der Compiler sich selber raussucht, welche Klassen es
> gibt?

Was bedeutet das? Oder welches Problem hast Du bzw. löst Du damit?

von Wilhelm M. (wimalopaan)


Lesenswert?

Johann L. schrieb:
> Wilhelm M. schrieb:
>> In C++ hat man insgesamt mehr Möglichkeiten.
>
> Ja, das wissen wir.
>
> Ist aber kein Grund, in jedem, aber auch wirklich jedem Thread, der
> NIX mit C++ zu tun hat, immer wieder den gleichen Sermon runterzuleiern
> und dein C++ hochzupreisen...

Es geht doch gar nicht um C oder(!) C++.

Solche Fehler wie der in diesem Thread treten leider immer wieder auf, 
weil man ein altes Werkzeug (Cpp) benutzt in einer Art, die heute nicht 
mehr notwendig ist. Leider kann man das Werkzeug nicht abschaffen. Aber 
man kann einen eigenen Übergang zu besserem Code machen. Der hier 
diskutierte C-Code lässt sich auch als C++-Code übersetzen (zumindest 
der gezeigte Teilbereich, aber ich denke, dass das allgemein für die 
überwiegende Menge gilt. Ich weiß auch, dass es Compiler-spezifische 
Erweiterungen bzw. (noch) nicht standardisierte Erweiterungen gibt). 
Dann kann man eben Dinge besser machen.

Hier konkret: statt #define verwendet man constexpr und 
constexpr-functions (ggf. zusammen mit namespaces).

von Wilhelm M. (wimalopaan)


Lesenswert?

Achim schrieb:
> Dussel schrieb:
>> Hieße das, dass man nicht mehr mehrere Dateien, h und cpp aktuell halten
>> muss, sondern der Compiler sich selber raussucht, welche Klassen es
>> gibt?
>
> Was bedeutet das? Oder welches Problem hast Du bzw. löst Du damit?

Das möchte und kann ich nicht alles aufschreiben, das haben andere Leute 
schon besser gemacht, und es gab ja einen Entwurf, der es leider nicht 
in C++17 geschafft hat, der aber in clang/clang++ schon drin ist:

http://clang.llvm.org/docs/Modules.html

Viel Spaß beim Lesen!

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.