Comparez les flotteurs en PHP

157

Je veux comparer deux flottants en PHP, comme dans cet exemple de code:

$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';
}

Dans ce code, il renvoie le résultat de la elsecondition au lieu de la ifcondition, même si $aet $bsont identiques. Existe-t-il un moyen spécial de gérer / comparer les flottants en PHP?

Si oui, aidez-moi à résoudre ce problème.

Ou y a-t-il un problème avec la configuration de mon serveur?

Santosh Sonarikar
la source
Je reçois a and b are same. Est-ce votre code complet?
Pekka
quelle version? Ça fonctionne bien pour moi.
gblazex
@Andrey c'est probablement ça parce que le cas réel est susceptible d'être plus complexe que l'exemple cité. Pourquoi ne pas l'ajouter comme réponse?
Pekka
2
Avez-vous lu la floating-pointdescription du tag? stackoverflow.com/tags/floating-point/info C'est un comportement que vous rencontrerez probablement dans n'importe quel langage de programmation, lorsque vous utilisez des nombres à virgule flottante. Voir par exemple stackoverflow.com/questions/588004/is-javascripts-math-broken
Piskvor a quitté le bâtiment

Réponses:

232

Si vous le faites comme ça, ils devraient être les mêmes. Mais notez qu'une caractéristique des valeurs à virgule flottante est que les calculs qui semblent aboutir à la même valeur n'ont pas besoin d'être réellement identiques. Donc, si $aest un littéral .17et $by arrive par un calcul, il se peut bien qu'ils soient différents, bien que les deux affichent la même valeur.

Habituellement, vous ne comparez jamais les valeurs à virgule flottante pour l'égalité comme celle-ci, vous devez utiliser une plus petite différence acceptable:

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

Quelque chose comme ca.

Joey
la source
21
IL FAUT SE MÉFIER! Choisir un epsilon fixe est une mauvaise façon simplement parce qu'il semble petit, cette comparaison retournera vrai dans beaucoup d'erreurs de précision lorsque les nombres sont petits. Une manière correcte serait de vérifier si l'erreur relative est plus petite que l'epsilon. abs($a-$b)>abs(($a-$b)/$b)
Piet Bijl
1
@Alexandru: Je sais ce que vous voulez dire, mais PHP n'est pas seul à cet égard. Vous devez distinguer deux cas d'utilisation ici: Afficher un numéro à un utilisateur. Dans ce cas, l'affichage 0.10000000000000000555111512312578270211815834045410156est généralement inutile et ils préfèrent à la 0.1place. Et écrire un nombre pour qu'il puisse être relu exactement de la même manière. Comme vous le voyez, ce n'est pas aussi clair que vous le prétendez. Et pour mémoire, vous voulez toujours comparer des nombres à virgule flottante comme je l'ai montré, car vous pouvez arriver à $aet $bvia différents calculs qui peuvent les rendre différents.
Joey
2
Il existe encore des cas extrêmes où ce test échoue. Tels que a=b=0et si aest la plus petite valeur positive sans zéro possible et best la plus petite valeur négative non nulle possible, le test échouera de manière incorrecte. Quelques bonnes informations ici: floating-point-gui.de/errors/comparison
Dom
13
Pourquoi diviser $b? le manuel PHP vient de faire if(abs($a-$b) < $epsilon) le manuel MySQL a également fait de mêmeHAVING ABS(a - b) <= 0.0001
Accountant م
1
@CaslavSabani: C'est une erreur relative et non absolue. Il est toujours cassé (surtout quand $a == $b == 0, mais c'est déjà beaucoup plus général que l'erreur absolue. Si $aet $bsont dans les millions, alors vous EPSILONdevriez être très différent de si $aet $bêtes quelque part près de 0. Voir le lien de Dom ci-dessus pour une meilleure discussion sur ceci.
Joey
65

Lisez d'abord l'avertissement rouge dans le manuel . Vous ne devez jamais comparer les flottants pour l'égalité. Vous devez utiliser la technique epsilon.

Par exemple:

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

