Vergleiche Floats in PHP

155

Ich möchte zwei Floats in PHP vergleichen, wie in diesem Beispielcode:

$a = 0.17;
$b = 1 - 0.83; //0.17
if($a == $b ){
 echo 'a and b are same';
}
else {
 echo 'a and b are not same';
}

In diesem Code wird das Ergebnis der elseBedingung anstelle der ifBedingung zurückgegeben, obwohl $aund $bdasselbe sind. Gibt es eine spezielle Möglichkeit, Floats in PHP zu handhaben / zu vergleichen?

Wenn ja, helfen Sie mir bitte, dieses Problem zu lösen.

Oder gibt es ein Problem mit meiner Serverkonfiguration?

Santosh Sonarikar
quelle
Ich verstehe a and b are same. Ist das Ihr vollständiger Code?
Pekka
welche Version? es funktioniert gut für mich.
Gblazex
@Andrey das ist wahrscheinlich, weil der Fall der realen Welt wahrscheinlich komplexer ist als das zitierte Beispiel. Warum nicht als Antwort hinzufügen?
Pekka
2
Hast du die floating-pointTag-Beschreibung gelesen ? stackoverflow.com/tags/floating-point/info Dies ist ein Verhalten, auf das Sie wahrscheinlich in jeder Programmiersprache stoßen würden, wenn Sie Gleitkommazahlen verwenden. Siehe z. B. stackoverflow.com/questions/588004/is-javascripts-math-broken
Piskvor verließ das Gebäude am

Antworten:

232

Wenn Sie es so machen, sollten sie gleich sein. Beachten Sie jedoch, dass ein Merkmal von Gleitkommawerten darin besteht, dass Berechnungen, die zum gleichen Wert zu führen scheinen , nicht tatsächlich identisch sein müssen. Wenn $aes sich also um ein Literal handelt .17und $bdurch eine Berechnung dort ankommt, kann es durchaus sein, dass sie unterschiedlich sind, obwohl beide den gleichen Wert anzeigen.

Normalerweise vergleichen Sie niemals Gleitkommawerte für eine solche Gleichheit. Sie müssen einen kleinsten akzeptablen Unterschied verwenden:

if (abs(($a-$b)/$b) < 0.00001) {
  echo "same";
}

Sowas in der Art.

