Joachim: Brett vorm Kopf. Finde den Fehler nicht.

0 47

Brett vorm Kopf. Finde den Fehler nicht.

Joachim
  • php
  1. -1
    Operator
    1. 0
      dedlfix
    2. 0
      Graf Bit
      1. 0
        Der Martin
      2. 0
        Rolf B
        1. 0
          Der Martin
        2. 0

          Float binär darstellen

          Graf Bit
          • datenvisualisierung
          • php
          1. 0
            Rolf B
  2. 0
    Erik K.
  3. 0
    Der Martin
    1. 0
      Joachim
      1. 0
        Der Martin
        1. 0
          Joachim
          1. 0

            Brett vorm Kopf. Finde den Fehler nicht. Oder verstehe settype nicht. :-/

            Joachim
            1. 0
              dedlfix
              1. 0
                Joachim
                1. 0
                  Rolf B
        2. 0
          Joachim
          1. 0
            Der Martin
            1. 0
              Joachim
      2. 0
        encoder
        1. 0
          Rolf B
          1. 0
            encoder
  4. 0
    Rolf B
    1. 0
      Der Martin
      • php
      • selfhtml-wiki
      1. 0
        Rolf B
        1. 0
          Der Martin
          1. 0
            Matthias Apsel
        2. 0
          Matthias Apsel
      2. 0
        Matthias Apsel
        1. 0
          Rolf B
    2. 0
      Joachim
      1. 0
        Joachim
      2. 2
        Der Martin
        1. 0
          Joachim
      3. 2
        Rolf B
        1. 0
          Der Martin
          1. 0
            Matthias Apsel
            • sonstiges
            1. 0
              Rolf B
              1. 0
                Der Martin
        2. 0
          Joachim
          1. 0
            Der Martin
        3. 0
          Tabellenkalk
          1. 0
            Der Martin
            1. 0
              MudGuard
          2. 0
            Rolf B

Hallo,

wer nimmt mal gerade mein Brett vor dem Kopf weg? Warum ergibt die if-Bedingung true?

