Forum: Mikrocontroller und Digitale Elektronik mal geteilt plus minus mit signed und unsigned 16bit integers


von Fatih T. (alibaba06)


Lesenswert?

Hallo Ihr!

ich hab mit einem ATMega32 diverse Berechnungen zu erledigen und zum 
Schluss sollte dann eine uint16_t Zahl rauskommen
habe drei Formeln:

1. teilergebnis1= (uint16_t*int16_t/(2^24))+(uint16_t/(2^10))
2. teilergebnis2= (1/(2^14))*(int16_t + (int16_t*int16_t/(2^17)) + 
((int16_t*(int16_t)²)/2^34)

3. teilergebnis3= noch komplizierter

Endergebnis ist dann teilergebnis2*unit16_t + teilergebnis3

ich sitzt jetzt schon seit 4 Tagen daran und hab fast keine Haare mehr 
weil ich sie mir alle rausgerissen habe...

mir ist klar dass wenn man uint*int rechnet uint wieder als Ergebnis 
rauskommt, jedenfalls mit gcc.

Meine Frage:

um bei den Multiplikationen von zwei oder mehreren 16bit integern, deren 
Ergebnis 32bit oder gar 64bit sind kein Fehler zu machen ist welche 
Methode die sichere? Variablen gleich mit 64bit deklarieren? oder 
während der Berechnung castn? oder eine ganz andere Methode?
ich habe die beiden Methoden zum erbrechen getestet und komme bei jeder 
methode auf ein anderes Ergebnis... ich hab die ganze sache mal mit 
stift und blatt papier nach gerechnet und auf das ergebnis auf dem 
papier komme ich mit beiden methoden nicht..
zusätzlich kommt ja noch das vorzeichen Problem dazu... wie löse ich das 
am besten?  vllt alle variablen ersteinmal uint machen und dann bei der 
Berechnung berücksichtigen? oder erst beim endergebnis?

Danke schonmal für euren support/feedback :D

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Fatih T. schrieb:
> mir ist klar dass wenn man uint*int rechnet uint wieder als Ergebnis
> rauskommt, jedenfalls mit gcc.
Wie bist du darauf gekommen?

> wie löse ich das am besten?
Du musst dir selber erst mal klar werden, wo Überläufe passieren können, 
und wie du damit umgehen kannst. Auf jeden Fall wirst du zwischendurch 
herunterskaliern müssen, wenn du mit 32 Bit auskommen willst. Denn bei 
2^32/2^34 bleibt nicht viel übrig...

von Fatih T. (alibaba06)


Lesenswert?

uint*int=uint.... hat man mir so beigebracht.. es ist äquivalent zu 
double*int=int... also es wird der typ genommen der mit kleinerer bit 
anzahl und genauigkeit und vorzeichen.. oder hab ich da was falsch 
gelernt?

ja da hast du recht, da bleibt nicht viel übrig und zum float typ will 
ich nicht casten... mein problem ist wie ich an die sache überhaupt mal 
rangehe... gleich von anfang an 32 oda 64 bit variablen oder während der 
laufzeit richtiges casten... oder vllt doch mit float arbeiten?

von Karl H. (kbuchegg)


Lesenswert?

Fatih T. schrieb:

> um bei den Multiplikationen von zwei oder mehreren 16bit integern, deren
> Ergebnis 32bit oder gar 64bit sind kein Fehler zu machen ist welche
> Methode die sichere?

Den Code durchgehen und die Reihenfolge feststellen, in der die 
Operationen ausgeführt werden.
Danach die Wertebereiche der Variablen abklären und die Sequenzen von 
oben nach unten durchgehen, ob es zu einem Overflow kommen kann.

> Variablen gleich mit 64bit deklarieren?

Das ist die Rundumschlagmethode :-)
Äquivalent mit: Auf einem Kreuzfahrtschiff ist Tag und Nacht eine 
Schwimmweste zu tragen.

> oder
> während der Berechnung castn?

Falls es notwendig ist: ja

> ich habe die beiden Methoden zum erbrechen getestet und komme bei jeder
> methode auf ein anderes Ergebnis...

Dann machst du was falsch.

von Karl H. (kbuchegg)


Lesenswert?

Fatih T. schrieb:
> uint*int=uint.... hat man mir so beigebracht..

wenn ich mal eine Definition für uint zu Grunde lege. Ja, stimmt.
Solange die Bitzahlen gleich sind und unsigned und signed aufeinander 
treffen, gewinnt unsigned.

> es ist äquivalent zu
> double*int=int...

Äh. Nein.
Bei double und int gewinnt double

> also es wird der typ genommen der mit kleinerer bit
> anzahl und genauigkeit und vorzeichen..

Denk über diesen Satz nochmal nach. Ist der logisch, macht das Sinn, 
wenn man es so definiert?

von Volkmar D. (volkmar)


Lesenswert?

Fatih T. schrieb:
> gleich von anfang an 32 oda 64 bit variablen oder während der
> laufzeit richtiges casten... oder vllt doch mit float arbeiten?