Joey
quelle
21
IN ACHT NEHMEN! Die Wahl eines festen Epsilons ist ein schlechter Weg, nur weil es klein aussieht. Dieser Vergleich wird bei vielen Präzisionsfehlern wahr sein, wenn die Zahlen klein sind. Ein korrekter Weg wäre zu überprüfen, ob der relative Fehler kleiner als das Epsilon ist. abs($a-$b)>abs(($a-$b)/$b)
Piet Bijl
1
@Alexandru: Ich weiß was du meinst, aber PHP ist in dieser Hinsicht nicht allein. Hier müssen Sie zwei Anwendungsfälle unterscheiden: Anzeigen einer Nummer für einen Benutzer. In diesem Fall ist die Anzeige 0.10000000000000000555111512312578270211815834045410156normalerweise sinnlos und sie würden es vorziehen, 0.1stattdessen. Und schreiben Sie eine Nummer, damit sie genauso wieder gelesen werden kann. Wie Sie sehen, ist es nicht so eindeutig, wie Sie es sich vorstellen. Und für die Aufzeichnung möchten Sie immer noch Gleitkommazahlen vergleichen, wie ich gezeigt habe, weil Sie zu $aund $büber verschiedene Berechnungen gelangen können, die sie unterschiedlich machen können.
Joey
2
Es gibt noch einige Randfälle, bei denen dieser Test fehlschlägt. Wie a=b=0und wenn ader kleinstmögliche keiner Null positive Wert ist und bist der kleinste mögliche nicht Null negativer Wert ist , wird der Test falsch ausfallen. Einige gute Informationen hier: float-point-gui.de/errors/comparison
Dom
13
Warum weitergeben $b? das PHP - Handbuch tat nur if(abs($a-$b) < $epsilon) das MySQL - Handbuch haben auch das gleicheHAVING ABS(a - b) <= 0.0001
Accountant م
1
@CaslavSabani: Dies ist ein relativer, kein absoluter Fehler. Es ist immer noch kaputt (besonders wenn $a == $b == 0, aber es ist bereits viel allgemeiner als ein absoluter Fehler. Wenn $aund $bin Millionenhöhe, dann EPSILONmüssten Sie ganz anders sein als wenn $aund $birgendwo in der Nähe sind 0. Siehe Doms Link oben für eine bessere Diskussion über dies.
Joey
64

Lesen Sie zuerst die rote Warnung im Handbuch . Sie dürfen Floats niemals auf Gleichheit vergleichen. Sie sollten die Epsilon-Technik verwenden.

Beispielsweise:

if (abs($a-$b) < PHP_FLOAT_EPSILON) {  }

Dabei PHP_FLOAT_EPSILONist die Konstante eine sehr kleine Zahl (Sie müssen sie in alten PHP-Versionen vor 7.2 definieren).

Andrey
quelle
2
Ist EPSILON in diesem Fall das Maschinen-Epsilon, das ungefähr 2.2204460492503E-16 ist? Und funktioniert dieser Vergleich für zwei Floats beliebiger Größe?
Michael Cordingley
1
@MichaelCordingley Nein, EPSILONhier ist eine beliebige benutzerdefinierte Konstante. PHP verfügt nicht über eine integrierte Konstante, die die spezifische Idee einer Architektur von epsilon darstellt. (Siehe auch get_defined_constants.)
Bischof
5
PHP_FLOAT_EPSILONKleinste darstellbare positive Zahl x, so dass x + 1.0! = 1.0. Verfügbar ab PHP 7.2.0.
Code4R7
2
Dies funktioniert in diesem Fall nicht: $ a = 270.10 + 20.10; $ b = 290,20; if (abs ($ a- $ b) <PHP_FLOAT_EPSILON) {echo 'same'; }
NemoXP
@NemoXP, da diese Ausdrücke unterschiedliche Zahlen erzeugen. echo $a - $b; /* 5.6843418860808E-14 */ echo PHP_FLOAT_EPSILON; /* 2.2204460492503E-16 */Die Frage ist, wie Sie "gleich" für Ihre Anwendung definieren möchten, wie nahe die Zahlen sein sollten, um als gleich zu gelten.
Andrey
28

Oder versuchen Sie, bc mathematische Funktionen zu verwenden:

<?php
$a = 0.17;
$b = 1 - 0.83; //0.17

echo "$a == $b (core comp oper): ", var_dump($a==$b);
echo "$a == $b (with bc func)  : ", var_dump( bccomp($a, $b, 3)==0 );

Ergebnis:

0.17 == 0.17 (core comp oper): bool(false)
0.17 == 0.17 (with bc func)  : bool(true)
Mario
quelle
2
Bei Ihrer Verwendung von bccomp haben Sie die "Skala" verpasst, also vergleichen Sie tatsächlich 0 mit 0 gemäß dem Handbuch: php.net/manual/en/function.bccomp.php
stefancarlton
Ich mag das. Die meisten Lösungen scheinen auf Rundung und Genauigkeitsverlust zu beruhen, aber ich habe es mit Breiten- und Längenkoordinaten mit 12 Präzisionspunkten zu tun, und dies scheint sie genau zu vergleichen, ohne dass Anpassungen erforderlich sind.
Rikaelus
Was ist mit Leistung? bccomp()nimmt Zeichenfolgen als Argumente. Wie auch immer, Sie könnten es PHP_FLOAT_DIGfür das Skalierungsargument verwenden.
Code4R7
18

Seien Sie, wie bereits erwähnt, sehr vorsichtig, wenn Sie Gleitkomma-Vergleiche (ob gleich, größer als oder kleiner als) in PHP durchführen. Wenn Sie jedoch immer nur an einigen wichtigen Stellen interessiert sind, können Sie Folgendes tun:

$a = round(0.17, 2);
$b = round(1 - 0.83, 2); //0.17
if($a == $b ){
    echo 'a and b are same';
}
else {
    echo 'a and b are not same';
}

Die Verwendung der Rundung auf 2 Dezimalstellen (oder 3 oder 4) führt zum erwarteten Ergebnis.

Michael Butler
quelle
1
Zusätzliches Wort der Warnung, ich würde nicht empfehlen, Ihre Codebasis mit Aussagen wie diesen zu verunreinigen. Wenn Sie einen losen Float-Vergleich durchführen möchten, machen Sie eine Methode wie diese, loose_float_comparedamit klar ist, was los ist.
Michael Butler
PHPs native bccomp($a, $b, 2)ist Ihrer Lösung überlegen. In diesem Beispiel ist die 2 die Genauigkeit. Sie können eine beliebige Anzahl von Gleitkommazahlen festlegen, die Sie vergleichen möchten.
John Miller
@ JohnMiller Ich bin nicht anderer Meinung als Sie, aber bccomp ist standardmäßig nicht verfügbar. Dazu muss entweder ein Kompilierungsflag aktiviert oder eine Erweiterung installiert sein. Nicht Teil des Kerns.
Michael Butler
17

Es wäre besser, einen nativen PHP-Vergleich zu verwenden :

bccomp($a, $b, 3)
// Third parameter - the optional scale parameter
// is used to set the number of digits after the decimal place
// which will be used in the comparison. 

Gibt 0 zurück, wenn die beiden Operanden gleich sind, 1, wenn der linke Operand größer als der rechte Operand ist, andernfalls -1.

FieryCat
quelle
10

Wenn Sie Gleitkommawerte zum Vergleich mit Gleichheit haben, können Sie das Risiko einer internen Rundungsstrategie des Betriebssystems, der Sprache, des Prozessors usw. auf einfache Weise vermeiden , indem Sie die Zeichenfolgendarstellung der Werte vergleichen.

Sie können eine der folgenden Methoden verwenden, um das gewünschte Ergebnis zu erzielen: https://3v4l.org/rUrEq

String Type Casting

if ( (string) $a === (string) $b) {  }

String-Verkettung

if ('' . $a === '' . $b) {  }

strval Funktion

if (strval($a) === strval($b)) {  }

String-Darstellungen sind bei der Überprüfung der Gleichheit viel weniger pingelig als Floats.

Ame Nomade
quelle
oder if (strval ($ a) === strval ($ b)) {…} wenn Sie die ursprünglichen Werte nicht konvertieren möchten
Ekonoval
Nun, meine ursprüngliche Antwort war: if (''. $ A === ''. $ B) {…}, aber jemand hat sie bearbeitet. Also ...
Ame Nomade
1
@Ekonoval Könnten Sie bitte Ihre Änderung näher erläutern? Es scheint, dass Sie behaupten, dass die (string)Cast-Operation als Referenz ausgeführt wird und die ursprüngliche Deklaration geändert wird. Wenn ja, ist das nicht der Fall. 3v4l.org/Craas
fyrye
@fyrye Ja, ich glaube ich habe mich geirrt, beide Ansätze liefern das gleiche Ergebnis.
Ekonoval
Die Antwort wurde aktualisiert, um eine Beispielverwendung und alle Beispiele der anderen Änderungen zusammen mit dem Original
anzugeben
4

Wenn Sie eine kleine, endliche Anzahl von Dezimalstellen haben, die akzeptabel sind, funktioniert Folgendes gut (wenn auch mit einer langsameren Leistung als die Epsilon-Lösung):

$a = 0.17;
$b = 1 - 0.83; //0.17

if (number_format($a, 3) == number_format($b, 3)) {
    echo 'a and b are same';
} else {
    echo 'a and b are not same';
}
dtbarne
quelle
4

Dies funktioniert bei mir unter PHP 5.3.27.

$payments_total = 123.45;
$order_total = 123.45;

if (round($payments_total, 2) != round($order_total, 2)) {
   // they don't match
}
crmpicco
quelle
3

Für PHP 7.2 können Sie mit PHP_FLOAT_EPSILON ( http://php.net/manual/en/reserved.constants.php ) arbeiten:

if(abs($a-$b) < PHP_FLOAT_EPSILON){
   echo 'a and b are same';
}
Gladhon
quelle
Gute Lösung. Aber: 1- Erfordert Aktualisierung PHP 7.2 , die nicht jeder leicht tun kann für bestehende großes / Altanlagen 2- Dies funktioniert nur für ==und !=aber nicht >, >=, <,<=
evilReiko
2

Wenn Sie es einfach so schreiben, wird es wahrscheinlich funktionieren, also stelle ich mir vor, Sie haben es für die Frage vereinfacht. (Und die Frage einfach und präzise zu halten, ist normalerweise eine sehr gute Sache.)

In diesem Fall stelle ich mir jedoch vor, dass ein Ergebnis eine Berechnung und ein Ergebnis eine Konstante ist.

Dies verstößt gegen eine Grundregel der Gleitkommaprogrammierung: Führen Sie niemals Gleichheitsvergleiche durch.

Die Gründe dafür sind etwas subtil 1, aber es ist wichtig, sich daran zu erinnern, dass sie normalerweise nicht funktionieren (außer ironischerweise für ganzzahlige Werte) und dass die Alternative ein unscharfer Vergleich im Sinne von:

if abs(a - y) < epsilon



1. Eines der Hauptprobleme ist die Art und Weise, wie wir Zahlen in Programme schreiben. Wir schreiben sie als Dezimalzeichenfolgen, und daher haben die meisten von uns geschriebenen Brüche keine exakten Maschinendarstellungen. Sie haben keine exakten endlichen Formen, weil sie sich binär wiederholen. Jeder Maschinenbruch ist eine rationale Zahl der Form x / 2 n . Jetzt sind die Konstanten dezimal und jede Dezimalkonstante ist eine rationale Zahl der Form x / (2 n * 5 m ). Die 5- m- Zahlen sind ungerade, daher gibt es für keinen von ihnen einen 2- n- Faktor. Nur wenn m == 0 ist, gibt es eine endliche Darstellung sowohl in der binären als auch in der dezimalen Erweiterung des Bruchs. 1,25 ist also genau, weil es 5 / (2 2 * 5 0 ist) aber 0.1 ist nicht, weil es 1 / (2 0 * 5 1 ) ist. Tatsächlich sind in der Serie 1.01 .. 1.99 nur 3 der Zahlen genau darstellbar: 1.25, 1.50 und 1.75.

DigitalRoss
quelle
DigitalRoss ist in Ihrem Kommentar nur schwer zu verstehen, aber es ist sehr informativ. Und ich werde diese Begriffe googeln. Danke :)
Santosh Sonarikar
Ist es nicht sicher genug, Vergleiche mit Floats durchzuführen, vorausgesetzt, Sie runden das Ergebnis jedes Mal ab und befinden sich innerhalb einiger wichtiger Stellen? Mit anderen Wortenround($float, 3) == round($other, 3)
Michael Butler
2

Hier ist die Lösung zum Vergleichen von Gleitkomma- oder Dezimalzahlen

//$fd['someVal'] = 2.9;
//$i for loop variable steps 0.1
if((string)$fd['someVal']== (string)$i)
{
    //Equal
}

Wirf eine decimalVariable auf stringund es wird dir gut gehen.

Natalie Rey
quelle
1

Der Vergleich von Floats auf Gleichheit hat einen naiven O (n) -Algorithmus.

Sie müssen jeden Gleitkommawert in eine Zeichenfolge konvertieren und dann jede Ziffer beginnend von der linken Seite der Zeichenfolgendarstellung jedes Gleitkommas mit ganzzahligen Vergleichsoperatoren vergleichen. PHP überträgt die Ziffer an jeder Indexposition vor dem Vergleich automatisch auf eine Ganzzahl. Die erste Ziffer, die größer als die andere ist, unterbricht die Schleife und deklariert den Float, zu dem sie gehört, als die größere der beiden. Im Durchschnitt wird es 1/2 * n Vergleiche geben. Für gleich große Floats gibt es n Vergleiche. Dies ist das Worst-Case-Szenario für den Algorithmus. Das beste Szenario ist, dass die erste Ziffer jedes Floats unterschiedlich ist und nur einen Vergleich verursacht.

Sie können INTEGER COMPARISON OPERATORS nicht für Float-Rohwerte verwenden, um nützliche Ergebnisse zu erzielen. Die Ergebnisse solcher Operationen haben keine Bedeutung, da Sie keine ganzen Zahlen vergleichen. Sie verletzen die Domäne jedes Operators, was zu bedeutungslosen Ergebnissen führt. Dies gilt auch für den Delta-Vergleich.

Verwenden Sie Ganzzahlvergleichsoperatoren für das, wofür sie entwickelt wurden: Vergleichen von Ganzzahlen.

VEREINFACHTE LÖSUNG:

<?php

function getRand(){
  return ( ((float)mt_rand()) / ((float) mt_getrandmax()) );
 }

 $a = 10.0 * getRand();
 $b = 10.0 * getRand();

 settype($a,'string');
 settype($b,'string');

 for($idx = 0;$idx<strlen($a);$idx++){
  if($a[$idx] > $b[$idx]){
   echo "{$a} is greater than {$b}.<br>";
   break;
  }
  else{
   echo "{$b} is greater than {$a}.<br>";
   break;
  }
 }

?>
Kyle
quelle
1

2019

TL; DR

Verwenden Sie meine Funktion unten wie folgt if(cmpFloats($a, '==', $b)) { ... }

  • Einfach zu lesen / schreiben / ändern: cmpFloats($a, '<=', $b)vs.bccomp($a, $b) <= -1
  • Keine Abhängigkeiten erforderlich.
  • Funktioniert mit jeder PHP-Version.
  • Funktioniert mit negativen Zahlen.
  • Funktioniert mit der längsten Dezimalstelle, die Sie sich vorstellen können.
  • Nachteil: Etwas langsamer als bccomp ()

Zusammenfassung

Ich werde das Geheimnis enthüllen.

$a = 0.17;
$b = 1 - 0.83;// 0.17 (output)
              // but actual value internally is: 0.17000000000000003996802888650563545525074005126953125
if($a == $b) {
    echo 'same';
} else {
    echo 'different';
}
// Output: different

Wenn Sie also Folgendes versuchen, ist es gleich:

if($b == 0.17000000000000003) {
    echo 'same';
} else {
    echo 'different';
}
// Output "same"

Wie erhalte ich den tatsächlichen Wert von float?

$b = 1 - 0.83;
echo $b;// 0.17
echo number_format($a, 100);// 0.1700000000000000399680288865056354552507400512695312500000000000000000000000000000000000000000000000

Wie können Sie vergleichen?

  1. Verwenden Sie BC Math-Funktionen . (Sie werden immer noch viele wtf-aha-gotcha Momente bekommen)
  2. Sie können die Antwort von @ Gladhon mit PHP_FLOAT_EPSILON (PHP 7.2) versuchen.
  3. Wenn Sie Floats mit ==und vergleichen und !=sie in Strings typisieren können, sollte dies perfekt funktionieren:

Geben Sie cast mit string ein :

$b = 1 - 0.83;
if((string)$b === (string)0.17) {
    echo 'if';
} else {
    echo 'else';
}
// it will output "if"

Oder typisiert mit number_format():

$b = 1 - 0.83;
if(number_format($b, 3) === number_format(0.17, 3)) {
    echo 'if';
} else {
    echo 'else';
}
// it will output "if"

Warnung:

Vermeiden Sie Lösungen, bei denen Floats mathematisch manipuliert (multipliziert, dividiert usw.) und dann verglichen werden. Meistens lösen sie einige Probleme und führen andere Probleme ein.


Vorgeschlagene Lösung

Ich habe eine reine PHP-Funktion erstellt (keine Abhängigkeiten / Bibliotheken / Erweiterungen erforderlich). Überprüft und vergleicht jede Ziffer als Zeichenfolge. Funktioniert auch mit negativen Zahlen.

/**
 * Compare numbers (floats, int, string), this function will compare them safely
 * @param Float|Int|String  $a         (required) Left operand
 * @param String            $operation (required) Operator, which can be: "==", "!=", ">", ">=", "<" or "<="
 * @param Float|Int|String  $b         (required) Right operand
 * @param Int               $decimals  (optional) Number of decimals to compare
 * @return boolean                     Return true if operation against operands is matching, otherwise return false
 * @throws Exception                   Throws exception error if passed invalid operator or decimal
 */
function cmpFloats($a, $operation, $b, $decimals = 15) {
    if($decimals < 0) {
        throw new Exception('Invalid $decimals ' . $decimals . '.');
    }
    if(!in_array($operation, ['==', '!=', '>', '>=', '<', '<='])) {
        throw new Exception('Invalid $operation ' . $operation . '.');
    }

    $aInt = (int)$a;
    $bInt = (int)$b;

    $aIntLen = strlen((string)$aInt);
    $bIntLen = strlen((string)$bInt);

    // We'll not used number_format because it inaccurate with very long numbers, instead will use str_pad and manipulate it as string
    $aStr = (string)$a;//number_format($a, $decimals, '.', '');
    $bStr = (string)$b;//number_format($b, $decimals, '.', '');

    // If passed null, empty or false, then it will be empty string. So change it to 0
    if($aStr === '') {
        $aStr = '0';
    }
    if($bStr === '') {
        $bStr = '0';
    }

    if(strpos($aStr, '.') === false) {
        $aStr .= '.';
    }
    if(strpos($bStr, '.') === false) {
        $bStr .= '.';
    }

    $aIsNegative = strpos($aStr, '-') !== false;
    $bIsNegative = strpos($bStr, '-') !== false;

    // Append 0s to the right
    $aStr = str_pad($aStr, ($aIsNegative ? 1 : 0) + $aIntLen + 1 + $decimals, '0', STR_PAD_RIGHT);
    $bStr = str_pad($bStr, ($bIsNegative ? 1 : 0) + $bIntLen + 1 + $decimals, '0', STR_PAD_RIGHT);

    // If $decimals are less than the existing float, truncate
    $aStr = substr($aStr, 0, ($aIsNegative ? 1 : 0) + $aIntLen + 1 + $decimals);
    $bStr = substr($bStr, 0, ($bIsNegative ? 1 : 0) + $bIntLen + 1 + $decimals);

    $aDotPos = strpos($aStr, '.');
    $bDotPos = strpos($bStr, '.');

    // Get just the decimal without the int
    $aDecStr = substr($aStr, $aDotPos + 1, $decimals);
    $bDecStr = substr($bStr, $bDotPos + 1, $decimals);

    $aDecLen = strlen($aDecStr);
    //$bDecLen = strlen($bDecStr);

    // To match 0.* against -0.*
    $isBothZeroInts = $aInt == 0 && $bInt == 0;

    if($operation === '==') {
        return $aStr === $bStr ||
               $isBothZeroInts && $aDecStr === $bDecStr;
    } else if($operation === '!=') {
        return $aStr !== $bStr ||
               $isBothZeroInts && $aDecStr !== $bDecStr;
    } else if($operation === '>') {
        if($aInt > $bInt) {
            return true;
        } else if($aInt < $bInt) {
            return false;
        } else {// Ints equal, check decimals
            if($aDecStr === $bDecStr) {
                return false;
            } else {
                for($i = 0; $i < $aDecLen; ++$i) {
                    $aD = (int)$aDecStr[$i];
                    $bD = (int)$bDecStr[$i];
                    if($aD > $bD) {
                        return true;
                    } else if($aD < $bD) {
                        return false;
                    }
                }
            }
        }
    } else if($operation === '>=') {
        if($aInt > $bInt ||
           $aStr === $bStr ||
           $isBothZeroInts && $aDecStr === $bDecStr) {
            return true;
        } else if($aInt < $bInt) {
            return false;
        } else {// Ints equal, check decimals
            if($aDecStr === $bDecStr) {// Decimals also equal
                return true;
            } else {
                for($i = 0; $i < $aDecLen; ++$i) {
                    $aD = (int)$aDecStr[$i];
                    $bD = (int)$bDecStr[$i];
                    if($aD > $bD) {
                        return true;
                    } else if($aD < $bD) {
                        return false;
                    }
                }
            }
        }
    } else if($operation === '<') {
        if($aInt < $bInt) {
            return true;
        } else if($aInt > $bInt) {
            return false;
        } else {// Ints equal, check decimals
            if($aDecStr === $bDecStr) {
                return false;
            } else {
                for($i = 0; $i < $aDecLen; ++$i) {
                    $aD = (int)$aDecStr[$i];
                    $bD = (int)$bDecStr[$i];
                    if($aD < $bD) {
                        return true;
                    } else if($aD > $bD) {
                        return false;
                    }
                }
            }
        }
    } else if($operation === '<=') {
        if($aInt < $bInt || 
           $aStr === $bStr ||
           $isBothZeroInts && $aDecStr === $bDecStr) {
            return true;
        } else if($aInt > $bInt) {
            return false;
        } else {// Ints equal, check decimals
            if($aDecStr === $bDecStr) {// Decimals also equal
                return true;
            } else {
                for($i = 0; $i < $aDecLen; ++$i) {
                    $aD = (int)$aDecStr[$i];
                    $bD = (int)$bDecStr[$i];
                    if($aD < $bD) {
                        return true;
                    } else if($aD > $bD) {
                        return false;
                    }
                }
            }
        }
    }
}

$a = 1 - 0.83;// 0.17
$b = 0.17;
if($a == $b) {
    echo 'same';
} else {
    echo 'different';
}
// Output: different (wrong)

if(cmpFloats($a, '==', $b)) {
    echo 'same';
} else {
    echo 'different';
}
// Output: same (correct)
böseReiko
quelle
1

Funktion von @evilReiko haben einige Fehler wie diese:

cmpFloats(-0.1, '==', 0.1); // Expected: false, actual: true
cmpFloats(-0.1, '<', 0.1); // Expected: true, actual: false
cmpFloats(-4, '<', -3); // Expected: true, actual: true
cmpFloats(-5.004, '<', -5.003); // Expected: true, actual: false

In meiner Funktion habe ich diese Fehler behoben, aber in einigen Fällen gibt diese Funktion falsche Antworten zurück:

cmpFloats(0.0000001, '==', -0.0000001); // Expected: false, actual: true
cmpFloats(843994202.303411, '<', 843994202.303413); // Expected: true, actual: false
cmpFloats(843994202.303413, '>', 843994202.303411); // Expected: true, actual: false

Feste Funktion zum Vergleichen von Floats

function cmpFloats($a, $operation, $b, $decimals = 15)
{
    if ($decimals < 0) {
        throw new Exception('Invalid $decimals ' . $decimals . '.');
    }
    if (!in_array($operation, ['==', '!=', '>', '>=', '<', '<='])) {
        throw new Exception('Invalid $operation ' . $operation . '.');
    }

    $aInt = (int)$a;
    $bInt = (int)$b;

    $aIntLen = strlen((string)$aInt);
    $bIntLen = strlen((string)$bInt);

    // We'll not used number_format because it inaccurate with very long numbers, instead will use str_pad and manipulate it as string
    $aStr = (string)$a;//number_format($a, $decimals, '.', '');
    $bStr = (string)$b;//number_format($b, $decimals, '.', '');

    // If passed null, empty or false, then it will be empty string. So change it to 0
    if ($aStr === '') {
        $aStr = '0';
    }
    if ($bStr === '') {
        $bStr = '0';
    }

    if (strpos($aStr, '.') === false) {
        $aStr .= '.';
    }
    if (strpos($bStr, '.') === false) {
        $bStr .= '.';
    }

    $aIsNegative = strpos($aStr, '-') !== false;
    $bIsNegative = strpos($bStr, '-') !== false;

    // Append 0s to the right
    $aStr = str_pad($aStr, ($aIsNegative ? 1 : 0) + $aIntLen + 1 + $decimals, '0', STR_PAD_RIGHT);
    $bStr = str_pad($bStr, ($bIsNegative ? 1 : 0) + $bIntLen + 1 + $decimals, '0', STR_PAD_RIGHT);

    // If $decimals are less than the existing float, truncate
    $aStr = substr($aStr, 0, ($aIsNegative ? 1 : 0) + $aIntLen + 1 + $decimals);
    $bStr = substr($bStr, 0, ($bIsNegative ? 1 : 0) + $bIntLen + 1 + $decimals);

    $aDotPos = strpos($aStr, '.');
    $bDotPos = strpos($bStr, '.');

    // Get just the decimal without the int
    $aDecStr = substr($aStr, $aDotPos + 1, $decimals);
    $bDecStr = substr($bStr, $bDotPos + 1, $decimals);

    $aDecLen = strlen($aDecStr);
    //$bDecLen = strlen($bDecStr);

    // To match 0.* against -0.*
    $isBothZeroInts = $aInt == 0 && $bInt == 0;

    if ($operation === '==') {
        return $aStr === $bStr ||
            ($isBothZeroInts && $aDecStr === $bDecStr && $aIsNegative === $bIsNegative);
    } elseif ($operation === '!=') {
        return $aStr !== $bStr ||
            $isBothZeroInts && $aDecStr !== $bDecStr;
    } elseif ($operation === '>') {
        if ($aInt > $bInt) {
            return true;
        } elseif ($aInt < $bInt) {
            return false;
        } else {// Ints equal, check decimals
            if ($aIsNegative !== $bIsNegative) {
                return (!$aIsNegative && $bIsNegative);
            }

            if ($aDecStr === $bDecStr) {
                return false;
            } else {
                for ($i = 0; $i < $aDecLen; ++$i) {
                    $aD = (int)$aDecStr[$i];
                    $bD = (int)$bDecStr[$i];

                    if ($aIsNegative && $bIsNegative) {
                        if ($aD < $bD) {
                            return true;
                        } elseif ($aD > $bD) {
                            return false;
                        }
                    } else {
                        if ($aD > $bD) {
                            return true;
                        } elseif ($aD < $bD) {
                            return false;
                        }
                    }
                }
            }
        }
    } elseif ($operation === '>=') {
        if ($aInt > $bInt ||
            $aStr === $bStr ||
            $isBothZeroInts && $aDecStr === $bDecStr) {
            return true;
        } elseif ($aInt < $bInt) {
            return false;
        } else {// Ints equal, check decimals
            if ($aIsNegative !== $bIsNegative) {
                return (!$aIsNegative && $bIsNegative);
            }

            if ($aDecStr === $bDecStr) {// Decimals also equal
                return true;
            } else {
                for ($i = 0; $i < $aDecLen; ++$i) {
                    $aD = (int)$aDecStr[$i];
                    $bD = (int)$bDecStr[$i];

                    if ($aIsNegative && $bIsNegative) {
                        if ($aD < $bD) {
                            return true;
                        } elseif ($aD > $bD) {
                            return false;
                        }
                    } else {
                        if ($aD > $bD) {
                            return true;
                        } elseif ($aD < $bD) {
                            return false;
                        }
                    }
                }
            }
        }
    } elseif ($operation === '<') {
        if ($aInt < $bInt) {
            return true;
        } elseif ($aInt > $bInt) {
            return false;
        } else {// Ints equal, check decimals
            if ($aIsNegative !== $bIsNegative) {
                return ($aIsNegative && !$bIsNegative);
            }

            if ($aDecStr === $bDecStr) {
                return false;
            } else {
                for ($i = 0; $i < $aDecLen; ++$i) {
                    $aD = (int)$aDecStr[$i];
                    $bD = (int)$bDecStr[$i];

                    if ($aIsNegative && $bIsNegative) {
                        if ($aD > $bD) {
                            return true;
                        } elseif ($aD < $bD) {
                            return false;
                        }
                    } else {
                        if ($aD < $bD) {
                            return true;
                        } elseif ($aD > $bD) {
                            return false;
                        }
                    }
                }
            }
        }
    } elseif ($operation === '<=') {
        if ($aInt < $bInt ||
            $aStr === $bStr ||
            $isBothZeroInts && $aDecStr === $bDecStr) {
            return true;
        } elseif ($aInt > $bInt) {
            return false;
        } else {// Ints equal, check decimals
            if ($aIsNegative !== $bIsNegative) {
                return ($aIsNegative && !$bIsNegative);
            }

            if ($aDecStr === $bDecStr) {// Decimals also equal
                return true;
            } else {
                for ($i = 0; $i < $aDecLen; ++$i) {
                    $aD = (int)$aDecStr[$i];
                    $bD = (int)$bDecStr[$i];

                    if ($aIsNegative && $bIsNegative) {
                        if ($aD > $bD) {
                            return true;
                        } elseif ($aD < $bD) {
                            return false;
                        }
                    } else {
                        if ($aD < $bD) {
                            return true;
                        } elseif ($aD > $bD) {
                            return false;
                        }
                    }
                }
            }
        }
    }
}

Antwort auf Ihre Frage

$a = 1 - 0.83;// 0.17
$b = 0.17;
if($a == $b) {
    echo 'same';
} else {
    echo 'different';
}
// Output: different (wrong)

if(cmpFloats($a, '==', $b)) {
    echo 'same';
} else {
    echo 'different';
}
// Output: same (correct)
Volodymyr
quelle
0

Hier ist eine nützliche Klasse aus meiner persönlichen Bibliothek für den Umgang mit Gleitkommazahlen. Sie können es nach Ihren Wünschen tweeken und eine beliebige Lösung in die Klassenmethoden einfügen :-).