PHP_FLOAT_EPSILONest constante représentant un très petit nombre (il faut la définir dans les anciennes versions de PHP avant la 7.2)

Andreï
la source
2
Pour clarifier, EPSILON est-il dans ce cas la machine epsilon, qui est à peu près 2,2204460492503E-16? Et cette comparaison fonctionne-t-elle pour deux flotteurs de n'importe quelle grandeur?
Michael Cordingley
1
@MichaelCordingley Non, EPSILONvoici une constante définie par l'utilisateur arbitraire. PHP n'a pas de constante intégrée représentant l'idée spécifique d'une architecture d'epsilon. (Voir aussi get_defined_constants.)
évêque
5
PHP_FLOAT_EPSILONPlus petit nombre positif représentable x, de sorte que x + 1,0! = 1,0. Disponible à partir de PHP 7.2.0.
Code4R7
2
Cela ne fonctionne pas réellement dans ce cas: $ a = 270.10 + 20.10; $ b = 290,20; if (abs ($ a- $ b) <PHP_FLOAT_EPSILON) {echo 'same'; }
NemoXP
@NemoXP car ces expressions produisent des nombres différents. echo $a - $b; /* 5.6843418860808E-14 */ echo PHP_FLOAT_EPSILON; /* 2.2204460492503E-16 */La question est de savoir comment définir «égal» pour votre application, à quel point les nombres doivent être proches pour être considérés comme égaux.
Andrey
29

Ou essayez d'utiliser les fonctions mathématiques bc:

<?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 );

Résultat:

0.17 == 0.17 (core comp oper): bool(false)
0.17 == 0.17 (with bc func)  : bool(true)
Mario
la source
2
dans votre utilisation de bccomp, vous avez manqué "l'échelle", donc vous comparez en fait 0 à 0 selon le manuel: php.net/manual/en/function.bccomp.php
stefancarlton
J'aime ça. La plupart des solutions semblent reposer sur l'arrondi et la perte de précision, mais je traite avec des coordonnées de latitude et de longitude avec 12 points de précision et cela semble les comparer avec précision sans aucun ajustement nécessaire.
Rikaelus
Et les performances? bccomp()prend des chaînes comme arguments. Quoi qu'il en soit, vous pouvez utiliser PHP_FLOAT_DIGpour l'argument échelle.
Code4R7
19

Comme indiqué précédemment, soyez très prudent lorsque vous effectuez des comparaisons en virgule flottante (qu'elles soient égales à, supérieures ou inférieures à) en PHP. Cependant, si vous n'êtes intéressé que par quelques chiffres significatifs, vous pouvez faire quelque chose comme:

$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';
}

L'utilisation de l'arrondi à 2 décimales (ou 3 ou 4) entraînera le résultat attendu.

Michael Butler
la source
1
Un mot d'avertissement supplémentaire, je ne recommanderais pas de joncher votre base de code avec des déclarations comme celles-ci. Si vous voulez faire une comparaison de flotteurs lâches, utilisez une méthode comme loose_float_comparecelle-ci pour que ce qui se passe soit évident.
Michael Butler
Le natif de PHP bccomp($a, $b, 2)est supérieur à votre solution. Dans cet exemple, le 2 est la précision. vous pouvez le définir sur le nombre de virgules flottantes que vous souhaitez comparer.
John Miller le
@JohnMiller Je ne suis pas en désaccord avec vous, mais bccomp n'est pas disponible par défaut. Il nécessite l'activation d'un indicateur de compilation ou l'installation d'une extension. Ne fait pas partie du noyau.
Michael Butler
17

Il serait préférable d'utiliser la comparaison PHP native :

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. 

Renvoie 0 si les deux opérandes sont égaux, 1 si l'opérande gauche est plus grand que l'opérande droit, -1 sinon.

FieryCat
la source
10

Si vous avez des valeurs à virgule flottante à comparer à l'égalité, un moyen simple d'éviter le risque de stratégie d' arrondi interne du système d'exploitation, de la langue, du processeur, etc., consiste à comparer la représentation sous forme de chaîne des valeurs.

