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
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...
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?
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.
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?
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:
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?
@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.
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.
@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
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));
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.
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
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.
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.