/**
 * A class for dealing with PHP floating point values.
 * 
 * @author Anthony E. Rutledge
 * @version 12-06-2018
 */
final class Float extends Number
{
    // PHP 7.4 allows for property type hints!

    private const LESS_THAN = -1;
    private const EQUAL = 0;
    private const GREATER_THAN = 1;

    public function __construct()
    {

    }

    /**
     * Determines if a value is an float.
     * 
     * @param mixed $value
     * @return bool
     */
    public function isFloat($value): bool
    {
        return is_float($value);
    }

    /**
     * A method that tests to see if two float values are equal.
     * 
     * @param float $y1
     * @param float $y2
     * @return bool
     */
    public function equals(float $y1, float $y2): bool
    {
        return (string) $y1 === (string) $y2;
    }

    /**
     * A method that tests to see if two float values are not equal.
     * 
     * @param float $y1
     * @param float $y2
     * @return bool
     */
    public function isNotEqual(float $y1, float $y2): bool
    {
        return !$this->equals($y1, $y2);
    }

    /**
     * Gets the bccomp result.
     * 
     * @param float $y1
     * @param float $y2
     * @return int
     */
    private function getBccompResult(float $y1, float $y2): int
    {
        $leftOperand = (string) $y1;
        $rightOperand = (string) $y2;

        // You should check the format of the float before using it.

        return bccomp($leftOperand, $rightOperand);
    }

