Seite 1 von 1

while-Schleife akzeptiert unter 1 nur den Wert 0.5

Verfasst: Di Feb 03, 2015 3:26 pm
von rbecher
Hallo, Leute.

Habe vor kurzer Zeit angefangen, mich in C einzulernen, da für mich Programmieren schon immer reizvoll war. Nun habe ich mich hier angemeldet, um Teil einer Community zu werden und euch mit meinen Fragen zu belästigen :).

Das habe ich gleich ein Problem, bei dem ihr mir vielleicht behilflich sein könnt. Doch zuerst der Code (es handelt sich um ein Übungsprogramm, um Schleifen und if-Anweisungen zu testen):

Code: Alles auswählen

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    //Getränkeautomat

    int drink;
    int menge;
    float preis;
    float geld;
    float zuZahlen;
    float change;
    char weiter;

    do        
    {
        system ("Cls");
        printf ("Get-A-Drink 2000\n\n\n");
        printf ("\n1 - Cola - 1.50 Euro\n");
        printf ("2 - Fanta - 1.30 Euro\n");
        printf ("3 - Sprite - 1.20 Euro\n\n");

        do         
        {
            printf ("Geben Sie die Nummer Ihres Wunschgetraenks ein: ");
            scanf ("%d", &drink);
            fflush(stdin);
            if (drink != 1 && drink != 2 && drink != 3)
            {
                printf ("\n\nIhre Eingabe war leider falsch. Bitte versuchen Sie es erneut.\n\n");
            }
        }
        while (drink != 1 && drink != 2 && drink != 3);         

        switch (drink)              
        {
            case 1:
                {
                    preis = 1.50;
                    printf ("\n\nSie haben sich fuer erfrischende Cola entschieden.\n");
                    break;
                }
            case 2:
                {
                    preis = 1.30;
                    printf ("\n\nSie haben sich fuer fruchtige Fanta entschieden.\n");
                    break;
                }
            case 3:
                {
                    preis = 1.20;
                    printf ("\n\nSie haben sich fuer spritzige Sprite entschieden.\n");
                    break;
                }
        }

        do
        {
            printf ("\n\nWieviele Flaschen moechten Sie kaufen (max. 5)?\n");
            scanf ("%d", &menge);
            if (menge > 5 || menge < 1)
            {
                printf ("\n\nIhre Eingabe war leider falsch. Bitte versuchen Sie es erneut.\n\n");
            }
        }
        while (menge > 5 || menge < 1);

        zuZahlen=preis*menge;

        do
        {
            printf ("\n\nEs fehlen noch %.2f Euro.\n\n", zuZahlen);
            printf ("Bitte werfen Sie ein Geldstueck ein: ");
            fflush(stdin);
            scanf ("%f", &geld);

            [b]while (geld != 2 && geld != 1 && geld != 0.50 && geld != 0.10 && geld != 0.20)               
            {
                printf ("\n\n\nDas Geldstueck wurde leider nicht erkannt.\n");
                printf ("\n*KLING* (Das Geldstueck liegt im Fach.)\n\n");
                system ("PAUSE");
                printf ("\n\nEs fehlen noch %.2f Euro.\n\n", zuZahlen);
                printf ("Bitte werfen Sie ein Geldstueck ein: ");
                fflush(stdin);
                scanf ("%f", &geld);

            }[/b]
            zuZahlen -= geld;
        }
        while (zuZahlen > 0);

        printf ("\n\nVielen Dank! Sie erhalten nun Ihre Auswahl!\n\n");
        system ("PAUSE");

        if (menge == 1)
        {
            printf ("\n\n*KDONK* (Das Getraenk liegt im Entnahmefach.)\n\n");
            system ("PAUSE");
        }
        else
        {
            printf ("\n\n*KDONKDONK* (Die Getraenke liegen im Entnahmefach.)\n\n");
            system ("PAUSE");
        }

        change=zuZahlen * -1;

        if (change != 0)
        {
            printf ("\n\nSie bekommen ausserdem %.2f Euro zurueck.\n\n", change);
            system ("PAUSE");
            printf ("\n\n*KLING* (Das Wechselgeld liegt im Fach.)\n\n");
            system ("PAUSE");
        }

        printf ("\n\nVielen Dank fuer Ihren Einkauf!\n\n");
        system ("PAUSE");

        do
        {
            printf ("\n\nMoechten Sie weitere Getraenke kaufen? j/n\n");
            fflush(stdin);
            scanf ("%c", &weiter);
        }
        while (weiter != 'j' && weiter != 'n');
    }
    while (weiter == 'j');

    printf ("\n\nAuf Wiedersehen!\n\n");

    return 0;
}
Bei dem fettgedruckten Teil handelt es sich um eine While-Schleife, die wahr ist, wenn man Münzen, die in Wirklichkeit nicht existieren, angibt. Das Problem ist: gibt man 0.2 oder 0.1 ein, aktiviert dies trotzdem die Schleife. Auch bei anderen Werten unter 1 (außer 0.5) ergibt sich dieser Fehler. Bei 0.5 und alles ab dem Wert 1 (sofern mit angegeben) ist die Schleife korrekterweise falsch. Ich habe auch probiert, die While-Schleife anders zu formulieren - z.b. while ( ! ( geld == 1|| geld == .5 usw.)) - aber das Ergebnis ist das gleiche.

