Forum: Mikrocontroller und Digitale Elektronik Division eines Floatwertes mit sich selbst ergibt nicht 1


von Johannes (menschenskind)


Lesenswert?

Hallo,

Im Debugsimulator im AtmelStudio habe ich festgestellt, dass bei einer 
Berechnung anstatt 255 leider nur 254 rauskommt.
Unter diesem Link (https://ideone.com/iXZIJD) habe ich die Funktion mal 
ausprobiert, da funktioniert es ohne Probleme (Result2), aber nicht bei 
mir im ATTiny85.

Wisst ihr woran das liegen könnte?

Ich hatte mir im Debugsimulator mal die Nachkommastellen anschauen 
wollen, aber da steht dann im Watchfenster immer "optimized away" wenn 
ich "result" als float definiere. Als int wird es aber angezeigt. Was 
mach ich da noch falsch?

von Karl M. (Gast)


Lesenswert?

Nun mit 32 bit float rechnet man immer nur näherungsweise..
Und muss sich über den Zahlenbereich Gedanken machen.

von Hans Blaubein (Gast)


Lesenswert?

float macht doch keine genaue Berechnung oder je nach Wert sind die 
Schritte die dargestellt werden können ja unterschiedlich gross. Hängt 
vielleicht einfach  mit deiner float berechnungs Software zusammen oder 
hast du eine FPU?

von Joe F. (easylife)


Lesenswert?

Bei der Konvertierung von float auf int entsteht ein Rundungsfehler.
Aus 1.999 wird 1
Vielleicht hilft es vor dem casten noch 0.5 dazu zu addieren.

von Wauwau (Gast)


Lesenswert?

habe das ausprobiert:
  float a = 987.123;
  float b = 987.123;
  float c = a / b;
  printf("c = %f\n", c);

es kommt 1 raus.

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


Lesenswert?

Wauwau schrieb:
> habe das ausprobiert:
Womit?

Johannes H. schrieb:
> Wisst ihr woran das liegen könnte?
Daran, dass das eben nicht die gleichen Zahlen sind. Zeig doch mal 
deinen Code mit der Division und wie du da auf die gleichen Werte 
kommst.

: Bearbeitet durch Moderator
von Wauwau (Gast)


Lesenswert?

gcc auf dem PC, der hat eine FPU falls das wichtig ist.

von Der FLoater (Gast)


Lesenswert?

1
int  pwmBaseValue = 255,pwmIndex = 15;
2
int result = 0;
3
 
4
result = pwmBaseValue * (pow(pwmIndex, EXPONENT));
5
result /= pow(INDEX_MAX, EXPONENT);

"result" ist vom Typ Integer, damit wird das Ergebnis der Multiplikation 
implizit in "int" gecasted, so dass dort der Wert 33381 steht. Das 
Gleiche nach der anschließenden Division, zunächst ergibt sich hier 
(33381 / 130.9 = 254.9). Dieses Ergebnis wird aber wiederum implizit in 
Integer gecasted, daher ergibt sich 254.

von Johannes (menschenskind)


Lesenswert?

Hallo Karl,

Ja, aber selbst wenn nur näherungsweise gerechnet wird, sollte doch 
"Wert/Wert = 1" sein, oder?

@Hans Ich hab den ATTiny85, der hat keine FPU.

Warum funktioniert es denn aber in dem angehängten Link?

von Karl M. (Gast)


Lesenswert?

Wauwau schrieb:
> habe das ausprobiert:
>   float a = 987.123;
>   float b = 987.123;
>   float c = a / b;
>   printf("c = %f\n", c);
>
> es kommt 1 raus.

Nöö, das ist nicht sein Beispiel:
1
result = pwmBaseValue * (pow(pwmIndex, EXPONENT));
Es wird mit int16_t gerechnet und auch abgeschnitten!

von Wauwau (Gast)


Lesenswert?

genauer gesagt kommt c = 1.000000 raus.

von Wauwau (Gast)


Lesenswert?

ne das steht in dem Betreff:

 Division eines Floatwertes mit sich selbst ergibt nicht 1

von Johannes (menschenskind)


Lesenswert?

@Lothar & Karl:

In dem Link unter "Result2", wie im Text oben schon beschrieben.
Beides mal ist es ein int-Wert von "15"

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Johannes H. schrieb:
> Division eines Floatwertes mit sich selbst ergibt nicht 1.
> Wisst ihr woran das liegen könnte?

Du verwendest float :-)

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


Lesenswert?

Johannes H. schrieb:
> Ja, aber selbst wenn nur näherungsweise gerechnet wird, sollte doch
> "Wert/Wert = 1" sein, oder?
In der geposteten Formel ist nirgends Wert/Wert zu sehen.

Johannes H. schrieb:
> Warum funktioniert es denn aber in dem angehängten Link?
Weil dort zur Vereinfachung gleich mit 64 Bit breiten Floats gerechnet 
wird. Hier mal ein paar Worte zur Gleitkommadarstellung: 
http://www2.informatik.uni-halle.de/lehre/c/c623.html

