Forum: Mikrocontroller und Digitale Elektronik Berechnung mit ATTiny44 berechnet einen komischen Wert


von Sven Puga (Gast)


Lesenswert?

Hi Zusammen

Ums gleich auf den Punkt zu bringen: (der rest des Programms ist gerade 
irrelevant)
1
unsigned long v = 0;
2
unsigned long n = 0;
3
unsigned int n1 = 0;
4
unsigned int D = 450;
5
unsigned int time_vorne = 0;
6
7
time_vorne = 10000; //Wert nur zum Testen
8
    n = 5461333/time_vorne;
9
    if (n <= 65535) {n1 = n;}
10
    else {n1 = 65535;}
11
    
12
    v = D*n1;
13
    v = v*36;
14
    v = v/1000000;
15
    v1 = v;
Wieso bekomme ich die ganze Zeit nach der Berechnung für v1=1???? Egal 
welchen Wert ich für time_vonre habe.
(Programm ist aus einer Drehzahlberechnung)

Danke für Antworten

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Was hindert dich daran, dir nacheinander einfach die Zwischenergebnisse 
ausgeben zu lassen und so selbst rauszufinden an welcher Stelle in der 
Berechnung du den Overflow hast?

Offenbar hast du ja eine Ausgabemöglichkeit.
Es ist nicht verboten, sich während der Programmentwicklung zb hier
1
time_vorne = 10000; //Wert nur zum Testen
2
    n = 5461333/time_vorne;
3
    if (n <= 65535) {n1 = n;}
4
    else {n1 = 65535;}
5
6
    ******

mal den Wert für n1 ausgeben zu lassen.
Und wenn man sich davon überzeugt hat, dass dieser Wert korrekt ist, 
dann lässt man sich eben
1
time_vorne = 10000; //Wert nur zum Testen
2
    n = 5461333/time_vorne;
3
    if (n <= 65535) {n1 = n;}
4
    else {n1 = 65535;}
5
    
6
    v = D*n1;
7
    *****
den Wert für v ausgeben. Usw. usw. Bis man die Stelle hat, an der 
"seltsame Dinge" passieren.

Klar kann ich dir auch so raussuchen, wo du den Overflow in der 
Berechnung hast. Die Frage ist nur, wie zielführend das ist. Denn sich 
selbst helfen zu können, ist eine wesentliche Fähigkeit beim Debuggen 
von Code. Und irgendwann muss man damit anfangen.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Dazu müßte man ja im AVRStudio den Simulator starten.

von Loipe (Gast)


Lesenswert?

>  v1=1????

Wieso kommen da vier Fragezeichen raus?

von Sven Puga (Gast)


Lesenswert?

@Karl Heinz: Stimmt, daran hatte ich noch gar nicht gedacht... Bin schon 
dran, Danke

@Peter Dannegger: Den müsste ich zuerst finden :D (Binärer Rechner des 
PC's sagt, es sollte der Wert 8 herauskommen)

von Karl H. (kbuchegg)


Lesenswert?

Sven Puga schrieb:
> @Karl Heinz: Stimmt, daran hatte ich noch gar nicht gedacht... Bin schon
> dran, Danke
>
> @Peter Dannegger: Den müsste ich zuerst finden :D (Binärer Rechner des
> PC's sagt, es sollte der Wert 8 herauskommen)

Dein Binärer Rechner hat aber auch nicht die Beschänkung, dass ein int 
nur 16 Bit groß ist. :-)
Und an einer Stelle hast du eben den üblichen Denkfehler gemacht, dass 
der Datentyp der Ergebnisvariable die Art und Weise der Berechnung 
(konkret eine Multiplikation) beeinflusst.

von Sven Puga (Gast)


Lesenswert?

Ach du kake!
Dass ich alle auf long setzen muss, hätte ich nicht gedacht... Danke 
vielmals Karl Heinz (Funktioniert jetzt ;))

Jetzt hab ich wieder was neues gelernt :)

von Karl H. (kbuchegg)


Lesenswert?

Musst du nicht unbedingt.

Du musst nur bedenken, dass in
1
    v = D*n1;