Falls jemand Lust hat, seine Zeit für eine Antwort aufzubringen, würde ich mich sehr darüber freuen.

Vielen Dank im Voraus.

Viele Grüße
Rico

Re: while-Schleife akzeptiert unter 1 nur den Wert 0.5

Verfasst: Di Feb 03, 2015 4:27 pm
von cloidnerux
Hallo, Leute.
Hi und Willkommen im Forum :D
Bei dem fettgedruckten Teil handelt es sich um eine While-Schleife, die wahr ist, wenn man Münzen, die in Wirklichkeit nicht existieren, angibt. Das Problem ist: gibt man 0.2 oder 0.1 ein, aktiviert dies trotzdem die Schleife. Auch bei anderen Werten unter 1 (außer 0.5) ergibt sich dieser Fehler. Bei 0.5 und alles ab dem Wert 1 (sofern mit angegeben) ist die Schleife korrekterweise falsch. Ich habe auch probiert, die While-Schleife anders zu formulieren - z.b. while ( ! ( geld == 1|| geld == .5 usw.)) - aber das Ergebnis ist das gleiche.
Du wirst wrsl an die "floatingpoint-precision" geraten sein.
Das Problem ist, dass der Computer rationale Zahlen(Kommazahlen, float und double) nur näherungsweise berechnen kann.
Dies liegt auch an der Art und weise, wie die Zahlen gespeichert sind, nämlich Binär: http://de.wikipedia.org/wiki/IEEE_754
Zahlen die Potenzen von 2 sind, sind sehr einfach zu süeichern(2, 1, 0.5), andere zahlen werden genähert.
Und da kann es zu Problemen kommen.
Wenn "geld" nun mit
0.200000000000000000001 genähert wurde ist das zwar sehr knapp an 0.2, aber nicht genau gleich.

Deswegen vergleicht man floats und doubles meistens nicht direkt, sondern gibt ein Toleranzband an:

Code: Alles auswählen

|a - b| < c
Mit a und b als beliebige float/double Zahl, | | als den Betrag und c als obere Schranke für die Toleranz.

Alternativ und in deinem Fall sinnvoller ist es, mit Ganzzahlen zu arbeiten.
Dies geht recht einfach wenn du statt in Euros in cents Rechnest, also statt 2€ 200c schreibst. Damit Sparst du dir floatingpoint Berechnungen und die damit verbundene Ungenauigkeit.

Gruß cloidnerux.

Re: while-Schleife akzeptiert unter 1 nur den Wert 0.5

Verfasst: Di Feb 03, 2015 4:49 pm
von rbecher
Hey vielen Dank für die schnelle und vor allem kompetente Antwort :D .

Schon interessant, wie so ein Computer tickt. Saugeiles Thema.

Ich werde das dann gleich mal ausprobieren. Vielleicht nehme ich die Berechnung in Cent nur intern und gebe trotzdem in Euro aus. Wäre ja bei so Automaten auch in Euro-Beträgen. Bin gespannt, ob's klappt :) .

Viele Grüße
Rico

Re: while-Schleife akzeptiert unter 1 nur den Wert 0.5

Verfasst: Mi Feb 04, 2015 11:10 am
von Xin
rbecher hat geschrieben:Hey vielen Dank für die schnelle und vor allem kompetente Antwort :D .
Ein Computer kann den Wert 0.1 nicht darstellen. Das gilt in der Regel auch für ganzzahlige Vielfache von 0.1 wie 0.2 oder 0.05. Ein Computer rechnet binär und kann daher sehr genau mit 2 Multiplizieren oder durch 2 teilen, genauso wie der Mensch das mit 10 macht. Wir können daher 1/10 gut darstellen, der Computer kann es nur annähern, so wie wir die Zahl 1/3 im Zehnerzahlensystem nur annähern können. So sind 0.1, 0.2, 0.3, 0.4 angenäherte Zahlen, 0.5 aber wieder genau, da 1/2 genau 0.5 ist.

Du kannst entweder ein sogenanntes Epsilon einführen, wie cloidnerux vorschlägt, welches eine hinreichende Genauigkeit beschreibt (alles was hinreichend nah an 0.20 liegt ist dann als gleich 0.20 definiert) oder Du kannst intern mit Cent-Beträgen rechnen.

Wenn Du Euro-Beträge eingeben möchtest, solltest Du ein Epsilon von 0.1 Cent einführen, was den Betrag nicht verfälschen würde:

Code: Alles auswählen

double epsilon = 0.001;
double euro = 0.20001;
int cents = int( (euro + epsilon) * 100);
Dann hast Du in 'cent' den Wert 20 stehen, da bei der Umwandlung zu Ganzzahlen (Integers) die nachkommastellen abgeschnitten werden. Ist die Annäherung gerüngfügig kleiner als 0.20, z.b. 0.199999 wird der Betrag per Epsilon über die Schwelle von 0.20 gehievt: 0.209999, mal 100 ergibt 20.9999, die Nachkommastellen werden abgeschnitten: 20.

Entsprechend vergleichst Du auch die ganzzahligen Cent-Beträge und nicht die Eurobeträge.

Mehr zum Thema: Zahlendarstellung von Integers und Fließkommazahlen