Andreas Korthaus: Datentyp für genaue Dezimalzahlen

Hallo!

Kürzlich wurde hierüber schonmal kurz diskutiert. Aber ich bin noch nicht ganz schlüssig, da es bei mir auf viele Nachkommastellen(8) ankommt, und die Genauigkeit hier enorm wichtig ist.

Wenn ich mySQL verwende, da gibt es ja den Datentyp DECIMAL, und dazu steht im Manual, dass das nicht als Zahl gespeichert wird, sondern im Prinzip als CHAR. Würde das nicht eigentlich reichen um das Problem mit der Ungenauigkeit von FLOAT und DOUBLE komplett(!) zu umgehen?

Ich würde also sowas wie DECIMAL(10,8) verwenden, könnte dann Zahlen da reinschreiben und hätte dabei immer 100% Genauigkeit solange es max. 8 Nachkommastellen sind, oder?

Und von der Darstellung? Da nicht alle Zahlen 8 Nachkommastellen haben müssen, manchmal auch nur 2, kann ich dann so ein Ergebnis aus der Datenbank mit PHP einfach über echo $zahl_aus_db; ausgeben ohne Probleme zu bekommen? Echo wandelt das ja dann auch noch in eine Zahl, vermutlich float um, aber hierbei dürften keine Informationen verloren gehen, oder? Gilt das auch für number_format() welches ja round() verwendet?
Was wenn man eine solche Zahl mit einem, Faktor < 0 multipliziert, und dann theoreetisch 9 Nachkommastellen hätte, man aber nur 8 ausgeben will, kann ich mich dann auf round() & Co. verlassen? probleme dürfte es doch erst bei Division durch bestimmte Priemzahlen geben, oder? Aber das mache ich nicht.