D ein int ist und n1 ein int ist.
Daher wird diese Multiplikation als 16-Bit int Multiplikation 
ausgeführt, die per Definition kein größeres Ergebnis als 16 Bit bringt.
Das du das Ergebnis dann an eine 32-Bit Variable (v) zuweist, ist zwar 
schön, aber uninteressant.
Um das zu verhindern, reicht es wenn einer der beiden Multiplikanden ein 
32 Bit Datentyp ist. Man kann jetzt natürlich D zu einem unsigned long 
machen, oder auch n1. Es reicht aber auch, wenn man einen der beiden 
(oder beide) entsprechend hochcastet:
1
    v = (unsigned long)D * n1;
und schon wird die Multiplikation als unsigned long Multiplikation 
durchgeführt mit einem echten 32 Bit Ergebnis.

von Sven Puga (Gast)


Lesenswert?

Interessant :D

Es funktioniert wirklich auch auf deine Art.
Dann kann man also im Prinzip Die Grösse der Berechnung mit dieser 
Klammer zuweisen, egal auf welche Grösse? (natürlich auf diejenige 
Variabel bezogen, die man ausrechnen will)

von Stefan K. (berliner)


Lesenswert?

>Dass ich alle auf long setzen muss
Besser float, denn das Zwischenergebniss v = D*n1 = 450/ 546,1333 = 
0,824 kann durch ganzzahlige Variablen nicht korrekt 
berechnet/dargestellt werden

von Stefan K. (berliner)


Lesenswert?

falls float mit ATTiny überhaupt geht, da war doch was mit begrenztem 
Arbeitsspeicher.

von Karl H. (kbuchegg)


Lesenswert?

Sven Puga schrieb:
> Interessant :D
>
> Es funktioniert wirklich auch auf deine Art.
> Dann kann man also im Prinzip Die Grösse der Berechnung mit dieser
> Klammer zuweisen,

?
Kann es sein, dass du gerade auf deine Art und WEise nach einem C-Buch 
schreist?

Die 'Klammer' hat einen Namen. Das nennt sich ein Cast!

Der Cast besagt
1
                  D          nimm D. Das ist ein unsigned int, so wie definiert
2
  (unsigned long) D          und dann wandle diesen Zahlenwert zu einem 
3
                              unsigned long.

Auf die Berechnung hat das nur insofern indirekt Einfluss, als die C 
Regeln nun mal besagen, dass die Art und Weise der Berechnung von
1
    a * b
vom Datentyp von a bzw. vom Datentyp von b abhängt. Der jeweils höhere 
Datentyp 'gewinnt'. Ist also a ein unsigned long und b ein unsigned int, 
dann wird die Berechnung als unsigned long Multiplikation durchgeführt, 
weil unsigned long der 'höhere' Datentyp ist. b wird dann vom Compiler 
automatisch implizit auf unsigned long hochgecastet, weil ja beide 
Datentypen gleich sein müssen. Es gibt an dieser STelle nun mal nur
1
    unsigned int  *   unsigned int
oder
1
    unsigned long   *   unsigned long
Die erste Form geht nicht, weil a ja ein unsigned long ist. Also geht 
nur die 2.te Form, dazu muss aber auch b ein unsigned long sein. Und 
genau das kann der Compiler von alleine machen, denn einen unsigned int 
kann man verlustfrei in einen unsigned long verwandeln.

Durch den Cast wurde nur sicher gestellt, dass a vom Typ unsigned long 
ist (obwohl D ja eigentlich ein unsigned int ist).

Da Casts die höchste Priorität aller C-Operatoren haben, ist also die 
implizit Klammerung der Operationen
1
   ( (unsigned long)D ) * v1

zuerst wird D von 16 Bit auf 32 Bit 'hochgehoben'. Und zwar auf 
Veranlassung des Programmierers.
dann wird implizit (durch den Compiler) v1 auf 32 Bit hochgehoben
dann wird multipliziert.

Datentypen sind nicht einfach nur schmückendes Beiwerk, sondern haben 
ganz praktische Konsequenzen!

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Stefan K. schrieb:
> falls float mit ATTiny überhaupt geht, da war doch was mit begrenztem
> Arbeitsspeicher.

Nö, sondern mit fehlender Linkeroption -lm.