Johannes H. schrieb:
> Beides mal ist es ein int-Wert von "15"
Aber die Berechnung, die.mit dieser 15 durchgeführt wird, wird auf 2 
verschiedenen Plattformen durchgeführt. Die Konstante wird auf dem PC 
berechnet,  die Variable auf dem uC. Und natürlich kommt da bei der 
gleichen Rechnung was anderes raus. Siehe den Link vom letzten 
Abschnitt.

Fazit: wenn man etwas genau haben möchte, dann nimmt man Integer.

: Bearbeitet durch Moderator
von Johannes (menschenskind)


Lesenswert?

@Lothar
Na das war doch sinnbildlich gemeint. Ich hätte auch schreiben können: 
4/4 = 1

> wird auf 2 verschiedenen Plattformen durchgeführt.

Das hast Du misverstanden. Das Programm habe ich, als es auf dem µC 
nicht funktionierte, im Debugsimulator laufen lassen um  zu schauen 
woran es liegt.

Wie soll ich denn integer verwenden, wenn die pow-Funktion einen 
Fließkommawert zurückgibt?

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


Lesenswert?

Johannes H. schrieb:
> Wie soll ich denn integer verwenden, wenn die pow-Funktion einen
> Fließkommawert zurückgibt?
Das braucht ein wenig Nachdenken. Und evtl eine pow-Funktion, die auch 
mit Integern kann...

Seis drum.
Die beiden Berechnungen in dem verlinkten ideone-Codeschnipsel sind 
natürlich nicht gleich, weil bei der ersten Rechnung ein Zwischenschritt 
über einen Integer gemacht und dabei alle Nachkommastellen abgeschnitten 
werden.

Probiers doch mal so:
1
...
2
const float EXPONENT = 1.8;
3
const int INDEX_MAX = 15;
4
int main(void) {
5
 
6
int  pwmBaseValue = 255,pwmIndex = 15;
7
int result = 0;
8
float resultf;
9
 
10
resultf = pwmBaseValue * (pow(pwmIndex, EXPONENT));// / pow(INDEX_MAX, EXPONENT));
11
result = resultf / pow(INDEX_MAX, EXPONENT);
12
printf("Result1 = %i",result);
13
...
Dann wäre das Zwischenergebnis auch im ersten Fall ein nicht 
abgeschnittener Float.

: Bearbeitet durch Moderator
von PittyJ (Gast)


Lesenswert?

Vielleicht hilft ja etwas Theorie zu den floats

https://de.wikipedia.org/wiki/IEEE_754

von Johannes (menschenskind)


Lesenswert?

@Lothar
Ja aber da lande ich ja wieder bei meiner ursprünglichen Formel. Diesen 
Zwischenschritt hatte ich nur eingefügt, weil ich die Berechnung 
analysieren wollte.

Wie wäre der Ansatz, das "pow"-Ergebnis als float oder int zu casten? Da 
wird doch theoretisch ein Stück des Nachkommawertes abgeschnitten bzw. 
die Nachkommastellen ganz entfernt. Und dann müsste meine Berechnung ja 
funktionieren.

: Bearbeitet durch User
von Augen auf (Gast)


Lesenswert?

Johannes H. schrieb:
> Wie wäre der Ansatz, das "pow"-Ergebnis als float oder int zu casten.

Nein, nicht casten, sondern die richtige Umrechnung dazu benutzen. In 
dem Fall float/double to Integer sind das IMMER Round Funktionen.

https://www.gnu.org/software/libc/manual/html_node/Rounding-Functions.html

von Amateur (Gast)


Lesenswert?

>Wie wäre der Ansatz, das "pow"-Ergebnis als float oder int zu casten? Da
Also eine nachträgliche Manipulation der Zahlen - so lange bis das 
gewünschte Ergebnis herauskommt - ist bestimmt das Gelbe vom Ei;-)

von Johannes (menschenskind)


Lesenswert?

@Augen auf
Ok, daran hatte ich gar nicht gedacht :)

@Amateur
Ja, denn es ist nur wichtig, dass bei der Division der beiden 
pow-Funktionen (wenn sie die selben Werte bekommen) eine glatte 1 
rauskommt, bzw. 255 im Endresultat.

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


Lesenswert?

Johannes H. schrieb:
> Ja, denn es ist nur wichtig, dass bei der Division der beiden
> pow-Funktionen (wenn sie die selben Werte bekommen) eine glatte 1
> rauskommt, bzw. 255 im Endresultat.
Warum?
Denn wenn es nur ein klitzekleiner "Mückenschiss" weniger ist, dann 
kommt auch nicht mehr 255 heraus. Du betrachtest also eigentlich einen 
ausserordentlich seltenen Fall.