Das hängt auch von Deinen Anforderungen an die Genauigkeit ab. Float hat 
zwar einen größeren Dynamikbereich, aber weniger Stellen als int32_t.

In jedem Fall solltest Du Dir die Reihenfolge der Berechnungen 
überlegen. Bei Verwendung der Ganzzahl-Arithmetik würde ich zum Beispiel 
die Division erst zum Schluß machen.

Beispiel:
1
1. teilergebnis1= (uint16_t*int16_t/(2^24))+(uint16_t/(2^10))
würde ich umformen in:
1
teilergbnis1 = ((uint16_t*int16_t) + (uint16_t*2^14) )/(2^24)

Wie werden Deine Teilergenisse weiter verwendet? Kannst Du Dir erlauben 
den Rest abzuschneiden? Oder solltest Du besser runden:
1
teilergbnis1 = ((uint16_t*int16_t) + (uint16_t*2^14) + (2^23) )/(2^24)

Anmerkung: Ich habe hier nur die prinzipielle Reihenfolge dargestellt, 
die Größen der Variablen habe ich nicht angefaßt, das ist ein anderes 
Thema.

von Fatih T. (alibaba06)


Lesenswert?

mir was fast sicher dass ich was falsch mache :)

oke die reihenfolge feststellen und den wertebereiche festlegen hört 
sich nicht schlecht an, aber wird es denn nicht schnell unübersichtlich?

lieber ne schwimmweste tragen als zu ertrigen :).. aber ich könnte doch 
nur eine 64bit variable als teilergebnis nehmen und nacheinander die 
variable mit den jeweiligen operator hineinschieben, also zb so:

uint64_t teilerg1;

uint16_t c1;
uint16_t c2;

teilerg1=(uint64_t)c1;
teilerg1*=(uint64_t)c2;
teilerg1+=(uint64_t)69;
teilerg1>>=24;    //teilerg1/=c^24;
.
.
.
usw?

von Fatih T. (alibaba06)


Lesenswert?

@volkmar

also zum Schluss muss rauskommen eine zahl wischen 300,00 bis 1100,00 .. 
die genauigkeit auf zweinachkommastellen muss ich einhalten... oda es 
geht auch wenn der wertebereich zwischen 30000 bis 110000 geht, dann 
weis ich ja dass das endergebnis um den faktor 100 zu groß ist... aba 
des ist dann kein problem mehr beim weiteren programm verlauf.

von Karl H. (kbuchegg)


Lesenswert?

Fatih T. schrieb:

> uint64_t teilerg1;
>
> uint16_t c1;
> uint16_t c2;
>
> teilerg1=(uint64_t)c1;
> teilerg1*=(uint64_t)c2;
> teilerg1+=(uint64_t)69;

wo kommen jetzt schon wieder die 69 her?

> teilerg1>>=24;    //teilerg1/=c^24;

Ich hab ja nicht gesagt, dass du dein Programm so umformulieren musst.
Du musst dir nur darüber klar werden, dass die Operationen 'im Prinzip' 
in dieser Reihenfolge ablaufen.

(Einschub: Du bist dir darüber im Klaren, dass ^ in C NICHT die 
Exponentenoperation ist? 2^24 ist nicht "2 hoch 24"!)

Das ist dein Ausdruck

teilergebnis1= (c1*c2/(1<<24))+(uint16_t/(1<<10))

Was wird gerechnet

   c1       ist ein uint16_t
   c2       ist ein uint16_t

also wird 16*16 Bit multiplizert und das Ergebnis ist wieder ein 
uint16_t.
Welchen Wert kann c1 maximal annehmen? Da du nichts dazu sagst, nehme 
ich mal was an: 2500
Welchen Wert kann c2 maximal annehmen? Du hast wieder nichts gesagt, 
also erfinde ich wieder: 5000
2500 * 5000 macht am Taschenrechner 12500000. Zu groß für uint16_t.
Also muss man den Compiler zwingen, die Multiplikation in 32 Bit zu 
machen: -> Eines der Argument hochcasten

    (uint32_t)c1 * c2

jetzt wird 32 * 16 Bit multiplizert. Dazu muss der Compiler den andern 
Operanden ebenfalls auf 32 Bit bringen und das Ergebnis ist wird ein 
uint32_t sein. Das arithmetische Ergebnis, 12500000, passt in einen 
uint32_t.
Der Teil ist also erledigt, da passiert nichts mehr.

Wie gehts weiter?

((uint32_t)c1 * c2 / (1<<24)

Das uint32_t Ergebnis wird durch 1<<24 dividiert. Soweit ok. Da kann 
nichts passieren. Ausser das ein paar Stellen wegfallen. Immerhin so 
viele, dass von den 12500000 nichts mehr übrig bleibt :-)

Dann wird
   c4 / (1<<10)

gerechnet. c4 ist wieder ein uint16_t. Den durch 1<<10 zu dividieren 
gibt wieder keinen Overflow