von Sven Puga (Gast)


Lesenswert?

Ok, den Fehler habe ich jetzt kapiert (den Begriff "Cast" kannte ich 
noch gar nicht).
Also wenn ich jetzt schreibe:
x = (unsigned long)a * b;
dann ist a nur für diese Operation als 32Bit-Variabel definiert oder für 
"immer" als 32Bit zu finden?

Und ja, Fachbegriffe kenn ich nur ganz wenige ;) Hab das nie wirklich 
gelernt.

von Karl H. (kbuchegg)


Lesenswert?

Sven Puga schrieb:


> x = (unsigned long)a * b;
> dann ist a nur für diese Operation als 32Bit-Variabel definiert oder für
> "immer" als 32Bit zu finden?

der Datentyp von a spielt hier keine nennenswerte Rolle, ausser das er 
als Erbonkel für den Datentyp des Wertes fungiert, mit dem dann 
tatsächlich gerechnet wird.

Denn es wird der WERT (die Zahl) von a genommen.
Da a ein unsigned int ist, hat auch dieser Wert erst mal den Datentyp 
unsigned int.
Durch den Cast wird dann diese Zahl zu einem unsigned long hochgecastet.

Ja, auch Zahlen haben Datentypen!
In arithmetischen Ausdrücken geht es immer nur um Werte. Der Ausdruck
1
   a + b
besagt ja nichts anderes als
* nimm den Wert von a
* nimm den Wert von b
* addiere die beiden Werte

und  jetzt kommen da einfach nur noch die entsprechenden Datentypen ins 
Spiel. Wo der Wert konkret herkommt, spielt erst mal keine Rolle. 
Entscheidend ist, dass dieser WErt einen Datentyp hat und dieser 
Datentyp die Art und WEise steuer, wie eine Operation gemacht wird.

In
1
int foo()
2
{
3
  return 5;
4
}
5
6
int main()
7
{
8
  long j = foo() + 7;
9
}
ist der Datentyp des Ergebnisses, das von der Funktion geliefert wird 
ein int. 7 hat ebenfalls den Datentyp int. Daher ist das eine
1
   int + int
Addition und das Ergebnis hat wieder den Datentyp int.

Wohingegen in
1
int main()
2
{
3
  long j = (long)foo() + 7;
4
}
der Datentyp des Ergebnisses des Funktionsaufrufs zu einem long 
hochgecastet wird. (Die Zahl ändert ihren Datentyp!) D.h aus Sicht der 
Addition steht da
1
   long + int
und das bewirkt dann eine long-Addition, die ein long Ergebnis liefert.





Du solltest dir wirklich ein C-Buch besorgen. Das alles ist keineswegs 
irgendwelcher esoterischer Kram, sondern Grundlagen wie die 
Programmiersprache funktioniert. Wenn es schon daran scheitert, was in 
jedem Pipifax-Buch auf weniger als 5 Seiten beschrieben und erklärt 
wird, dann möchte ich nicht wissen, wieviel du von dem restlichen Zeugs, 
der auf den restlichen 195 Seiten des Buches steht, auch nicht weißt.

: Bearbeitet durch User
von Sven Puga (Gast)


Lesenswert?

Danke, dann ist mir das mal kar.

Ja, ich werde deinen Rat befolgen. Du hast recht, dass mir alle 
Grundlagen fehlen (Wunder also, dass die restlichen Programme laufen).
Das ich überhaupt programmiere ist sozusagen ein "Nebenprodukt einer 
Kettenreaktion", bei der ich auf eimal gezwungen war blut einzusteigen 
:(

Trozdem Danke für alle Antworten aller Beteiligten :)

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz schrieb:

> der Datentyp des Ergebnisses des Funktionsaufrufs zu einem long
> hochgecastet wird. (Die Zahl ändert ihren Datentyp!) D.h aus Sicht der
> Addition steht da
>
1
>    long + int
2
>
> und das bewirkt dann eine long-Addition, die ein long Ergebnis liefert.


