Forum: Compiler & IDEs Rundungsfehler trotz cast?


von Martin (Gast)


Lesenswert?

Hallo Leute.

Ich habe ein Problem beim lösen einer (Geraden)Gleichung.

Sie ist vom typ y=kx+d

y folgendermaßen berechnet:

Ein Zähler "time" zählt von 0...99:
1
uint8_t result;
2
3
uint8_t x1=0;
4
uint8_t x2=100;
5
6
uint8_t y2=100;
7
uint8_t y1=0;
8
9
for (time=0; time<x2; time++) {
10
11
result = ( (y2-y1) * 10 * (int32_t)time) ) / ( (x2-x1) * (int32_t)10 ) )
12
13
}

Dabei ist das Ergebnis immer einer weniger. Das liegt sicher am Runden. 
Ich dachte, dass ich mit dem cast auf int32 alles abfangen könnte. 
Scheinbar ist dem aber nicht so. Wie kann man das Problem lösen?

von Lasse S. (cowz) Benutzerseite


Lesenswert?

Das hast du doch sicherlich aus irgendeinem Code rauskopiert, oder?

Zumindest ist das rein syntaktisch falsch, eine Klammer wird nie 
geöffnet.

Außerdem ist die Schleife so recht sinnlos, man könnte auch einfach 
time=99 setzen und die Berechnung (die dann nur noch aus Konstanten 
besteht) nur einmal berechnen lassen.

Bei mir im Taschenrechner kommt da 99 raus, was kommt denn bei deinem 
Code raus?

Gruß
Lasse

PS: Zumindest die 10 könnte man in jedem Fall kürzen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ganzzahl-Division rundet eben zu 0 hin. Um a/b zu bekommen hilft also 
u.U (a+a/2)/b zu berechnen. Das Erweitern mit 10 bringt die wie bereits 
geschrieben überhaupt nix.

Falls du wirklich nacheinander alle Punkte auf der Strecke willst -- was 
durch den Variablenname 'time' zumindest nahegelegt wird -- ist 
Bresenham dein Freund.

   http://de.wikipedia.org/wiki/Bresenham-Algorithmus

Der braucht auch keine Division bzw. berechnet diese implizit, indem er 
Zähler und Nenner mitführt anstatt immer einen Quotienten auszurechnen.

von Martin (Gast)


Lesenswert?

Ich möchte im Prinzip auf einer Gerade alle Punkte berechnen können- den 
ersten, den letzten und alle dazwischen.

Die Punkte werden für eine Positionierung im weitesten Sinne verwendet.

von nicht "Gast" (Gast)


Lesenswert?

Was spricht dagegen, alle weiteren Punkte aus dem Ursprung (erster 
Punkt) und der Steigung zu berechnen?

Gast

von Rolf Magnus (Gast)


Lesenswert?

nicht "Gast" schrieb:
> Was spricht dagegen, alle weiteren Punkte aus dem Ursprung (erster
> Punkt) und der Steigung zu berechnen?

Daß die Steigung auch mehr oder weniger weit abweicht und man dann 
sonstwo rauskommt.

von Karl H. (kbuchegg)


Lesenswert?

Martin schrieb:

> Das liegt sicher am Runden.

Eigentlich nicht.
Es liegt daran, dass es sich um eine Ganzzahldivision handelt und dort 
wrden weder Nachkommastellen berechnet, noch wird gerundet.

von Martin (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Eigentlich nicht.
>
> Es liegt daran, dass es sich um eine Ganzzahldivision handelt und dort
>
> wrden weder Nachkommastellen berechnet, noch wird gerundet.

Und wie lös ich das Problem?

Mein "Trick" wäre ja, beim letzten Wert der for schleife einfach zu 
sagen
1
 result = y2
 aber das ist schon komisch...

von Rolf Magnus (Gast)


Lesenswert?


von adfix (Gast)


Lesenswert?

>Mein "Trick" wäre ja, beim letzten Wert der for schleife einfach zu sagen
> result = y2
> aber das ist schon komisch...

Ich meine, langsam Deine Frage zu verstehen.
Du hast eine Gerade, die von Punkt X:0 bis X:100 geht. Also über 101 
Punkte.
Zählst aber nur von 0..99, also über 100 Punkte.

Dann ist alles richtig. Kein Rundungsfehler.
Dann musst Du nur:
1
uint8_t x2=99;

setzen.

von Karl H. (kbuchegg)


Lesenswert?

Martin schrieb:

>> wrden weder Nachkommastellen berechnet, noch wird gerundet.
>
> Und wie lös ich das Problem?

Indem zu zb selber für eine Rundung sorgst.

Nach dem Muster:
a / b   als ganzzahlige Division gerundet wird als  (a + b/2) / b 
gerechnet.
1
  uint32_t deltaX = x2 - x1;
2
  uint32_t deltaY = y2 - y1;
3
4
  for( time = 0; time < x2; time++ ) {
5
    result = ( deltaY * time + deltaX / 2 ) / deltaX;

Man kann sich das so vorstellen:
angenommen du hättest eine Gleitkommadivision

    c = a / b

dann hat c Nachkommastellen. Weist du c einem int zu

    int d = c

dann werden Nachkommastellen einfach abgeschnitten. Aus 1.9 wird so 1
Um das zu vermeiden musst du runden

     int d = c + 0.5

Die Rundungskorrektur von 0.5 sorgt jetzt dafür, dass das Ergebnis 
soweit erhöht wird, dass alle Werte > x.5 über die nächste ganze Zahl 
kommen und wenn dann die Nachkommastellen einfach weggeworfen werden, 
kommt x+1 raus.
Aus 1.6 wird so 2 und nicht 1

Und jetzt wird rückwärts eingesetzt

    int d = a / b + 0.5

wir wollen die Kommazahl loswerden.

     int d = a / b + 1 / 2

auf gemeinsamen Nenner gebracht

     int d = a / b + b / 2b

     int d = a / b + ( b/2 ) / b

     int d = ( a + b/2 ) / b

voila. Du Rundungskorrektur wurde soweit nach vor gezogen, dass alles 
ganzzahlig ist. Auch die Division muss jetzt nicht mehr in Gleitkomma 
erfolgen sondern kann ganzzahlig gemacht werden.

Soweit zum runden. Allerdings: mit deinen originalen Zahlen, ist das 
Problem nicht das runden :-)
adfix hat, denk ich, auch recht. Aber sein Fix ist falsch.
Wenn du den Endpunkt erreichen willst, dann muss es lauten

   for( time = 0; time <= x2; time++ ) {

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.