Vous pouvez utiliser l'un des éléments suivants pour produire le résultat souhaité: https://3v4l.org/rUrEq

Casting de type chaîne

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

Concaténation de chaînes

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

fonction strval

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

Les représentations de chaînes sont beaucoup moins difficiles que les flottants lorsqu'il s'agit de vérifier l'égalité.

Ame Nomade
la source
ou if (strval ($ a) === strval ($ b)) {…} si vous ne voulez pas convertir les valeurs d'origine
Ekonoval
Eh bien, ma réponse initiale était: if (''. $ A === ''. $ B) {…} mais quelqu'un l'a édité. Alors ...
Ame Nomade
1
@Ekonoval Pourriez-vous s'il vous plaît expliquer votre modification. Il semble que vous affirmiez que l' (string)opération de conversion est effectuée par référence, en modifiant la déclaration d'origine? Si tel n'est pas le cas 3v4l.org/Craas
fyrye
@fyrye Ouais, je suppose que j'avais tort, les deux approches donnent le même résultat.
Ekonoval
Mise à jour de la réponse pour donner un exemple d'utilisation et tous les exemples des autres modifications avec l'original
fyrye
4

Si vous avez un petit nombre fini de points décimaux qui sera acceptable, ce qui suit fonctionne bien (bien qu'avec des performances plus lentes que la solution epsilon):

$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
la source
4

Cela fonctionne pour moi sur 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
la source
3

Pour PHP 7.2, vous pouvez travailler avec PHP_FLOAT_EPSILON ( http://php.net/manual/en/reserved.constants.php ):

if(abs($a-$b) < PHP_FLOAT_EPSILON){
   echo 'a and b are same';
}
Gladhon
la source
Bonne solution. Mais: 1- la mise à jour nécessite PHP 7.2 qui tous ne peuvent pas faire facilement pour les systèmes de grands / anciens existants 2- Cela ne fonctionne que pour ==et !=non >, >=, <,<=
evilReiko
2

Si vous l'écrivez comme ça, cela fonctionnera probablement, alors j'imagine que vous l'avez simplifié pour la question. (Et garder la question simple et concise est normalement une très bonne chose.)

Mais dans ce cas, j'imagine qu'un résultat est un calcul et un résultat est une constante.

Cela viole une règle cardinale de la programmation en virgule flottante: ne jamais faire de comparaisons d'égalité.

Les raisons en sont un peu subtiles 1, mais ce qui est important de se rappeler, c'est qu'elles ne fonctionnent généralement pas (sauf, ironiquement, pour les valeurs intégrales) et que l'alternative est une comparaison floue du type:

if abs(a - y) < epsilon



1. L'un des problèmes majeurs concerne la manière dont nous écrivons les nombres dans les programmes. Nous les écrivons sous forme de chaînes décimales et, par conséquent, la plupart des fractions que nous écrivons n'ont pas de représentation exacte de la machine. Ils n'ont pas de formes finies exactes car ils se répètent en binaire. Chaque fraction de machine est un nombre rationnel de la forme x / 2 n . Maintenant, les constantes sont décimales et chaque constante décimale est un nombre rationnel de la forme x / (2 n * 5 m ). Les nombres de 5 m sont impairs, il n'y a donc pas de facteur 2 n pour aucun d'entre eux. Ce n'est que lorsque m == 0 qu'il y a une représentation finie dans le développement binaire et décimal de la fraction. Donc, 1,25 est exact car c'est 5 / (2 2 * 5 0) mais 0,1 n'est pas parce que c'est 1 / (2 0 * 5 1 ). En fait, dans la série 1.01 .. 1.99, seuls 3 des nombres sont exactement représentables: 1,25, 1,50 et 1,75.

DigitalRoss
la source
DigitalRoss c'est assez difficile de comprendre quelques termes dans votre commentaire, mais oui, c'est très instructif. Et je vais chercher ces termes sur Google. Merci :)
Santosh Sonarikar
N'est-il pas assez sûr de faire des comparaisons sur des flottants, à condition que vous arrondissiez le résultat à chaque fois et que vous soyez à quelques chiffres significatifs? En d'autres termesround($float, 3) == round($other, 3)
Michael Butler
2

Voici la solution pour comparer des virgules flottantes ou des nombres décimaux

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

Cast une decimalvariable sur stringet tout ira bien.

Natalie Rey
la source
1

La comparaison des flottants pour l'égalité a un algorithme naïf O (n).

Vous devez convertir chaque valeur flottante en chaîne, puis comparer chaque chiffre en commençant par le côté gauche de la représentation sous forme de chaîne de chaque flottant à l'aide d'opérateurs de comparaison d'entiers. PHP diffusera automatiquement le chiffre de chaque position d'index en un entier avant la comparaison. Le premier chiffre plus grand que l'autre cassera la boucle et déclarera le flottant auquel il appartient comme le plus grand des deux. En moyenne, il y aura 1/2 * n comparaisons. Pour les flottants égaux les uns aux autres, il y aura n comparaisons. Il s'agit du pire des cas pour l'algorithme. Le meilleur des cas est que le premier chiffre de chaque flottant est différent, ne provoquant qu'une seule comparaison.

Vous ne pouvez pas utiliser INTEGER COMPARISON OPERATORS sur des valeurs flottantes brutes dans le but de générer des résultats utiles. Les résultats de ces opérations n'ont aucune signification car vous ne comparez pas des entiers. Vous violez le domaine de chaque opérateur qui génère des résultats dénués de sens. Cela vaut également pour la comparaison delta.

Utilisez des opérateurs de comparaison d'entiers pour ce pour quoi ils sont conçus: comparer des entiers.

SOLUTION SIMPLIFIÉE:

<?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
la source
1

2019

TL; DR

Utilisez ma fonction ci-dessous, comme ceci if(cmpFloats($a, '==', $b)) { ... }

  • Facile à lire / écrire / modifier: cmpFloats($a, '<=', $b)vsbccomp($a, $b) <= -1
  • Aucune dépendance nécessaire.
  • Fonctionne avec n'importe quelle version de PHP.
  • Fonctionne avec des nombres négatifs.
  • Fonctionne avec la décimale la plus longue que vous puissiez imaginer.
  • Inconvénient: légèrement plus lent que bccomp ()

Résumé

Je vais dévoiler le mystère.

$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

Donc, si vous essayez ce qui suit, ce sera égal:

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

Comment obtenir la valeur réelle du float?

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

Comment pouvez-vous comparer?

  1. Utilisez les fonctions BC Math . (vous aurez toujours beaucoup de moments wtf-aha-gotcha)
  2. Vous pouvez essayer la réponse de @ Gladhon, en utilisant PHP_FLOAT_EPSILON (PHP 7.2).
  3. Si vous comparez des flottants avec ==et !=, vous pouvez les convertir en chaînes, cela devrait fonctionner parfaitement:

Tapez cast avec corde :

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

Ou transtypé avec 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"

Avertissement:

Évitez les solutions qui impliquent de manipuler mathématiquement les flottants (multiplier, diviser, etc.) puis de comparer, la plupart du temps, elles résoudront certains problèmes et introduiront d'autres problèmes.


Solution suggérée

J'ai créé une fonction PHP pure (aucun depenedcies / bibliothèques / extensions nécessaires). Vérifie et compare chaque chiffre sous forme de chaîne. Fonctionne également avec des nombres négatifs.

/**
 * 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)
malReiko
la source
1

La fonction de @evilReiko a quelques bugs comme ceux-ci:

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

Dans ma fonction, j'ai corrigé ces bugs, mais de toute façon dans certains cas, cette fonction renvoie de mauvaises réponses:

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

Fonction fixe pour comparer les flottants

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;
                        }
                    }
                }
            }
        }
    }
}

Répondez à votre question

$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
la source
0

Voici une classe utile de ma bibliothèque personnelle pour traiter les nombres à virgule flottante. Vous pouvez l'ajuster à votre guise et insérer la solution de votre choix dans les méthodes de classe :-).

/**
 * 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
la source
0

Réponse simple:

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