Genauso wie hier
1
int main()
2
{
3
  long i = 3 * (long)( 5 + 7 );

Die Addition 5 plus 7 wird als int Addition durchgeführt, dann wird 
dieses Ergebnis zu einem long gecastet und mit 3 multipliziert.

Hier sind überhaupt keine Variablen auf der rechten Seite im Spiel. Wie 
soll daher irgendetwas dauerhaft auf long umdefiniert worden sein?

Es geht einfach nur darum, im Berechnungsbaum
1
                   Ergebnis - int
2
                       |
3
                       | (^ int)
4
                       |
5
             +----- Multiplikation int * int ----+
6
             |                                   |
7
             | (^int)                            | (^ int)
8
             |                                   |
9
             3 (int)              +------ Addition int + int --+
10
                                  |                            |
11
                                  | (^ int)                    | (^ int)
12
                                  |                            |
13
                                  5 (int)                      7 (int)
einen zusätzlichen Schritt einzufügen
1
                   Ergebnis (long)
2
                       |
3
                       | (^ long)
4
                       |
5
             +----- Multiplikation int * long ----+
6
             |                                    |
7
             | (^ int)                            | (^ long)
8
             |                                    |
9
             3 (int)                   nach long wandeln
10
                                                  |
11
                                                  | (^ int)
12
                                                  |
13
                                      +------ Addition int + int --+
14
                                      |                            |
15
                                      | (^ int)                    | (^ int)
16
                                      |                            |
17
                                      5 (int)                      7 (int)
so dass die Multiplikation zu einer int * long Operation gezwungen wird, 
was wiederrum den Compiler dazu nötigt, seinerseits eine weitere 
Operation
1
                   Ergebnis - long
2
                       |
3
                       | (^ long)
4
                       |
5
             +----- Multiplikation long * long ---+
6
             |                                    |
7
             | (^ long)                           | (^ long)
8
             |                                    |
9
         nach long wandeln                  nach long wandeln
10
             |                                    |
11
             | (^ int)                            | (^ int)
12
             |                                    |
13
             3 (int)                  +------ Addition int + int --+
14
                                      |                            |
15
                                      | (^ int)                    | (^ int)
16
                                      |                            |
17
                                      5 (int)                      7 (int)
einzufügen. Das muss er tun, weil bei der Multiplikation die beiden 
Teilbäume links und rechts unterschiedliche Datentypen haben. Der 
Teilbaum links hatt den Typ int, der Teilbaum rechts hatte den Typ long. 
Durch das Einfügen des Casts im linken Teilbaum sind jetzt wieder beide 
Teilbäume vom gleichen Datentyp, so dass eine Multiplikation möglich 
ist.

Der Compiler baut sich genau so einen Baum auf, setzt am unteren Ende 
(bei den Blättern) die Datentypen ein und arbeitet sich dann in Richtung 
oberes Ende (der Wurzel des Bauems) vor, wobei er anhand der in jedem 
Knoten bzw. den Teilbäumen vorliegenden Datentypen entscheidet, was zu 
tun ist.

Ausgehend von den Blättern dieses Baumes (die unteren Enden) durchlaufen 
die Werte den Baum nach oben, wobei die jeweilige Operation angewendet 
wird UND (ganz wichtig) auch die Datentypen entsprechend berücksichtigt 
bzw. angepasst bzw. ausgewertet werden.

Und so ergibt sich dann ein Ergebnis, bestehend aus einem konkreten 
Zahlenwert und einem Datentyp, das dann wieder weiter verarbeitet werden 
kann - zb. in einer Variablen gespeichert wird.

: Bearbeitet durch User
von Sven Puga (Gast)


Lesenswert?

Genau, so hab ich es auch verstanden. Ich bin (leider) eben davon 
ausgegangen, dass die Grösse der Variabeln für die Rechnung selber keine 
Rolle spielen, so wie das für den Binären Taschenrechner auch keine 
Rolle spielt (-> Hauptsache war, dass die auszurechnende Variabel genug 
gross definiert ist).

Der Baum macht es nochmals genau übersichtlich, danke Karl Heinz :)

von Bole aus Serbien (Gast)


Lesenswert?

Stefan K. schrieb:
> falls float mit ATTiny überhaupt geht, da war doch was mit begrenztem
> Arbeitsspeicher.

 Und ob das geht.
 Weniger als 400Byt im Assembler.

 mfg, Bole

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.