Und wenn die 255 kein sehr seltener Fall sein soll, dann sollte die 
Berechnung des Wertes 255 nicht so dermaßen knapp auf der Kippe stehen. 
Dann könntest du vor dem Abschneiden der Nachkommastellen auch einfach 
aufrunden
result = resultf / pow(INDEX_MAX, EXPONENT) + 0.5;
Siehe https://ideone.com/i0cqkt

von Axel S. (a-za-z0-9)


Lesenswert?

Johannes H. schrieb:

>> wird auf 2 verschiedenen Plattformen durchgeführt.
>
> Das hast Du misverstanden.

Nein. Du hast das immer noch nicht verstanden.

> Das Programm habe ich, als es auf dem µC
> nicht funktionierte, im Debugsimulator laufen lassen um  zu schauen
> woran es liegt.

Das ist gar nicht der Punkt. Der erste Aufruf von pow() wird auf dem µC 
zur Laufzeit berechnet. Der zweite Aufruf von pow() ist aber konstant 
und deswegen berechnet den der Compiler, wenn er dein Programm 
compiliert. Im µC-Programm steht an dieser Stelle eine vorberechnete 
float Konstante.

Dein grundsätzlicher Fehler besteht aber darin, daß du float Zahlen 
direkt nach int castest. Das ist immer falsch. Der korrekte Weg 
besteht darin, mit round() zu konvertieren. Oder wenn du schon weißt, 
das die float Zahl "eigentlich" ganzzahlig sein sollte, dann kannst du 
auch 0.5 addieren und dann nach int casten.

von Johannes (menschenskind)


Lesenswert?

Ok, das mit der berechneten Konstanten hab ich jetzt verstanden.

Die Variante mit "+ 0.5" funktioniert gut. Da spart man wahrscheinlich 
auch Rechenzeit und Programmspeicher als wenn man den Wert runden würde, 
oder?

von Patrick J. (ho-bit-hun-ter)


Lesenswert?

Hi

Durch das +0.5 gibst Du quasi diese Rundung vor.
Alles, was vorher x.5 (oder größer) war, ist danach (x+1).0-4
Da die Nachkommastelle bei INT (bzw. beim 'cast' zu int) abgeschnitten 
wird, ist so aufgerundet.
Bis zur x.4 bekommst Du nur x.9 raus, abgeschnitten verbleibt x und 
damit abgerundet.

Könnte mir vorstellen, daß Das etwas Rechenzeit einspart.

MfG

von Wolfgang (Gast)


Lesenswert?

Joe F. schrieb:
> Bei der Konvertierung von float auf int entsteht ein Rundungsfehler.
> Aus 1.999 wird 1

Seit wann rundet man durch das Abschneiden der Nachkommastellen?

von Joe F. (easylife)


Lesenswert?

Wolfgang schrieb:
> Joe F. schrieb:
>> Bei der Konvertierung von float auf int entsteht ein Rundungsfehler.
>> Aus 1.999 wird 1
>
> Seit wann rundet man durch das Abschneiden der Nachkommastellen?

Es wird ja beim Casten von float auf int eben nicht gerundet, sondern 
abgeschnitten.
1
float f = 1.99;
2
int i, j;
3
i = f;
4
j = (f + 0.5);
5
printf("i=%d j=%d\n", i, j);

ergibt:
1
i=1 j=2

daher sagte ich ja: 0.5 dazu addieren. Geht natürlich nur bei positiven 
Zahlen.
Universell wäre
1
j = (f >= 0) ? (f + 0.5) : (f - 0.5);

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Axel S. schrieb:
> Johannes H. schrieb:
>
>>> wird auf 2 verschiedenen Plattformen durchgeführt.
>>
>> Das hast Du misverstanden.
>
> Nein. Du hast das immer noch nicht verstanden.
>
>> Das Programm habe ich, als es auf dem µC
>> nicht funktionierte, im Debugsimulator laufen lassen um  zu schauen
>> woran es liegt.
>
> Das ist gar nicht der Punkt. Der erste Aufruf von pow() wird auf dem µC
> zur Laufzeit berechnet. Der zweite Aufruf von pow() ist aber konstant
> und deswegen berechnet den der Compiler, wenn er dein Programm
> compiliert. Im µC-Programm steht an dieser Stelle eine vorberechnete
> float Konstante.

Die auf dem Host berechnete Konstante wird aber nicht nach den Regeln 
des Host-Rechners berechnet, sondern gemäß dem float-Format des Targets.

GCC verwendet dazu u.a. MPFR.

Das grundlegende Missverständnis ist, dass bei vorgegebenen float-Werten 
und vorgegebener Plattform immer ein und dasselbe Ergebnis herauskommen 
müsse, was schlichweg nicht der Fall ist.  Das Ergebnis muss lediglich 
der Spezifikation (z.B. IEEE) entsprechen.

von Gu. F. (mitleser)


Lesenswert?

Wolfgang schrieb:
> Seit wann rundet man durch das Abschneiden der Nachkommastellen?

Seit man den Thread gelesen und das Problem verstanden hat.

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.