Sebastian Jurk: Max eines float in mysql abrufen?

Hallo,

ich habe gerade ein Verständnisproblem zu floats und den Funktionen max und min.

Ich fülle eine Tabelle mit Preisen im amerikanischen Format. ZB 9.2312 und 9.4. Diese fülle ich dann in eine Tabelle und der Preis wird als float gespeichert.

Die queries lasse ich mir ausgeben und da sind immer maximal 5 Nachkommastellen. Auch die Tabelle zeigt das dann so an. Und ohne max oder min werden sie auch so ausgegeben. Allerdings wenn ich das max ausgeben lassen will dann erscheint da, anstatt 9.45, wie es in der Tabelle steht ein 9.449999809265137. Also 15 Stellen hintendran. Obwohl etwas anderes in der Tabelle steht.

Wieso? Mein select sieht einfach so aus:

  
select max(price) as price from pricelist;

Wenn ich das max rausnehme dann sind wieder maximal 5 nachkommastellen da.

Wie kommt das und wie bekomme ich einfach nur das normale max heraus das ich suche? Ich meine Mysql kann es nicht genauer wissen als ich es gespeichert habe. Also muss der Wert schlicht falsch sein.

Danke!
Sebastian

  1. Hi,

    Ich fülle eine Tabelle mit Preisen im amerikanischen Format. ZB 9.2312 und 9.4. Diese fülle ich dann in eine Tabelle und der Preis wird als float gespeichert.

    Das ist natürlich mal schlecht.
    Da Float bekanntlich eine immanente Ungenauigkeit besitzt, sollten Geldbeträge, die exakt gespeichert und wieder gelesen werden sollen, lieber als DECIMAL abgespeichert werden.

    Allerdings wenn ich das max ausgeben lassen will dann erscheint da, anstatt 9.45, wie es in der Tabelle steht ein 9.449999809265137. Also 15 Stellen hintendran.

    Siehste, da hamm wa den Salat ja schon.

    Also: Nimm DECIMAL.

    MfG ChrisB

    --
    RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
    1. Danke... ich hätte nicht geglaubt dass es einen ungenauen Datentyp gibt. Das macht für mich irgendwie gar keinen Sinn. Aber ich werde jetzt Decimal nehmen.

      1. Tach!

        ich hätte nicht geglaubt dass es einen ungenauen Datentyp gibt. Das macht für mich irgendwie gar keinen Sinn.

        Float ist der Versuch, reelle Zahlen im Binärformat abzulegen. Dabei kommt es zu Rundungsverlusten, ähnlich dem Problem, das Brüche wie 1/3 im Dezimalsystem verursachen: 0,33333... Float kann sehr große und sehr kleine Zahlen speichern, die aber nur in begrenzter Genauigkeit und mit den erwähnten Rundungsfehlern. Das ist systemübergreifend so. Meist ist das kein Problem - nicht so beim Geld. Für solche genauen Darstellungen und Berechnungen gibt es spezielle Datentypen, die nicht das Binärsystem zugrunde legen sondern die Ziffern einzeln und meist in halben Bytes ablegen. Dabei bleiben pro Halbbyte 6 von den 16 Werten ungenutzt und es rechnet sich auch langsamer, weil der Prozessor nicht mit seinem nativen Binärsystem arbeiten kann.

        dedlfix.

        1. Ok... ist die funktion round() in php genauso problembehaftet? Ich habe eine decimal-zahl mit 0.0001 summiert. Danach kam 1.00061709... heraus. Dann wollte ich das Ganze runden mit round(1.00061709..., 8, PHP_ROUND_HALF_DOWN);

          Heraus kam: 1.0006171

          Wurde da wieder in ein float umgewandelt und dadurch kam der Rundungsfehler zu Stande? Normal hat er in dem Fall hier ja einfach nur auf 7 Stellen gerundet und dann auch noch nach oben. Verstehe nicht wieso.

          Ich habe es jetzt erstmal mit der Funktion aus den Kommentaren von round() gelöst:

            
            function rfloor($real,$decimals = 2) {  
              return substr($real, 0,strrpos($real,'.',0) + (1 + $decimals));  
            }
          

          aber woher der rundungsfehler kommt würde ich schon gern wissen.

          1. Tach!

            Ok... ist die funktion round() in php genauso problembehaftet? Ich habe eine decimal-zahl mit 0.0001 summiert. Danach kam 1.00061709... heraus.

            Ich kann dir keinen Erklärungsversuch liefern, wenn du nicht nachvollziehbar beschreibst. Bitte gib die Zahlen genau an, um die es sich handelt. Und ja, alles was mit Float arbeitet hat dieselben Probleme, also auch round().

            Dann wollte ich das Ganze runden mit round(1.00061709..., 8, PHP_ROUND_HALF_DOWN);
            Heraus kam: 1.0006171

            Es kann sein, dass von ...09 aufgrund von Rundungsregeln völlig korrekt auf ...10 gerundet wurde und die 0 wegen Insignifikanz weggelassen wurde. Genauer kann ich es nicht vermuten oder gar prüfen, weil ich nicht weiß, was sich hinter deinem "..." verbirgt.

            dedlfix.

  2. Hi,

    Ich fülle eine Tabelle mit Preisen im amerikanischen Format. ZB 9.2312 und 9.4. Diese fülle ich dann in eine Tabelle und der Preis wird als float gespeichert.

    dass die Fließkommadarstellung manche Werte nicht exakt abbilden kann, liegt einfach an der binären Repräsentation. Ein harmloser Dezimalbruch wie 0.2 wird, wenn er binär dargestellt wird, plötzlich periodisch (nämlich 0.001100110011...) und damit ungenau, weil das Speicherformat nur eine begrenzte Stellenanzahl hat.

    Aber davon abgesehen: Was meinst du mit dem amerikanischen Format?

    So long,
     Martin

    --
    F: Kennt jemand einen Automobilfilm?
    A: Der mit dem Golf tankt.
    Selfcode: fo:) ch:{ rl:| br:< n4:( ie:| mo:| va:) de:] zu:) fl:{ ss:) ls:µ js:(
    1. Aber davon abgesehen: Was meinst du mit dem amerikanischen Format?

      Damit meinte ich dass die Nachkommastellen einen Punkt haben und kein Komma. Ist dort ja genau andersherum wie hier. Komma bei tausendern und . bei Dezimalstellen. Ich habs nur dazugeschrieben falls das ein Grund wäre.

      Grüße!
      Sebastian

      1. Hallo,

        Aber davon abgesehen: Was meinst du mit dem amerikanischen Format?
        Damit meinte ich dass die Nachkommastellen einen Punkt haben und kein Komma.

        ach das meinst du. Okay, das spielt überhaupt keine Rolle, solange du die Zahlen nicht in Textform (also als Strings) speichern willst, denn es betrifft ja nur das Anzeige- oder Eingabeformat.

        Ist dort ja genau andersherum wie hier. Komma bei tausendern und . bei Dezimalstellen.

        Ja, der Punkt als Tausender-Trennzeichen lässt sich hier einfach nicht ausrotten. Ich habe schon in der Schule gelernt, dass das bei uns zwar häufig gemacht wird, aber FALSCH ist, und dass man sich den Unsinn bitte gar nicht erst angewöhnen möge! Hauptsächlich wegen der möglichen Verwechslungen im internationalen Schriftverkehr, denn nicht immer ergibt sich die richtige Größenordnung aus dem Kontext.
        Das empfohlene Tausender-Trennzeichen in DE ist ein Leerzeichen - und zwar im Computer-Zeitalter ein geschütztes und -wenn verfügbar- halb-breites.

        Übrigens habe ich schon ab und zu gesehen, dass Leute ein Apostroph als Tausender-Trennzeichen setzen. Find ich auch gut, mach ich auch selbst gern.

        Ciao,
         Martin

        --
        "Hier steht, deutsche Wissenschaftler hätten es im Experiment geschafft, die Lichtgeschwindigkeit auf wenige Zentimeter pro Sekunde zu verringern." - "Toll. Steht da auch, wie sie es gemacht haben?" - "Sie haben den Lichtstrahl durch eine Behörde geleitet."
        Selfcode: fo:) ch:{ rl:| br:< n4:( ie:| mo:| va:) de:] zu:) fl:{ ss:) ls:µ js:(
        1. Ich hatte angenommen dass 1.000.000,00 die offizielle deutsche und 1,000,000.00 die offizielle amerikanische Schreibweise ist. Ich glaube auch Betriebssysteme und Officeprogramme haben das so voreingestellt... stimmt schon mal für Vista...

          Auf jeden Fall scheint jetzt erstmal das Rechenproblem für mysql behoben zu sein. Jetzt hab ich das selbe Problem in php selbst.

          ZB:
          Highest Price: 9.57471000
          Lowest Price: 9.64995000
          Price-Difference: 0.075240000000001

          Bis 5 Stellen nach dem Komma ist es ja korrekt. Aber es scheint hier wohl wieder eine Ungenauigkeit zu geben. Ich habe gesucht aber für php keinen Datentyp speziell für Währungsoperationen gefunden. Den muss es aber geben da ja auch andere Webseiten mit Finanzen rechnen und man sich keine Ungenauigkeiten erlauben kann. Einfach abschneiden ist sicher auch keine Lösung da es sicher auch Ergebnisse wie 0.0752399999999999 geben könnte.

          Wie löst man das? Hier: http://php.net/manual/de/language.types.float.php habe ich nur gelesen dass es ungenau ist und man das hier benutzen soll: http://php.net/manual/de/ref.bc.php

          Ich kann mir aber etwas schwer vorstellen dass alle Finanzseiten anstatt eines normalen Minus immer diese Funktionen benutzen...

          1. Tach!

            Auf jeden Fall scheint jetzt erstmal das Rechenproblem für mysql behoben zu sein. Jetzt hab ich das selbe Problem in php selbst.

            Und das kannst du, wie du nun wissen solltest, prinzipbedingt nicht mit dem Typ Float lösen. Es gibt (mindestens) zwei Lösungswege. Zum einen kannst du "das Komma verschieben", indem du einfach mit Integern rechnest, die du aus dem originalen Wert mutltipliziert mit 10, 100, 1000 und so weiter bildest, je nach gewünschter Präzision.

            Ich habe gesucht aber für php keinen Datentyp speziell für Währungsoperationen gefunden. Den muss es aber geben da ja auch andere Webseiten mit Finanzen rechnen und man sich keine Ungenauigkeiten erlauben kann.

            Den gibt es trotzdem nicht (für PHP).

            Wie löst man das? Hier: http://php.net/manual/de/language.types.float.php habe ich nur gelesen dass es ungenau ist und man das hier benutzen soll: http://php.net/manual/de/ref.bc.php

            Ja, die Verwendung BCMath oder auch GMP für noch mehr mathematische Möglichkeiten wäre der zweite Lösungsweg.

            Ich kann mir aber etwas schwer vorstellen dass alle Finanzseiten anstatt eines normalen Minus immer diese Funktionen benutzen...

            Was bleibt dir denn übrig, wenn du (abgesehen von Integern) präzise, auf dem Dezimalsystem beruhende Mathematik benötigst und nur Funktionalität für das Binärsystem direkt eingebaut ist?

            dedlfix.

            1. Ok, ich glaube dann werde ich bcmath benutzen. Workarounds würden vermutlich mehr Arbeit machen als es lohnt. Dann lieber gleich richtig machen... :)

          2. Hallo,

            Ich hatte angenommen dass 1.000.000,00 die offizielle deutsche und 1,000,000.00 die offizielle amerikanische Schreibweise ist. Ich glaube auch Betriebssysteme und Officeprogramme haben das so voreingestellt...

            ja, meistens. Leider.

            Auf jeden Fall scheint jetzt erstmal das Rechenproblem für mysql behoben zu sein. Jetzt hab ich das selbe Problem in php selbst.

            Klar, das wirst du in allen Programmiersprachen haben, die herkömmliche Fließkommaarithmetik benutzen.

            Bis 5 Stellen nach dem Komma ist es ja korrekt. Aber es scheint hier wohl wieder eine Ungenauigkeit zu geben. Ich habe gesucht aber für php keinen Datentyp speziell für Währungsoperationen gefunden. Den muss es aber geben da ja auch andere Webseiten mit Finanzen rechnen und man sich keine Ungenauigkeiten erlauben kann. Einfach abschneiden ist sicher auch keine Lösung da es sicher auch Ergebnisse wie 0.0752399999999999 geben könnte.

            Falls du keine langen Multiplikationsketten hast, kannst du dich aus der Affäre ziehen, indem du intern alle Beträge ganzzahlig in Cent speicherst. Additionen, Subtraktionen und Multiplikationen von Ganzzahlen sind exakt (solange kein Überlauf stattfindet).

            Sobald du aber Rechenschritte hast, die der Genauigkeit wegen mit Bruchteilen von Cent arbeiten müssen (Zinseszins, Währungsumrechnung o.ä.), ist das auch keine gute Idee mehr. Überlege also, ob das für deine Anwendung eine Lösung sein könnte.

            Wie löst man das? Hier: http://php.net/manual/de/language.types.float.php habe ich nur gelesen dass es ungenau ist und man das hier benutzen soll: http://php.net/manual/de/ref.bc.php

            Ich kann mir aber etwas schwer vorstellen dass alle Finanzseiten anstatt eines normalen Minus immer diese Funktionen benutzen...

            Zumal man dann die Werte immer als String vorhalten muss anstatt als echte Zahlenwerte. Aber alle anderen Möglichkeiten, die mir für PHP einfallen, sind eben nur Workarounds, die in vielen Fällen brauchbar sind, aber eben nicht in jedem Fall.

            So long,
             Martin

            --
            Der Bäcker schlägt die Fliegen tot
            Und macht daraus Rosinenbrot.
            Selfcode: fo:) ch:{ rl:| br:< n4:( ie:| mo:| va:) de:] zu:) fl:{ ss:) ls:µ js:(
    2. Om nah hoo pez nyeetz, Der Martin!

      Ein harmloser Dezimalbruch wie 0.2 wird, wenn er binär dargestellt wird, plötzlich periodisch (nämlich 0.001100110011...) und damit ungenau, weil das Speicherformat nur eine begrenzte Stellenanzahl hat.

      Das nenne ich mal "auf den Punkt gebracht"!

      Hatten wir nicht mal einen Artikel unter http://aktuell.de.selfhtml.org/artikel/, der das auch schön thematisierte?

      Matthias

      --
      1/z ist kein Blatt Papier.

      1. Hallo Matthias,

        Ein harmloser Dezimalbruch wie 0.2 wird, wenn er binär dargestellt wird, plötzlich periodisch (nämlich 0.001100110011...) und damit ungenau, weil das Speicherformat nur eine begrenzte Stellenanzahl hat.
        Das nenne ich mal "auf den Punkt gebracht"!

        danke, das ist natürlich nur die Ultra-Kurzfassung.

        Hatten wir nicht mal einen Artikel unter http://aktuell.de.selfhtml.org/artikel/, der das auch schön thematisierte?

        Mir war auch so, aber ich weiß nicht mehr, ob wir das vielleicht geträumt haben. ;-)
        Für den Fall, dass der Artikel nur in unserer Einbildung existiert, würde ich mir sogar ein <I> anheften und versuchen, den Sachverhalt möglichst anschaulich und ausführlich aufzubereiten. Sinnvollerweise dann gleich fürs Wiki.

        Natürlich nur, wenn nicht jemand den vermeintlich existierenden Artikel findet und uns darauf stößt.

        So long,
         Martin

        --
        Ordnung schaffen heißt, das Eigelb vom Dotter zu trennen.
        Selfcode: fo:) ch:{ rl:| br:< n4:( ie:| mo:| va:) de:] zu:) fl:{ ss:) ls:µ js:(
        1. Om nah hoo pez nyeetz, Der Martin!

          Für den Fall, dass der Artikel nur in unserer Einbildung existiert, würde ich mir sogar ein <I> anheften und versuchen, den Sachverhalt möglichst anschaulich und ausführlich aufzubereiten. Sinnvollerweise dann gleich fürs Wiki.

          Die FAQ verweisen auf zwei externe Artikel. Da du dir ein <I> angeheftet hast, würde ich an deiner Stelle diese Artikel _nicht_ lesen, denn so kommt man nur in die Versuchung, abzuschreiben.*

          Ich meinerseits hefte mir ein <I> an, dich ggf. bei Wikisyntaxproblemen zu unterstützen.

          *Das ist wie mit den blauen Elefanten.

          Matthias

          --
          1/z ist kein Blatt Papier.