if($a + $b > $c) {
    echo ("a+b ist größer als c.
    <br>a: ".$a."
    <br>b: ".$b."
    <br>c: ".$c);
}

ergibt:

a+b ist größer als c
a: 42.01
b: 146.18
c: 188.19

Ich habs auch mal versucht, die 3 Werte nochmal über settype explizit auf Floatwerte zu casten, aber das ändert nichts.

Joachim

  1. Auch Hallo,

    wer nimmt mal gerade mein Brett vor dem Kopf weg? Warum ergibt die if-Bedingung true?

    if($a + $b > $c) {
        echo ("a+b ist größer als c.
        <br>a: ".$a."
        <br>b: ".$b."
        <br>c: ".$c);
    }
    

    ergibt:

    a+b ist größer als c
    a: 42.01
    b: 146.18
    c: 188.19
    

    Ich habs auch mal versucht, die 3 Werte nochmal über settype explizit auf Floatwerte zu casten, aber das ändert nichts.

    Vielleicht findet sich die Lösung in der Operatorrangfolge?

    Manchmal helfen auch ein paar Klammern.

    Stay TRUE
    der Operator

    1. Tach!

      Vielleicht findet sich die Lösung in der Operatorrangfolge?

      Nein, + hat eine höhere Priorität als >, also ist das genau das gewünschte.

      Manchmal helfen auch ein paar Klammern.

      In dem Fall nicht.

      dedlfix.

    2. problematische Seite

      Hallo,

      könnte man nicht mit Hilfe von decbin() nachgucken, was PHP da im Hintergrund macht?

      bis später
      Graf Bit

      1. problematische Seite

        Moin,

        könnte man nicht mit Hilfe von decbin() nachgucken, was PHP da im Hintergrund macht?

        was willst du damit herausfinden? Das liefert dir nur einen String mit der binären Darstellung einer Zahl. Also decbin(200) ergibt z.B. den String "11001000".

        Das hat nichts mit der internen Darstellung oder Speicherung von Variablenwerten zu tun.

        Live long and pros healthy,
         Martin

        --
        Home is where my beer is.
      2. problematische Seite

        Hallo Graf,

        im Gegensatz zur Doku wandelt decbin keinen dezimalen Wert ins Binärformat, sondern nur einen Integer-Wert. D.h. decbin(42.02) ergibt 101010, die Binärdarstellung von 42.

        Etwas näher könnte man der Sache mit debug_zval_dump() kommen - aber der lügt ebenfalls und sagt nur: double(42.02). Dass da nicht 42.02 in der Variablen steht, sondern

        $$\displaystyle \frac{5913789260690883}{140737488355328} = 42.02000000000000312638803734444081783294677734375$$

        (danke an binaryconvert.com und Wolfram Alpha), das verrät er nicht.

        Was intern passiert, ist übrigens längst klar.

        Liebe Grüße an den edlen Cousin, Graf Zahl!
        Rolf

        --
        sumpsi - posui - obstruxi
        1. problematische Seite

          Hallo Rolf,

          im Gegensatz zur Doku wandelt decbin keinen dezimalen Wert ins Binärformat, sondern nur einen Integer-Wert. D.h. decbin(42.02) ergibt 101010, die Binärdarstellung von 42.

          das stimmt, der Name der Funktion ist schlecht gewählt.

          Einerseits ist dezimal hier nicht im Sinn eines Dezimalbruchs gemeint, sondern als Abgrenzung der Dezimaldarstellung einer Zahl zu anderen Zahlensystemen (hexadezimal, oktal).

          Andererseits ist auch das Unsinn, denn die Darstellung einer Zahl in irgendeinem Zahlensystem hat nichts damit zu tun, wie sie intern gespeichert wird. Mit decbin(0x1F04) würde ich die Bezeichnung ad absurdum führen.

          Daher wäre intbin tatsächlich die bessere, weil treffendere Bezeichnung.

          Etwas näher könnte man der Sache mit debug_zval_dump() kommen

          Oh, interessant. Das kannte ich noch gar nicht.

          Live long and pros healthy,
           Martin

          --
          Home is where my beer is.
        2. problematische Seite

          Hallo Ro.,
          hallo @all,

          im Gegensatz zur Doku wandelt decbin keinen dezimalen Wert ins Binärformat, sondern nur einen Integer-Wert. D.h. decbin(42.02) ergibt 101010, die Binärdarstellung von 42.

          Etwas näher könnte man der Sache mit debug_zval_dump() kommen - aber der lügt ebenfalls und sagt nur: double(42.02). Dass da nicht 42.02 in der Variablen steht, sondern

          $$\displaystyle \frac{5913789260690883}{140737488355328} = 42.02000000000000312638803734444081783294677734375$$

          (danke an binaryconvert.com und Wolfram Alpha), das verrät er nicht.

          Was intern passiert, ist übrigens längst klar.

          Liebe Grüße an den edlen Cousin, Graf Zahl!

          Ok, reingefallen.

          Kennt denn irgendjemand hier eine Methode, um sich die Float-Werte binär darstellen zu lassen, also quasi ein direktes Speicherabbild der Variable zu visualisieren?

          Bit frei!
          Graf Bit

          1. problematische Seite

            Hallo Graf Bit,

            wenn Du die interne Repräsentation einer Variablen in PHP abrufen willst, dann solltest Du den Gedanken gleich wieder streichen. Abgesehen davon bietet Dir PHP, solange Du nicht selbst eine Erweiterung programmierst, keinen Zugang zu den internen Strukturen.

            Nikita Popov von Jetbrains (die Macher von PHPStorm) hat in den letzten Jahren wenig gebloggt, aber: er hat 2015 zwei ausführliche Artikel über die interne Ablage von PHP 7 Variablen geschrieben und es auch mit PHP 5 verglichen.

            TL;DR: Diese Speicherung ist versionsabhängig und kann sich mit jedem PHP Release ändern.

            Aber um dem double-Problem auf die Spur zu kommen: PHP führt bei der String-Umwandlung eines double-Wertes offenbar selbst schon eine Rundung durch, um diese winzig kleinen IEEE-754 Abweichungen zu kompensieren. Wenn Du diese Rundung aushebeln willst, kannst Du printf statt echo verwenden (oder sprintf, wenn Du es in einem String speichern statt ausgeben willst):

            printf("%.53f", 42.02);
            // Schreibt 42.02000000000000312638803734444081783294677734375000000
            

            Das passt zu dem, was der binaryconverter erzeugt hat, den ich verlinkt hatte.

            Rolf

            --
            sumpsi - posui - obstruxi
  2. Also bei mir mit php7.3 ergibt der Code keine Ausgabe.

    Was sagt var_dump zu den 3 Variablen? Welche php-Version hast du?

  3. Hi,

    wer nimmt mal gerade mein Brett vor dem Kopf weg?
    Warum ergibt die if-Bedingung true?

    if($a + $b > $c) {
        echo ("a+b ist größer als c.
        <br>a: ".$a."
        <br>b: ".$b."
        <br>c: ".$c);
    }
    

    ergibt:

    a+b ist größer als c
    a: 42.01
    b: 146.18
    c: 188.19
    

    naja, ist ja auch fast korrekt.
    Ich tippe mal auf einen Klassiker: Rundungsfehler bei der Addition von float-Werten.

    Live long and pros healthy,
     Martin

    --
    Home is where my beer is.
    1. Hi Martin,

      naja, ist ja auch fast korrekt.
      Ich tippe mal auf einen Klassiker: Rundungsfehler bei der Addition von float-Werten.

      Hätte ich auch vermutet. Aber lass mal diesen Code laufen:

      $a = 42.02;
      $b = 146.18;
      $c = 188.20;
      
      if($a + $b > $c) {
          echo("a+b ist größer als c
          <br>a: ".$a."
          <br>b: ".$b."
          <br>c: ".$c);
      } else {
        echo "<br>equal";
      }
      
      $a = settype($a, 'float');
      $b = settype($b, 'float');
      $c = settype($c,'float');
      
      if($a + $b > $c) {
          echo("a+b ist größer als c
          <br>a: ".$a."
          <br>b: ".$b."
          <br>c: ".$c);
      } else {
        echo "<br>equal";
      }
      

      Er ergibt:

      a+b ist größer als c
      a: 42.02
      b: 146.18
      c: 188.20
      equal
      

      Ist schon irre. 😀

      Joachim

      1. Hallo Joachim,

        $a = 42.02;
        $b = 146.18;
        $c = 188.20;
        
        if($a + $b > $c) {
            echo("a+b ist größer als c
            <br>a: ".$a."
            <br>b: ".$b."
            <br>c: ".$c);
        } else {
          echo "<br>equal";
        }
        
        $a = settype($a, 'float');
        $b = settype($b, 'float');
        $c = settype($c,'float');
        
        if($a + $b > $c) {
            echo("a+b ist größer als c
            <br>a: ".$a."
            <br>b: ".$b."
            <br>c: ".$c);
        } else {
          echo "<br>equal";
        }
        

        Er ergibt:

        a+b ist größer als c
        a: 42.02
        b: 146.18
        c: 188.20
        equal
        

        da wird sowohl der if- als auch der else-Zweig bearbeitet??
        Das kann völlig unabhängig von der Bedingung eigentlich nicht sein; da muss noch ein anderer Fehler drinstecken. Irgendein überzähliges Semikolon oder falsch gesetzte Klammern - etwas in der Art.

        Gemein wäre etwa sowas:

        if (false);
         { echo "false ist doch true!";
         }
        

        Beachte das Semikolon hinter der if-Klammer: Es schließt die if-Anweisung ohne Anweisungsblock ab, der nachfolgende Block in geschweiften Klammern gehört dann nicht mehr zur if-Anweisung. Solche Fehler sind schwer zu finden, weil sie eben keine Syntaxfehler sind.

        Ist schon irre. 😀

        Vielleicht ist die Erklärung ganz simpel.

        Live long and pros healthy,
         Martin

        --
        Home is where my beer is.
        1. Hallo Martin,

          da wird sowohl der if- als auch der else-Zweig bearbeitet??
          Das kann völlig unabhängig von der Bedingung eigentlich nicht sein;

          Es sind 2 if-else-Blöcke 😉

          Daher weht der Wind.

          Was mich wundert, ist aber, dass ich dachte, wenn php Strings zum addieren vorfindet, dass es selber castet. Wenn möglich in INT oder wenn mit nachkomma, dann in Float.

          Das passiert auch, aber das Ergebnis stimmt trotzdem nicht.

          Joachim

          1. Wer kann mir erklären, was hier php genau macht? Wie kommt es zu dieser Ausgabe?

            // Werte
            $a = 42.02;
            $b = 146.18;
            $c = 188.20;
            
            // Addition von Strings
            $d1 = $a+$b;
            
            // Vergleich der Werte
            if($a + $b > $c) {
                echo ("1. Durchlauf: <br>Not equal<br>");
                echo var_dump($d1)."(Variable d)<br>";
                echo var_dump($c)."(Variable c)";
            } 
            
            // 2. Durchlauf mit gecasteten Werten
            //------------------------------------------
            
            // Cast in Float
            $a = settype($a, 'float');
            $b = settype($b, 'float');
            $c = settype($c,'float');
            
            // Adition von Float-Werten
            $d2 = $a+$b;
            
            // Vergleich der Werte
            if($a + $b > $c) {
            // nix
            } else {
              echo("<br>-----------------------------<br>");
              echo "2. Durchlauf: <br>equal<br>";    
              echo var_dump($d2)."(Variable d)<br>";
              echo var_dump($c)."(Variable c)";
            
            }
            

            Ausgabe:

            1. Durchlauf:
            Not equal
            float(188.2) (Variable d)
            float(188.2) (Variable c)
            -----------------------------
            2. Durchlauf:
            equal
            int(2) (Variable d)
            bool(true) (Variable c)
            
            1. Tach!

              // Cast in Float
              $a = settype($a, 'float');
              $b = settype($b, 'float');
              $c = settype($c, 'float');
              

              Zitat Handbuch: "Returns TRUE on success or FALSE on failure."

              Du castest erst den Typ zu float und überschreibst durch die Zuweisung die Variablen mit true.

              dedlfix.

              1. Hallo dedlfix,

                Du castest erst den Typ zu float und überschreibst durch die Zuweisung die Variablen mit true.

                Einverstanden. Hatte mir doch gedacht, dass ich bei settype Nachhilfe brauche 😉

                Trotzdem erklärt es noch nicht, warum php den cast nicht selbstständig hin bekommt, denn php versucht es ja offensichtlich:

                1. Durchlauf:
                Not equal
                float(188.2) (Variable d)
                float(188.2) (Variable c)
                

                Hast Du auch hierfür eine Lösung?

                Joachim

                1. Hallo Joachim,

                  PHP muss gar nicht casten.

                  $a = 42.01;
                  

                  erzeugt bereits eine float-Variable, so dass settype($a, "float") wirkungslos ist.

                  Zur Fehlerursache siehe meinen anderen Post.

                  Rolf

                  --
                  sumpsi - posui - obstruxi
        2. Gemein wäre etwa sowas:

          if (false);
           { echo "false ist doch true!";
           }
          

          Oder sowas:

          $var = "false";
          settype($var, 'bool');
          var_dump($var); // true
          
          1. Hallo,

            $var = "false";
            settype($var, 'bool');
            var_dump($var); // true
            

            das ist in der Tat "tricky", aber nachvollziehbar: Ein String muss leer sein oder einen Null-Wert repräsentieren, um als boolsches false zu gelten.

            Ähnliche Fehler im Ansatz sieht man übrigens oft bei PHP-Laien, die den Unterschied zwischen Zahlenwerten und Strings noch nicht verstanden haben. Die notieren dann gern auch numerische Werte in Anführungszeichen, also als Strings. Meistens klappt das wegen der automatischen Typumwandlung, aber manchmal fällt man damit eben auf die Klappe.

            Live long and pros healthy,
             Martin

            --
            Home is where my beer is.
            1. Ähnliche Fehler im Ansatz sieht man übrigens oft bei PHP-Laien, die den Unterschied zwischen Zahlenwerten und Strings noch nicht verstanden haben. Die notieren dann gern auch numerische Werte in Anführungszeichen, also als Strings. Meistens klappt das wegen der automatischen Typumwandlung, aber manchmal fällt man damit eben auf die Klappe.

              Oder ein Integerüberlauf z.b in java wird auch immer wieder gerne genommen 😉

              Oder auch sowas:

               char b = 65; 
               char c = 'A' + 1;
              java.lang.System.out.println( b ); // A
              java.lang.System.out.println( c ); // B
              
      2. Ganz so irre ist das nicht. Es ist doch der selbe Fall wie im ersten Beispiel.

        Interessehalber habe ich nach "online float representation" gesucht und neben ein paar anderen Ergebnissen das hier gefunden http://weitz.de/ieee/

        Damit kannst du sehen wie deine Zahlen binär dargestellt werden und kannst sie addieren.

        Dabei kommt heraus dass die Summe deiner Zahlen 188.20000000000002 ist.
        Die einzelne Zahl 188.2 lässt sich dagegen ohne Rundungsfehler darstellen, also ist die Summe tatsächlich größer als 188.2.

        Das hier war mein erster Fund https://www.h-schmidt.net/FloatConverter/IEEE754de.html.
        Auch nett, aber nur in 32 Bit Darstellung und ohne Rechenmöglichkeit.

        1. Hallo encoder,

          Die einzelne Zahl 188.2 lässt sich dagegen ohne Rundungsfehler darstellen

          Auch ohne einen IEEE754 Konverter widerspreche ich da. 0,2 ist $$\frac{1}{5}$$, das kannst Du nie als endliche Summe von Zweierpotenzen darstellen.

          IEEE754-Darstellung von 188,2.

          So wie es aussieht, ist

          $$188{,}2 = 10111100{,}\overline{0011}$$

          Oder, klassisch konvertiert wie man es bei Dezimalbrüchen gelernt hat:

          $$188{,}2 = 188 + \frac{1}{5} = 188 + \frac{3}{15} = 10111100_2 + \frac{0011_2}{1111_2} = 10111100{,}\overline{0011}_2$$

          Rolf

          --
          sumpsi - posui - obstruxi
          1. Mit dieser Argumentation stimmt das.
            Dann stellt die Seite das wohl falsch dar und rundet sich irgendwas schön.

  4. Hallo Joachim,

    man kann das Problem noch einfacher darstellen. <=> ist der Spaceship Operator, der -1, 0 oder 1 ausgibt, je nachdem, ob der linke Operand kleiner als, gleich wie oder größer als der rechte Operand ist.

    echo (42.01 + 146.17 <=> 188.18) . "<br>";
    echo (42.01 + 146.18 <=> 188.19) . "<br>";
    echo (42.02 + 146.18 <=> 188.20) . "<br>";
    

    Ergibt

    -1
    0
    1
    

    Grund: Der Datentyp float kann Integerzahlen im Bereich der Mantissenlänge jederzeit korrekt darstellen. Sobald Nachkommazahlen ins Spiel kommen, wird es schwierig, weil [IEEE 754] ein binäres Format ist. Du kannst bspw. 1/7 nicht als endliche Dezimalzahl darstellen. Und Du kannst 1/100 nicht als endliche Binärzahl darstellen.

    Dass der mittlere Vergleich 0 ergibt, ist reiner Zufall.

    Janosch war dazu im Wiki sehr fleißig

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hallo Rolf,

      Janosch war dazu im Wiki sehr fleißig

      genau den Beitrag wollte ich heute schon verlinken, habe aber nach etwa 10min erfolgloser Suche aufgegeben.

      Fazit: Der Beitrag ist gut, aber nicht prominent verlinkt, und die Wiki-Suche reißt's in dem Fall auch nicht raus.

      Live long and pros healthy,
       Martin

      --
      Home is where my beer is.
      1. Hallo Martin,

        suche nach IEEE oder Fließkomma.

        https://wiki.selfhtml.org/index.php?search=fließkomma&title=Spezial%3ASuche&go=Seite

        Im Programmiertechnik-Portal ist sie auch verlinkt, aber das muss man wissen. Ich habe mal in den PHP Tutorials einen Link hinzugefügt. Jetzt muss nur noch jemand die PHP Startseite verlinken :D

        Ob man auch über's Glossar sinnvoll was machen kann?

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Hallo Rolf,

          suche nach IEEE oder Fließkomma.

          https://wiki.selfhtml.org/index.php?search=fließkomma&title=Spezial%3ASuche&go=Seite

          ich hatte als erste spontane Eingebung nach float gesucht und den Datentyp gemeint, der einzige Suchtreffer führte aber zur CSS-Eigenschaft float. Dann nach Rundungsfehler, brachte aber auch nichts Sinnvolles. Ich weiß nicht mehr, welche Suchbegriffe ich sonst noch probiert habe.

          Im Programmiertechnik-Portal ist sie auch verlinkt, aber das muss man wissen.

          Ja, ergibt Sinn. Jedenfalls nicht spezifisch PHP oder Javascript; das Problem kann einen in sehr vielen Programmiersprachen treffen. Und nicht nur da, sogar in Excel.

          Ob man auch über's Glossar sinnvoll was machen kann?

          Bestimmt. 😀

          Live long and pros healthy,
           Martin

          --
          Home is where my beer is.
          1. Hallo Der Martin,

            https://wiki.selfhtml.org/index.php?search=fließkomma&title=Spezial%3ASuche&go=Seite

            ich hatte als erste spontane Eingebung nach float gesucht und den Datentyp gemeint, der einzige Suchtreffer führte aber zur CSS-Eigenschaft float. Dann nach Rundungsfehler, brachte aber auch nichts Sinnvolles. Ich weiß nicht mehr, welche Suchbegriffe ich sonst noch probiert habe.

            auf der float-Seite gibt es nun dank @Rolf B einen prominenten Link auf die Seite Programmiertechnik/Rechnerarithmetik, die Seite Rundungsfehler leitet seit 2018 ebenfalls auf diese Seite. Zusätzlich habe ich noch eine Weiterleitung von Rechnerarithmetik auf die entsprechende Seite erstellt.

            Bis demnächst
            Matthias

            --
            Du kannst das Projekt SELFHTML unterstützen,
            indem du bei Amazon-Einkäufen Amazon smile (Was ist das?) nutzt.
        2. Hallo Rolf B,

          Ob man auch über's Glossar sinnvoll was machen kann?

          https://wiki.selfhtml.org/wiki/Runden

          Bis demnächst
          Matthias

          --
          Du kannst das Projekt SELFHTML unterstützen,
          indem du bei Amazon-Einkäufen Amazon smile (Was ist das?) nutzt.
      2. Hallo Der Martin,

        wenn du deine Suchbegriffe mitteilst, könnte man entsprechende Weiterleitungen einrichten.

        Bis demnächst
        Matthias

        --
        Du kannst das Projekt SELFHTML unterstützen,
        indem du bei Amazon-Einkäufen Amazon smile (Was ist das?) nutzt.
        1. Hallo Matthias,

          done.

          CSS/float: Links auf PHP/float und JavaScript/Number eingefügt

          Bei PHP/float und Number Links zur Rechnerarithmetik eingefügt.

          Rolf

          --
          sumpsi - posui - obstruxi
    2. Hallo Rolf,

      Grund: Der Datentyp float kann Integerzahlen im Bereich der Mantissenlänge jederzeit korrekt darstellen. Sobald Nachkommazahlen ins Spiel kommen, wird es schwierig, weil [IEEE 754] ein binäres Format ist. Du kannst bspw. 1/7 nicht als endliche Dezimalzahl darstellen. Und Du kannst 1/100 nicht als endliche Binärzahl darstellen.

      Dass der mittlere Vergleich 0 ergibt, ist reiner Zufall.

      Irre 😀 Aber verständlich, wenn mans weiß. Danke für die Aufklärung. Trotzdem bleibt für mich die Frage, wie ich einen <=> Vergleich denn dann zweifelsfrei anstellen soll. Wirklich sehr interessantes Thema, auf das ich da gestoßen bin. Wie händelt man das? Über eine Art "Fuzzy-Logic"? Oder gibt es einen anderen "Trick"?

      Joachim

      1. Immerhin ergibt auch

        echo (round(42.01,2) + round(146.17,2) <=> round(188.18,2))
        
        
        -1
        

        😕

      2. Hallo Joachim,

        Trotzdem bleibt für mich die Frage, wie ich einen <=> Vergleich denn dann zweifelsfrei anstellen soll.

        ein Vergleich auf größer oder kleiner ist normalerweise unkritisch - außer wenn man weiß, dass der zu vergleichende Wert sehr dicht am Limit liegen kann und diese geringen Unterschiede wirklich relevant sind.

        Die übliche Strategie ist dann: Man legt eine maximal akzeptable Fehlerschwelle $error fest (z.B. $error=1E-6) und prüft dann nicht auf Gleichheit, sondern auf Einhaltung eines Toleranzbandes.

        // falsch:
        if ($a==$b)
        
        //besser:
        if (abs($a-$b)<$error)
        

        Live long and pros healthy,
         Martin

        --
        Home is where my beer is.
        1. Hallo Martin,

          Die übliche Strategie ist dann: Man legt eine maximal akzeptable Fehlerschwelle $error fest (z.B. $error=1E-6) und prüft dann nicht auf Gleichheit, sondern auf Einhaltung eines Toleranzbandes.

          Also eine Art Fuzzy-Logic 😀 Damit käme ich klar. Ich wußte nicht, ob es etwas "Besseres, Anderes, Klareres" gibt.

          Joachim

      3. Hallo Joachim,

        ok, darauf geht Janoschs Artikel nicht ein. Runden hilft Dir nicht. Wenn Du 42.01 auf 2 Nachkommastellen rundest, ist es immer noch eine Fließkommazahl die nicht genau dargestellt werden kann.

        Was Du tun musst, hängt von deiner Problemstellung ab. Wenn Du immer genau 2 Nachkommastellen hast, z.B. weil es Geld ist, dann rechne nicht mit Euro, sondern mit Cent. D.h. wenn Du "42.01" hereinbekommst, multipliziere es mit 100, addiere 0,5 und mache mit intval() eine Integerzahl draus.

        Könnte man in einer Funktion kapseln:

        function toCents($euro) {
           return intval($euro * 100 + 0.5);
        }
        

        Wenn Du dagegen mit tatsächlichen Fließkommazahlen rechnen musst, dann ist der Test auf Gleichheit nicht mehr realisierbar. Statt dessen musst Du prüfen, ob die beiden Zahlen „nah genug“ beieinander liegen. Was „nah genug“ ist, kommt auf den Einzelfall an und muss bei jeder Rechnung neu überlegt werden. Du musst bedenken, dass ein double 15 signifikante Ziffern hat. Wenn deine Werte im Bereich von 1 liegen, dann liegt die "Auflösung" bei 1E-15. Wenn deine Werte aber im Bereich von zehn Millionen liegen (was 7 Vorkommastellen sind), hast Du nur noch 8 Nachkommastellen. Die Auflösung liegt bei 1E-8.

        Dein Schwellwert für "gleich genug" sollte 1-2 Größenordnungen über der numerischen Auflösung liegen, um Fehlerfortpflanzung in Rechnungen zu berücksichtigen.

        Statt $a == $b kannst Du also zum Beispiel abs($a - $b) < 1E-13 testen. Oder $a < $b - 1E-12 statt $a < $b. Das Spaceship kannst Du bei float vergessen. Und wie gesagt: Statt 1E-12 kann auch mal 1E-8 oder 0.001 korrekt sein, je nach Größenordnung der Zahlen, mit denen Du hantierst.

        Wenn Du vorher mit wilden Formeln herumgerechnet hast, musst Du deine Formeln eigentlich noch auf Fehlerfortpflanzung untersuchen, um zu wissen, wie groß dein Schwellwert für "nicht mehr gleich" sein muss. Je nachdem, wie Du rechnest, kann es auch sein, dass das Umstellen einer Formel den Rechenfehler massiv verbessert oder verschlimmert. Man kann statt Fehlerrechnung aber auch Testprogramme schreiben und die Formel mit verschiedenen Werten testen, um ein Gefühl für den durch Probieren herausbekommen.

        Aus gutem Grund ist Richtig Rechnen (a.k.a. Numerik) eins der gruseligsten Fächer im Mathe- oder Informatik-Studium.

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Hallo Rolf,

          Aus gutem Grund ist Richtig Rechnen (a.k.a. Numerik) eins der gruseligsten Fächer im Mathe- oder Informatik-Studium.

          ich habe das Thema weder im Mathe-LK in der Schule, noch im Informatik-Studium kennengelernt, sondern erst im Beruf bei der Berech Abschätzung von Messunsicherheiten.
          Und ich stimme dir zu: Das ist ... naja, vielleicht nicht gruselig, aber doch sehr lästig.

          Live long and pros healthy,
           Martin

          --
          Home is where my beer is.
          1. Hallo Der Martin,

            ich habe das Thema [Numerik] weder im Mathe-LK in der Schule

            ich kenne nicht einen einzigen Mathelehrplan, in dem das Thema Numerik stünde, wenn man mal von solchen Sachen wie dem Newton-Verfahren absieht, das sicherlich in dem einen oder anderen Bundesland in einem Wahlthema zu finden ist.

            Bis demnächst
            Matthias

            --
            Du kannst das Projekt SELFHTML unterstützen,
            indem du bei Amazon-Einkäufen Amazon smile (Was ist das?) nutzt.
            1. Hallo Matthias,

              Schüler würden, wenn Du ihnen mit den Feinheiten der Fehlerrechnung kommst, schreiend das Gebäude verlassen.

              Rolf

              --
              sumpsi - posui - obstruxi
              1. Hallo,

                Schüler würden, wenn Du ihnen mit den Feinheiten der Fehlerrechnung kommst, schreiend das Gebäude verlassen.

                das kann sein - ich weiß nicht, wie ich als Schüler reagiert hätte. Aber als ich mit den üblichen Methoden der Fehlerrechnung konfrontiert wurde, habe ich erstmal protestiert: Das stimmt doch gar nicht!

                Die traditionelle Methode besagt: Ergibt sich eine Größe als Produkt aus n einzelnen Messgrößen, die alle mit einer gewissen prozentualen Unsicherheit behaftet sind, addiere einfach die Prozentwerte, um die resultierende Unsicherheit zu erhalten.

                Stimmt nicht. Angenommen, ich messe Spannung und Strom mit einer Unsicherheit von je 10%, dann würde ich nach der beschriebenen Methode eine Unsicherheit von 20% für die Leistung bekommen. Tatsächlich sind es aber 21%, denn 1.1² = 1.21.

                In der HF-Messtechnik, wo viele Größen in dB gemessen werden, passt das wieder.

                Live long and pros healthy,
                 Martin

                --
                Home is where my beer is.
        2. Hallo zusammen,

          sehr interessantes Thema und es wundert mich eigentlich, dass ich erst gestern darauf stoße. Dabei habe ich auch in der Vergangenheit oft mit Zahlen, auch Floatwerten gerechnet und sie verglichen.

          Im Grunde ist Deine Funktion toCents auch eine Art "Rundung". Ich hätte mir wirklich vorstellen können, dass PHP hier eigene Funktionen hat. 😀

          Joachim

          1. Moin Joachim,

            sehr interessantes Thema und es wundert mich eigentlich, dass ich erst gestern darauf stoße. Dabei habe ich auch in der Vergangenheit oft mit Zahlen, auch Floatwerten gerechnet und sie verglichen.

            das geht auch oft gut, aber irgendwann stößt man mal auf das Thema. Als Beispiel wird auch oft demonstriert, einen Startwert (z.B. 1.00) in einer Schleife pro Durchlauf um 0.10 zu erhöhen. Meist läuft das schon beim dritten oder vierten Schritt aus dem Ruder.

            Im Grunde ist Deine Funktion toCents auch eine Art "Rundung".

            Ja, aber nur einmalig, um von da an mit ganzzahligen Werten weiterzurechnen und weitere Rundungsfehler auszuschließen.

            Live long and pros healthy,
             Martin

            --
            Home is where my beer is.
        3. Hallo,

          Wenn Du immer genau 2 Nachkommastellen hast, z.B. weil es Geld ist, dann rechne nicht mit Euro, sondern mit Cent. D.h. wenn Du "42.01" hereinbekommst, multipliziere es mit 100, addiere 0,5 und mache mit intval() eine Integerzahl draus.

          Ich meine, mal gehört zu haben, dass das auch nicht der Weisheit letzter Schluss ist. Banken benötigen für Zinsrechnungen weit mehr als Cent-Genauigkeit, möglicherweise reichen Hundertstel-Cent. Aber wenn man Geldbeträge mit 10000 multipliziert, um genaustens rechnen zu können, stößt man viel schneller an speichergrenzen bei großen Beträgen... Ein Dilemma.

          Daher werden meines Wissens Euro und Cent irgendwie getrennt verwaltet. Das kann aber längst veraltet oder Einbildung sein…

          Gruß
          Kalk

          1. Hi,

            Wenn Du immer genau 2 Nachkommastellen hast, z.B. weil es Geld ist, dann rechne nicht mit Euro, sondern mit Cent. D.h. wenn Du "42.01" hereinbekommst, multipliziere es mit 100, addiere 0,5 und mache mit intval() eine Integerzahl draus.

            Ich meine, mal gehört zu haben, dass das auch nicht der Weisheit letzter Schluss ist. Banken benötigen für Zinsrechnungen weit mehr als Cent-Genauigkeit, möglicherweise reichen Hundertstel-Cent.

            das fängt bei den Tankstellen schon an, die zumindest intern auch mit den Zehntel-Cent rechnen.

            Aber wenn man Geldbeträge mit 10000 multipliziert, um genaustens rechnen zu können, stößt man viel schneller an speichergrenzen bei großen Beträgen... Ein Dilemma.

            Tja, irgendeinen Haken gibt's immer. 😉
            Es gibt noch andere Möglichkeiten. Traditionelle Taschenrechner verwenden AFAIK BCD-Arithmetik, d.h. Zahlen werden intern als BCD gespeichert und verarbeitet. So kann man zumindest im Dezimalsystem, das ja die meisten von uns primär verwenden, ohne Rundungsfehler rechnen.

            Daher werden meines Wissens Euro und Cent irgendwie getrennt verwaltet. Das kann aber längst veraltet oder Einbildung sein…

            Ich kann mir nicht vorstellen, was das für einen Vorteil bringen könnte.

            Live long and pros healthy,
             Martin

            --
            Home is where my beer is.
            1. Hi,

              Tja, irgendeinen Haken gibt's immer. 😉
              Es gibt noch andere Möglichkeiten. Traditionelle Taschenrechner verwenden AFAIK BCD-Arithmetik, d.h. Zahlen werden intern als BCD gespeichert und verarbeitet. So kann man zumindest im Dezimalsystem, das ja die meisten von uns primär verwenden, ohne Rundungsfehler rechnen.

              Nö, denn der Speicher ist begrenzt - Brüche wie 1/3 oder 1/7 werden also irgendwo irgendwie gerundet ...

              cu,
              Andreas a/k/a MudGuard

          2. Hallo Tabellenkalk,

            Banken benötigen für Zinsrechnungen weit mehr als Cent-Genauigkeit, möglicherweise reichen Hundertstel-Cent.

            Ich bin damals bei der Euro-Konvertierung unseres Versichertenbestandes nicht beteiligt gewesen, ich weiß aber, dass der Standard-Eurorechner, den wir damals zugekauft hatten (sic!) mit Hundertstel-Cent gerechnet hat. Und ich meine mal irgendwo gelesen zu haben, dass im Buchhaltungswesen das Rechnen mit Hundertstel-Cent und genaue Vorschriften, wann wie zu runden ist, vorgegeben sind. Das mag aber jetzt Fake-Memory sein.

            Euro und Cent getrennt zu verwalten, tja, das ist ggf als Workaround nötig und wird bei Punktrechnung umständlich. Aber nötig ist es nur bei alter Software. Der Klassiker ist hier COBOL mit dem COMP-3 Format, das früher maximal 18 Ziffern konnte (entspricht dem Umfang eines 64-bit Integer). Wenn man sich dem Billiardenbereich nähert, ist man bei 4 Nachkommastellen am Ende. Der IBM Compiler hat die ARITH(EXTEND) Option, dann wird der Code langsamer, kann aber 31 Ziffern verarbeiten. Der .net Decimal Typ kann 28 Ziffern. Mit diesen Grenzen hat vermutlich nur Venezuela ein Problem, aber die haben ja auch Übung in Währungsschnitten.

            Java legt mit BigDecimal deutlich was drauf, und in JavaScript kann man immerhin BigInt verwenden.

            IBM COBOLs Datenbankpartner DB2 speichert 31 stellige Decimals, MS SQL Server speichert DECIMAL bis zu 38 Ziffern und MySQL bis 65.

            Also - machbar ist's auch ohne Cent-Quarantäne.

            Rolf

            --
            sumpsi - posui - obstruxi