Viele Grüße
Andreas

  1. Hallo Andreas,

    probier es doch einfach aus. :o)

    Deine Ansätze klingen doch sehr gut; schreibe doch einfach mal ein Testprogramm und probier Deine Fälle aus...

    Damit sollte doch die Frage von selbst geklärt sein.

    Gruß Jan

    1. Hi!

      probier es doch einfach aus. :o)

      wie?

      Deine Ansätze klingen doch sehr gut; schreibe doch einfach mal ein Testprogramm und probier Deine Fälle aus...

      Weiß ich alle meien Fälle? Mein programm ist dynamisch, und arbeite gerde viel mit Benutzereingaben, da habe ich gar keinen Einfluss drauf.

      Grüße
      Andreas

      1. Hallo,

        Weiß ich alle meien Fälle? Mein programm ist dynamisch, und arbeite gerde viel mit Benutzereingaben, da habe ich gar keinen Einfluss drauf.

        Du solltest zumindest von der Logik her alle relevanten Fälle herleiten können. :o)

        Gruß Jan

        1. Hi!

          Du solltest zumindest von der Logik her alle relevanten Fälle herleiten können. :o)

          Kann ich leider nicht. Das Problem tritt dann auf wenn ich durch eine Zahl wie 3 oder 7 teile. Dann kann es mit bei round passieren dass ich falsche Werte bekomme, udn number_format ist nunmal nichts anderes als round!

          Was wären das denn Deiner Meinung nach für "Relevante Fälle"?

          Grüße
          Andreas

  2. hi Andreas!

    [mysql]

    Kürzlich wurde hierüber schonmal kurz diskutiert. Aber ich bin noch nicht ganz schlüssig, da es bei mir auf viele Nachkommastellen(8) ankommt, und die Genauigkeit hier enorm wichtig ist.

    im mysql manual auf http://www.mysql.com/doc/de/Numeric_types.html steht: "Der maximale Wertebereich von DECIMAL- und NUMERIC-Werten ist derselbe wie für DOUBLE, aber der tatsächliche Wertebereich einer gegebenen DECIMAL- oder NUMERIC-Spalte kann durch genauigkeit oder bereich für eine gegebene Spalte beschränkt werden." wobei genauigkeit und bereich die beiden parameter von DECIMAL(genauigkeit,bereich) sind.
    auf http://www.mysql.com/doc/en/Problems_with_float.html steht noch mehr interessantes dazu.

    Wenn ich mySQL verwende, da gibt es ja den Datentyp DECIMAL, und dazu steht im Manual, dass das nicht als Zahl gespeichert wird, sondern im Prinzip als CHAR. Würde das nicht eigentlich reichen um das Problem mit der Ungenauigkeit von FLOAT und DOUBLE komplett(!) zu umgehen?

    nein. aus oben genannten gruenden.
    (sobald man mit einem zahlen-string rechnen moechte, muss dieser ja wieder zu einer "richtigen" zahl konvertiert werden.)

    [php, mysql]

    Und von der Darstellung? Da nicht alle Zahlen 8 Nachkommastellen haben müssen, manchmal auch nur 2, kann ich dann so ein Ergebnis aus der Datenbank mit PHP einfach über echo $zahl_aus_db; ausgeben ohne Probleme zu bekommen? Echo wandelt das ja dann auch noch in eine Zahl, vermutlich float um, aber hierbei dürften keine Informationen verloren gehen, oder? Gilt das auch für number_format() welches ja round() verwendet? [...]

    ich schaetze, auch das haengt v.a. von der verwendeten plattform ab. aber bei nur 8 stellen treten da glaube ich noch keine probleme auf...

    wenn deine datenbank nur auf einem bestimmten recher laufen soll, dann probiers dort am besten einfach aus. ;-)

    prost
    seth

    1. Hi!

      http://www.mysql.com/doc/de/Numeric_types.html steht: "Der maximale Wertebereich von DECIMAL- und NUMERIC-Werten ist derselbe wie für DOUBLE, aber der tatsächliche Wertebereich einer gegebenen DECIMAL- oder NUMERIC-Spalte kann durch genauigkeit oder bereich für eine gegebene Spalte beschränkt werden." wobei genauigkeit und bereich die beiden parameter von DECIMAL(genauigkeit,bereich) sind.
      auf http://www.mysql.com/doc/en/Problems_with_float.html steht noch mehr interessantes dazu.

      Wirklich interessant, aber die Lösung gefällt mir nicht. Mit solchen Nachkommastellen in den Querys zu arbeiten bereitet mir irgendwie ein ungutes Gefühl. Ich habe aber noch folgednes:

      DECIMAL[(M[,D])] [ZEROFILL]
          Eine unkomprimierte Fließkommazahl. Kann nicht vorzeichenlos sein.
          Verhält sich wie eine CHAR-Spalte: ``Unkomprimiert'' bedeutet,
          dass die Zahl als Zeichenkette gespeichert wird, wobei ein Zeichen
          für jede Ziffer des Wertes steht.

      Aber es wird wie es aussieht wohl doch als Zahl gespeichert, spätestens bei der Umwandlung wenn man damit rechnen will passiert das. Also kann ich einen Dezimalwert nur auf 2 Wegen sicher speichern:

      1. wie oben beschreiben durch "Korrektur" von 0.00000001 in Queries...
      2. Indem ich alle Werte als BIGINT speichern und mit der Zahl der Nachkommastellen multiplizieren.

      Problem bei 1: ist mir zu kompliziert, wie frage ich dann ab sowas wie:

      SELECT * FROM TABLE WHERE spalte = "255555"

      kommt dann sowas wie:

      SELECT * FROM TABLE WHERE spalte < "255555" + 0.00000001 AND  spalte > "255555" - 0.00000001

      Problem 2: BIGINT hat "nichtmal" 19 komplette Stellen. Da ich eigentlich 10 Nachkommastellen wollte habeich nur noch knapp 9 echte Stellen. Sicher, ist ne ganze Menge, ich glaueb auch nicht das es probleme geben würde mit dem Bereich, aber es ist mir etwas zu nah an den tatsächlich möglichen, ich würde sagen 18 Stellen sind durchaus vorstellbar!

      Was ist wenn ich DECIMAL einfach mit einer viel höheren Genauigkeit sopeichere, sagen wir mal 15 oder sowas, dann kann dochtheoretisch die 10 Stelle nicht betroffen sein, oder?

      ich schaetze, auch das haengt v.a. von der verwendeten plattform ab. aber bei nur 8 stellen treten da glaube ich noch keine probleme auf...

      Im Manual wurde schon die 10. beispielsweise genannt!

      wenn deine datenbank nur auf einem bestimmten recher laufen soll, dann probiers dort am besten einfach aus. ;-)

      Wie? Ich wüßte nichtz wie ich das testen solte so dass ich danach ruhigen Gewissens sagen kann es _kann_ nichts passieren. Ich weiß noch nichtmal einen Test, ich weiß aber das mir 1 Test alleine nichts bringt, da vielleicht der eine Test funktioniert, ein andere, auf den ich nicht gekommen bin nicht. Ich will mich da nicht in falsche Sicherheit wägen, deshalb frage ich ja hier ;-)

      Viele Grüße
      Andreas

  3. Hallo,

    Ich würde also sowas wie DECIMAL(10,8) verwenden, könnte dann Zahlen da reinschreiben und hätte dabei immer 100% Genauigkeit solange es max. 8 Nachkommastellen sind, oder?

    Damit definiert man 10 signifikante Stellen, davon 8 Nachkommastellen + 1 Stelle fuer das negative Vorzeichen, was diesen Wertebereich abdeckt: -99.99999999 bis 999.99999999.

    MfG, Thomas

    1. Hi Thomas!

      Damit definiert man 10 signifikante Stellen, davon 8 Nachkommastellen + 1 Stelle fuer das negative Vorzeichen, was diesen Wertebereich abdeckt: -99.99999999 bis 999.99999999.

      Ja, das hatte ich auch gemerkt ;-)

      ich binmir aber jetzt gar nicht sicher ob die Zahlen in diesem Wertebereich auch 100% genau sind. Also angenommen ich multipliziere so eine Zahl 999.99999999 mit 0.27, woher kann man mit Sicherheit wissen dass bei so einer Aktion keine Fehler innerhalb der 8 Nachkommastellen auftreten?

      Viele Grüße
      Andreas

      1. Hallo,

        ich binmir aber jetzt gar nicht sicher ob die Zahlen in diesem Wertebereich auch 100% genau sind. Also angenommen ich multipliziere so eine Zahl 999.99999999 mit 0.27, woher kann man mit Sicherheit wissen dass bei so einer Aktion keine Fehler innerhalb der 8 Nachkommastellen auftreten?

        Verstehe ich nicht so ganz. Du bekommst die Genauigkeit von 8 Nachkommastellen, ggf. am Ende gerundet.

        Wenn es besonders genau sein soll, dann verwende die "Binary Coded Decimals"-Funktionen von PHP mit der Angabe der Anzahl Nachkommastellen:

        <?php

        $ergebnis=bcdiv(bcmul("99999999999","27"),"10000000000",8);
        // --> 269.99999999

        ?>

        Dieses Ergebnis sollte die Datenbank aber auch ohne diesen Umweg speichern, wenn die Berechnung innerhalb einer Abfrage vorkommt.

        MfG, Thomas

        1. Hi!

          Verstehe ich nicht so ganz. Du bekommst die Genauigkeit von 8 Nachkommastellen, ggf. am Ende gerundet.

          Ja, bekomme ich die garantiert? Computr könen ja keine Dezimalzahlen direkt speichern, so kann es sein das sie in wirkichkeiteie Zahl speichern die z.B. 0.000000001 daneben liegt, je nach CPU glaube ich. Das wird aber wieder richtig gerundet, dass keiner was merkt.
          Wenn ich jetzt number_format() verwende, dann wird ja evtl. am Ende gerundet, genau wie in dem beispiel auif php.net:

          "Fließkomma Präzision

          Es ist ziemlich normal, dass einfache Dezimalzahlen wie 0.1 oder 0.7 nicht in ihre internen binären Entsprechungen konvertiert werden können, ohne einen kleinen Teil ihrer Genauigkeit zu verlieren. Das kann zu verwirrenden Ergebnissen führen. So wird floor((0.1 + 0.7) * 10) normalerweise 7 statt des erwarteten Wertes 8 zurück geben (als Ergebnis der internen Entsprechung von 7.9999999999.... "
          http://www.php.net/manual/de/language.types.float.php

          Dieses Ergebnis sollte die Datenbank aber auch ohne diesen Umweg speichern, wenn die Berechnung innerhalb einer Abfrage vorkommt.

          Ich wollte eigentlich auch Berechnungen in der Datenbank durchführen, da habe ich sowas nicht zur Verfügung!

          Grüße
          Andreas

          1. Hallo,

            Ich wollte eigentlich auch Berechnungen in der Datenbank durchführen, da habe ich sowas nicht zur Verfügung!

            Dann verwende die zur Verfuegung stehenden Rundungsfunktionen, unter MySQL rundet ROUND(n,z) die Zahl n auf z Stellen.

            MfG, Thomas