damit bleibt

   erster_Teilergebnis + zweites_Teilergebnis

der erste Teil war ein uint32_t und wir wissen, der kann nicht größer 
als x sein, das zweite Teilergebnis ist ein uint16_t und kann nicht 
größer als y sein. (Ich muss hier x und y schreiben, weil du nichts zu 
den Zahlen gesagt hast).

uint32_t + uint16_t  wird daher als uint32_t gerechnet. Die große Frage 
ist jetzt: Wird dieses Ergebnis überlaufen? Setz deine maximalen Zahlen 
ein und finde es raus.

Und so gehst du deine Berechnungen durch. Und wenn du fertig bist, hast 
du die Stellen identifiziert an denen Überläufe passieren können und wo 
du hochcasten musst.

von Fatih T. (alibaba06)


Lesenswert?

@karl heinz

die 69 hab ich jetzt mal erfunden um die art und weise nur zu 
verdeutlichen was ich mit "nacheinander mit operator hineinschieben" 
meine.

alle unsigned Variablen können werte von 0 bis 2^16 und alle signed 
Variablen von -2^15 bis +2^15.. des macht die ganze sache ja so böse..

ja ich weis dass man in C nicht 2^x schreiben kann :D ..

also da ich so wenig wie möglich von der genauigkeit abgeben darf mache 
ich es warscheinlich so wie der Volkmar es gesagt hat..

1. ich erweitere alle brüche so dass kein bruch mehr übrig bleibt
2. führe die multiplikationen und die additionen durch
3. teile das zwischenergebnis durch die 2^x mit der ich zuvor alle 
brüche
   erweitert hab
4. und mach zum Schluss noch vorzeichenkorrektur

bin mal gespannt ob des hinhaut jetzt endlich mal

von Fatih T. (alibaba06)


Lesenswert?

nachtrag:

> teilergebnis1= (c1*c2/(1<<24))+(uint16_t/(1<<10));
  hier muss nach rechts geschoben werden, da geteilt wird

also richtig ises so:

  teilergebnis1= (c1*c2/(1>>24))+(uint16_t/(1>>10));

von Karl H. (kbuchegg)


Lesenswert?

Fatih T. schrieb:
> nachtrag:
>
>> teilergebnis1= (c1*c2/(1<<24))+(uint16_t/(1<<10));
>   hier muss nach rechts geschoben werden, da geteilt wird
>
> also richtig ises so:
>
>   teilergebnis1= (c1*c2/(1>>24))+(uint16_t/(1>>10));

No. 1>>24  ergibt 0.


richtig wäre

    (c1*c2)>>24

und das alles wäre so viel einfacher, wenn du konkreten Code zeigen 
würdest.

von Fatih T. (alibaba06)


Lesenswert?

ja es wäre sehr viel einfacher wenn ich mein code reinstelle da hast du 
recht, aba ich bin nicht sicher ob ich es von meiner firma aus her 
reinstellen darf. Das risiko, deswegen gefeuert zu werden geh ich lieber 
nicht ein.

nein auch nicht ganz richtig ^^

ganz richtig:  ((uint32_t)c1*(uint32_t))c2>>24  :D

von Fatih T. (alibaba06)


Lesenswert?

wieder falsch...

also jetzt nochmal!  ((uint32_t)c1*(uint32_t)c2)>>24   die Kalmmer war 
falsch

von Karl H. (kbuchegg)


Lesenswert?

Fatih T. schrieb:
> ja es wäre sehr viel einfacher wenn ich mein code reinstelle da hast du
> recht, aba ich bin nicht sicher ob ich es von meiner firma aus her
> reinstellen darf. Das risiko, deswegen gefeuert zu werden geh ich lieber
> nicht ein.

2 Zeilen Berechnung?
Entschuldige: Aber das sind keine Firmengeheimnisse. Demnächst darfst du 
hier nicht mehr posten weil du nicht weißt ob du den Buchstaben 'M' 
benutzen darfst.


> nein auch nicht ganz richtig ^^

Ich tipp mir da jetzt nicht mehr die Finger wund. Du weiß, dass 
mindestens einer der Teilnehmer ein uint32_t sein muss. Also schreib ich 
da nicht mehr, wenn wir sowieso nicht über konkreten Code reden.

von Volkmar D. (volkmar)


Lesenswert?

Fatih T. schrieb:
> also zum Schluss muss rauskommen eine zahl wischen 300,00 bis 1100,00 ..
> die genauigkeit auf zweinachkommastellen muss ich einhalten... oda es
> geht auch wenn der wertebereich zwischen 30000 bis 110000 geht,

Wenn Du sowieso ein Ergebnis mit 2 Nachkommastellen hast, dann kommst Du 
mit uint16_t für die Teilergebnisse eh nicht hin. Also entweder mit 
float oder wenn Du den Faktor 100 (oder eventuell auch 128 wegen der 
Teiler) verwendest mit uint32_t.

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.