Problem beim rechnen mit float variablen



  • Hallo Forum,

    ich habe eine Hausarbeit in der man einen betrag kleiner als 5 Euro in möglichst wenigen einzelnen Münzen ausgeben muss und dazu ein programm geschrieben alla:

    if(betrag>=4)
    {zweieuro=2;
    betrag=betrag-4.00;}
    if(betrag>=3)
    {zweieuro=1;
    eineuro=1;
    betrag=betrag-3.00;}
    if(betrag>=2)
    {zweieuro=1;
    betrag=betrag-2.00;}
    ...... usw bis ein cent herunter

    Es funktioniert auch bei vielen Werten richtig, nur bei z.b. 1.15 Euro berechnet der Compiler den Wert falsch, denn wenn er bei 1.15 -1.00 macht, hat er als neuen Wert in der Variablen "betrag" 0.149999999999 irgendwas und dann ist der rest verfälscht.

    Könntet ihr mir vielleicht sagen, wie ich das umgehen kann? Kann ich die nachkommastellen begrenzen oder einen anderen Datentypen verwenden?

    Vielen Dank für eure Zeit!



  • Das ist der typische Fehler: float/double sind nicht für diskrete Größen (wie Geld) geeignet, sondern für Kontinuums. Das liegt daran, dass sie nicht jede Zahl exakt darstellen können (das ist äquivalent zu 1/3 im Dezimalsystem).
    Nimm einen integer Typen und rechne in Cents.



  • Nathan schrieb:

    Das ist der typische Fehler: float/double sind nicht für diskrete Größen (wie Geld) geeignet, sondern für Kontinuums.

    Floating point formate sind diskrete Größen und keineswegs für Kontinua geeignet. Ein 64 bit Datentyp nimmt max. 2^64 Werte an. Genaugenommen sind alle Datentypen mit konstant begrenzter Bitlänge nicht nur diskret, sondern nur zur Darstellung endlich vieler Zahlen fähig.

    Ein Vorteil von floating point ist, daß man einen größeren Zahlenbereich überstreichen (nicht überdecken) kann wegen des Exponenten, der nur logarithmisch viele Bits braucht.

    Um das Kontinuum darzustellen, bräuchte man einen Datentyp, der Objekte wie z.B.

    exp(1)^PI + PI^exp(1) * sqrt(17*PI)
    

    und seine (überabzählbar vielen) Freunde exakt darstellt. Das kann kein digitaler Computer, und wie wir dank Shannon wissen, gibt es ähnliche Begrenzungen auch in der Analogtechnik.


  • Mod

    großbuchstaben schrieb:

    Nathan schrieb:

    Das ist der typische Fehler: float/double sind nicht für diskrete Größen (wie Geld) geeignet, sondern für Kontinuums.

    Floating point formate sind diskrete Größen und keineswegs für Kontinua geeignet. Ein 64 bit Datentyp nimmt max. 2^64 Werte an. Genaugenommen sind alle Datentypen mit konstant begrenzter Bitlänge nicht nur diskret, sondern nur zur Darstellung endlich vieler Zahlen fähig.

    Ein Vorteil von floating point ist, daß man einen größeren Zahlenbereich überstreichen (nicht überdecken) kann wegen des Exponenten, der nur logarithmisch viele Bits braucht.

    Vollkommener Blödsinn. Klar ist jeder endliche Datentyp auf einem Digitalcomputer immer irgendwie diskret. No shit, Sherlock. Aber Fließkommazahlen dienen dazu, das Kontinuum möglichst gut zu modellieren, indem sie die Eigenschaften nachbilden, die meisten wichtig ist*. Und dieses Modell bildet eben nicht das nach, was beim Rechnen mit diskreten Größen oft wichtig und gewünscht ist.

    Wenn du wirklich glaubst, dass es bei Fließkommazahlen allein um einen größeren Wertebereich geht, dann hast du nicht verstanden, wozu sie da sind. Die wohl wichtigste Eigenschaft ist nämlich, dass sie viele Größenordnungen abdecken können, dabei aber immer das gleiche, akzeptable Maß an Genauigkeit bieten. Der Integer bietet schließlich auch verschiedene Größenordnungen, aber diese ist fest gegeben, während man die Programmlogik erstellt. Beim Problem des Threaderstellers bietet es sich beispielsweise an, dass man in Cent oder Zehntelcent rechnet.

    *: Deswegen ist es auch wichtig, genau zu wissen, wie Fließkommazahlen funktionieren, wenn man ernsthaft damit arbeitet. Nicht, dass man irgendwelche Eigenschaften annimmt, die gar nicht gegeben sind.
    What every computer scientist should know about floating-point arithmetic


  • Mod

    Der Eigenschaft, auf die es ankommt, ist Exaktheit, und dabei geht es nicht um die Darstellung von Werten sondern um Operationen mit diesen.
    Exaktheit bedeutet, dass das korrekte Ergebnis einer Operation, sofern es im zulässigen Wertebereich liegt, eindeutig in diesem Typen dargestellt werden kann.
    Integertypen sind exakt, Gleitkommatypen sind es nicht.


  • Mod

    Exaktheit bedeutet, dass das korrekte Ergebnis einer Operation, sofern es im zulässigen Wertebereich liegt, eindeutig in diesem Typen dargestellt werden kann.

    Demnach wären Fließkommazahlen exakt. Der Wertebereich ist doch nicht etwa Intervall der reellen/rationalen Zahlen, sondern die Menge der Zahlen die vom entsprechenden Fließkomma-Typ dargestellt werden können.



  • Die Kontinuumshypothese war mir schon beim ersten Auftauchen mißfallen und ich war gar nicht erfreut, daß sie anscheinend ansteckend war.

    Fließkommazahlen sind vielleicht besser für Geld, wenn man nur 32 Bit hat und Überläufe vorkommen könnten. Ab 40 Mio € wirds nämlich bei Ganzzahlen voll doof und in der Nähe gibts ne Zahl, aber der, wenn ich noch eine Cola dazukaufte, werden schlagartig *alle* meine Schulden erlassen. Fließkommazahlen sagen hingegen, daß ab dann nur weitere Colas erlassen werden, die fetten Brummer wie die Überziehungszinsen und Immobilien aber ganz ordentlich erfasst werden.

    Beim vorliegenden Programm sind Ganzzahlen irgendwie viel viel passender. Naja, aber nur, weil man schon einplant, daß sich die Fahrkartenpreise ruck zuck bis 40 Mio € entwickeln könnten und es stets gegenüber dem Endkunden auf jeden Cent ankommt. Eigentlich gibt die Aufgabenstellung das nicht wirklich her. Wenn man ein wenig schummelt und vor Berechnungslauf einen halben Cent dazuaddiert, geht die Welt nicht unter. Man muss nur eine Zeile dazubauen und ist ferig. Leider zu dicken potenziellen Softwareweiterentwicklungskosten: Man kann nie wieder mit == auf den Betrag zugreifen, weil Fließkommazahlen sogesehen immer unexakt sind, sobald man einmal einen Centbetrag außer 50, 25, 75 oder 0 benutzt, also zu erwartend immer.


  • Mod

    Arcoth schrieb:

    Exaktheit bedeutet, dass das korrekte Ergebnis einer Operation, sofern es im zulässigen Wertebereich liegt, eindeutig in diesem Typen dargestellt werden kann.

    Demnach wären Fließkommazahlen exakt. Der Wertebereich ist doch nicht etwa Intervall der reellen/rationalen Zahlen, sondern die Menge der Zahlen die vom entsprechenden Fließkomma-Typ dargestellt werden können.

    Dann bring eine bessere Definition.



  • SeppJ schrieb:

    großbuchstaben schrieb:

    Nathan schrieb:

    Das ist der typische Fehler: float/double sind nicht für diskrete Größen (wie Geld) geeignet, sondern für Kontinuums.

    Floating point formate sind diskrete Größen und keineswegs für Kontinua geeignet. [...]
    Ein Vorteil von floating point ist, daß man einen größeren Zahlenbereich überstreichen (nicht überdecken) kann wegen des Exponenten, der nur logarithmisch viele Bits braucht.

    Vollkommener Blödsinn.

    LOL 👍

    SeppJ schrieb:

    Wenn du wirklich glaubst, dass [...]

    tldnr;

    SeppJ schrieb:

    What every computer scientist should know about floating-point arithmetic

    eines meiner Lieblingspaper - viel Spaß beim Schmökern



  • Hallo, danke für die vielen Antworten, ich habe nun versucht das ganze in integer umzuwandeln und in Cent zu rechnen, allerdings klappt auch das bei einigen Werten nicht, weil der eingelesene Wert mit scanf("%f",&betrag) schon falsch ist:

    wenn ich zum Beispiel 4.68 eingebe, bekommt die Variable betrag den Wert 4.679999999

    also müsste ich wohl darauf verzichten, float als einlese Datentyp zu verwenden, wie kann man ansonsten aber eine kommazahl einlesen? (Wir müssen es mit scanf machen, cin cout ist nicht erlaubt ....)

    Danke im Voraus!



  • eventuell gibts ja ausser %f noch etwas, was besser passt:

    http://www.cplusplus.com/reference/cstdio/scanf/



  • Pures Fett schrieb:

    [...] allerdings klappt auch das bei einigen Werten nicht, weil der eingelesene Wert mit scanf("%f",&betrag) schon falsch ist: [...]

    #include <iostream>
    
    int main()
    {
    	unsigned  euro;
    	unsigned  cent;
    	char      delim;
    
    	while( !( std::cin >> euro >> delim >> cent ) ||
    	       !( delim == '.' || delim == ',' ) ||
    		   euro > 5 ||
    		   ( euro == 5 && cent ) || 
    		   cent > 99 )
    
    	{
    		std::cerr << "Input error! Please try again!\n\n";
    	}
    
    	unsigned amount{ euro * 100 + cent };
    
    	std::cout << "in cents: " << amount;
    }
    

    ...

    ~// scheisz tags!!~



  • __someone schrieb:

    eventuell gibts ja ausser %f noch etwas, was besser passt:

    http://www.cplusplus.com/reference/cstdio/scanf/

    hmm also die Tabelle habe ich auch schon gesehen aber so wie ich das verstehe kann man nur mit %f %e %g (welche ja gleich zu seien scheinen) eine dezimalzahl MIT komma einlesen, habe auch versucht ob man scanf("%.2f",&betrag") machen kann, damit er sich dann nicht verrechnet bei nur 2 Stellen aber das funktioniert nicht.

    Also eine andere Möglichkeit wäre, den eingelesenen float Wert gleich in vor und nachkomma aufzuteilen, aber der Befehel dazu ist mir nicht geläufig (ich habe was von ceil(variable)) gelesen, aber auch das würde nicht funktionieren, wenn er direkt den Wert der Variable falsch einließt, wie ich schrieb aus 4.68 wird 4.67999999 😕

    @swordfish:

    ich glaube dein programm gibt nur cent und euro aus aber nicht die einzelnen Münzen also in der aufteilung wieviel 2euro münzen, 1 euro münzen etc das ist jedoch meine Aufgabenstellung. Leider dürfen wir wie geschrieben auch kein cin cout machen sondern müssten printf und scanf verwenden >_>

    Vielleicht müsste man gleich vor und nachkomma trennen in seperate Variablen einlesen, dann auf 2 stellen aufrunden und dann wieder beide zusammenrechnen als Cent betrag. Hätte jemand einen Vorschlag wie ich das machen könnte?

    int vorkomma= ceil(betrag); < müsste dann hier nur noch aufrunden
    int nachkomma= betrag- vorkomma; < und hier

    hilfsvariable=vorkomma+nachkomma;



  • Pures Fett schrieb:

    Hallo, danke für die vielen Antworten, ich habe nun versucht das ganze in integer umzuwandeln und in Cent zu rechnen, allerdings klappt auch das bei einigen Werten nicht, weil der eingelesene Wert mit scanf("%f",&betrag) schon falsch ist:

    wenn ich zum Beispiel 4.68 eingebe, bekommt die Variable betrag den Wert 4.679999999

    also müsste ich wohl darauf verzichten, float als einlese Datentyp zu verwenden, wie kann man ansonsten aber eine kommazahl einlesen? (Wir müssen es mit scanf machen, cin cout ist nicht erlaubt ....)

    Danke im Voraus!

    Das Problem ist, dass die Dezimalzahl 4.68 als float nicht exakt darstellbar ist. Der Computer hilft sich damit, dass er die nächst mögliche Fliesskommazahl speichert. Und dabei kommt es eben zu einen kleinen Fehler. Wenn Du die Zahl wieder ausgeben möchtest, dann versucht der Computer wieder sein bestes und konvertiert die Binärzahl wieder in Dezimal und macht damit den Fehler sichtbar.

    Die Lösung ist, entweder ganz auf Fliesskommazahlen zu verzichten, und zwar schon bei der Eingabe, oder mit dem Rundungsfehler leben und bei der Ausgabe die Zahl runden. Wenn Du den Wert 4.679999999 mit 2 Nachkommastellen ausgibst, dann zeigt der Computer 4.68. Und siehe da: das ist genau das, was Du eingegeben hast.

    Das steht sicher ausführlicher und exakter in dem genannten Paper. Ich habe mir das nicht angeschaut.



  • tntnet schrieb:

    Das Problem ist, dass die Dezimalzahl 4.68 als float nicht exakt darstellbar ist. Der Computer hilft sich damit, dass er die nächst mögliche Fliesskommazahl speichert. Und dabei kommt es eben zu einen kleinen Fehler. Wenn Du die Zahl wieder ausgeben möchtest, dann versucht der Computer wieder sein bestes und konvertiert die Binärzahl wieder in Dezimal und macht damit den Fehler sichtbar.

    Die Lösung ist, entweder ganz auf Fliesskommazahlen zu verzichten, und zwar schon bei der Eingabe, oder mit dem Rundungsfehler leben und bei der Ausgabe die Zahl runden. Wenn Du den Wert 4.679999999 mit 2 Nachkommastellen ausgibst, dann zeigt der Computer 4.68. Und siehe da: das ist genau das, was Du eingegeben hast.

    Das steht sicher ausführlicher und exakter in dem genannten Paper. Ich habe mir das nicht angeschaut.

    Danke das verstehe ich, jedoch kann man dann einen Vergleich einer float variable nicht wirklich machen da es hierbei zu fehlern kommt. Aber es gibt ja ettliche programme, bei denen man kommabeträge eingibt und die mit diesen dann rechnen und vergleichen. Die Frage ist nur, wie man das Problem umgehen kann.



  • Pures Fett schrieb:

    @swordfish:

    ich glaube dein programm gibt nur cent und euro aus aber nicht die einzelnen Münzen also in der aufteilung wieviel 2euro münzen, 1 euro münzen etc das ist jedoch meine Aufgabenstellung. Leider dürfen wir wie geschrieben auch kein cin cout machen sondern müssten printf und scanf verwenden >_>

    Du hast nach dem Einlesen gefragt ... und ein bißchen Eigeninitiative darf man doch wohl erwarten? Na, egal.

    #include <cstddef>
    #include <cstdio>
    
    int main()
    {
    	unsigned const coins[]{ 200, 100, 50, 20, 10, 5, 2, 1 };
    
    	unsigned  euro;
    	unsigned  cent;
    	char      delim;
    
    	while( std::scanf( "%u%c%u", &euro, &delim, &cent ) != 3 ||
    	       !( delim == '.' || delim == ',' ) ||
    		   euro > 5 ||
    		   ( euro == 5 && cent ) || 
    		   cent > 99 )
    
    	{
    		std::fputs( "Input error! Please try again!\n\n", stderr );
    	}
    
    	unsigned amount{ euro * 100 + cent };
    
    	std::printf( "Amount in cents: %u\n\n", amount );
    
    	while( amount )
    	{
    		for( std::size_t i{}; i < sizeof coins / sizeof *coins; ++i ) {
    			if( amount / coins[ i ] ) {
    				std::printf( "%1.2f\n", coins[ i ] / 100. );
    				amount -= coins[ i ];
    				--i;
    			}
    		}
    	}
    }
    


  • Was passiert bei der Eingabe von 4.9 oder 4.90 ?

    Reicht die Eingabe als float nicht aus und dann

    int cent_betrag = (betrag + 0.005) * 100
    

    ?

    Pures Fett schrieb:

    Aber es gibt ja ettliche programme, bei denen man kommabeträge eingibt und die mit diesen dann rechnen und vergleichen.

    Rechnen kannst du ja damit.
    Der Vergleich auf Gleichheit ist problematisch.

    Du müßtest z.B. noch überprüfen, ob nur zwei Nachkommstaellen (und nicht mehr) eingegeben wurde.
    Da du noch Anfänger bist, lässt du die Lösung für das Problem erstmal sein und lernst den Rest der Sprache.



  • DirkB schrieb:

    Was passiert bei der Eingabe von 4.9 oder 4.90 ?

    Reicht die Eingabe als float nicht aus und dann

    int cent_betrag = (betrag + 0.005) * 100
    

    ?

    unsigned amount{ euro * 100 + ( cent < 10 ? cent * 10 : cent ) };
    

    :p


Anmelden zum Antworten