    /**
     * A method that tests to see if y1 is less than y2.
     * 
     * @param float $y1
     * @param float $y2
     * @return bool
     */
    public function isLess(float $y1, float $y2): bool
    {
        return ($this->getBccompResult($y1, $y2) === self::LESS_THAN);
    }

    /**
     * A method that tests to see if y1 is less than or equal to y2.
     * 
     * @param float $y1
     * @param float $y2
     * @return bool
     */
    public function isLessOrEqual(float $y1, float $y2): bool
    {
        $bccompResult = $this->getBccompResult($y1, $y2);
        return ($bccompResult === self::LESS_THAN || $bccompResult === self::EQUALS);
    }

    /**
     * A method that tests to see if y1 is greater than y2.
     * 
     * @param float $y1
     * @param float $y2
     * @return bool
     */
    public function isGreater(float $y1, float $y2): bool
    {
        return ($this->getBccompResult($y1, $y2) === self::GREATER_THAN);
    }

    /**
     * A method that tests to see if y1 is greater than or equal to y2.
     * 
     * @param float $y1
     * @param float $y2
     * @return bool
     */
    public function isGreaterOrEqual(float $y1, float $y2): bool
    {
        $bccompResult = $this->getBccompResult($y1, $y2);
        return ($bccompResult === self::GREATER_THAN || $bccompResult === self::EQUALS);
    }

    /**
     * Returns a valid PHP float value, casting if necessary.
     * 
     * @param mixed $value
     * @return float
     *
     * @throws InvalidArgumentException
     * @throws UnexpectedValueException
     */
    public function getFloat($value): float
    {
        if (! (is_string($value) || is_int($value) || is_bool($value))) {
            throw new InvalidArgumentException("$value should not be converted to float!");
        }

        if ($this->isFloat($value)) {
            return $value;
        }

        $newValue = (float) $value;

        if ($this->isNan($newValue)) {
            throw new UnexpectedValueException("The value $value was converted to NaN!");
        }

        if (!$this->isNumber($newValue)) {
            throw new UnexpectedValueException("The value $value was converted to something non-numeric!");
        }

        if (!$this->isFLoat($newValue)) {
            throw new UnexpectedValueException("The value $value was not converted to a floating point value!");
        }

        return $newValue;
    }
}
?>
Anthony Rutledge
quelle
0

Einfache Antwort:

if( floatval( (string) $a ) >= floatval( (string) $b) ) { //do something }
Nader
quelle