Confronta i float in php


157

Voglio confrontare due float in PHP, come in questo codice di esempio:

$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 questo codice restituisce il risultato della elsecondizione anziché la ifcondizione, anche se $ae $bsono uguali. Esiste un modo speciale per gestire / confrontare i float in PHP?

Se sì, per favore aiutami a risolvere questo problema.

O c'è un problema con la mia configurazione del server?


Ho capito a and b are same. Questo è il tuo codice completo?
Pekka,

quale versione? funziona bene per me.
gblazex,

@Andrey questo è probabilmente perché il caso del mondo reale è probabilmente più complesso dell'esempio citato. Perché non aggiungerlo come risposta?
Pekka,

2
Hai letto la floating-pointdescrizione del tag? stackoverflow.com/tags/floating-point/info Questo è un comportamento che potresti incontrare in qualsiasi linguaggio di programmazione quando usi numeri in virgola mobile. Vedere ad esempio stackoverflow.com/questions/588004/is-javascripts-math-broken
Piskvor lasciato l'edificio

Risposte:


232

Se lo fai in questo modo dovrebbero essere gli stessi. Si noti che una caratteristica dei valori in virgola mobile è che i calcoli che sembrano dare lo stesso valore non devono necessariamente essere identici. Quindi, se $aè letterale .17e $barriva lì attraverso un calcolo, può benissimo essere diverso, sebbene entrambi mostrino lo stesso valore.

Di solito non si confrontano mai i valori in virgola mobile per l'uguaglianza come questa, è necessario utilizzare una differenza accettabile minima:

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

Qualcosa del genere.


21
ATTENZIONE! Scegliere un epsilon fisso è un brutto modo solo perché sembra piccolo, questo confronto tornerà vero in molti errori di precisione quando i numeri sono piccoli. Un modo corretto sarebbe verificare se l'errore relativo è più piccolo di epsilon. abs($a-$b)>abs(($a-$b)/$b)
Piet Bijl,

1
@Alexandru: so cosa intendi, ma PHP non è il solo a questo riguardo. È necessario distinguere due casi d'uso qui: Mostrare un numero a un utente. In tal caso, la visualizzazione 0.10000000000000000555111512312578270211815834045410156è di solito inutile e preferirebbero 0.1invece. E scrivere un numero in modo che possa essere letto di nuovo esattamente allo stesso modo. Come vedi, non è così nitido come sembra. E per la cronaca, vuoi ancora confrontare i numeri in virgola mobile come ho mostrato perché puoi arrivare $ae $btramite calcoli diversi che possono renderli diversi.
Joey,

2
Ci sono ancora alcuni casi limite che questo test fallisce. Ad esempio a=b=0se e se aè il valore zero zero più piccolo possibile ed bè il valore non zero negativo più piccolo possibile, il test avrà esito negativo. Alcune buone informazioni qui: floating-point-gui.de/errors/comparison
Dom

13
Perché dividersi $b? il manuale di PHP appena fatto if(abs($a-$b) < $epsilon) il manuale di MySQL anche fatto lo stessoHAVING ABS(a - b) <= 0.0001
Contabile م

1
@CaslavSabani: questo è un errore relativo, non assoluto. È ancora rotto (specialmente quando $a == $b == 0, ma è già molto più generale dell'errore assoluto. Se $ae $bsono in milioni, allora EPSILONdovresti essere molto diverso da se $ae $bsono da qualche parte vicino 0. Vedi il link di Dom sopra per una migliore discussione di questo
Joey,

65

Leggere prima l'avvertimento rosso nel manuale . Non devi mai confrontare i float per l'uguaglianza. Dovresti usare la tecnica epsilon.

Per esempio:

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

dove PHP_FLOAT_EPSILONè costante che rappresenta un numero molto piccolo (è necessario definirlo nelle vecchie versioni di PHP prima della 7.2)


2
Per chiarire, EPSILON in questo caso è la macchina epsilon, che è all'incirca 2.2204460492503E-16? E questo confronto funziona per due galleggianti di qualsiasi grandezza?
Michael Cordingley,

1
@MichaelCordingley No, EPSILONecco una costante arbitraria definita dall'utente. PHP non ha una costante incorporata che rappresenta l'idea specifica di epsilon di un'architettura. (Vedi anche get_defined_constants.)
vescovo

5
PHP_FLOAT_EPSILONNumero positivo rappresentabile più piccolo x, in modo che x + 1.0! = 1.0. Disponibile da PHP 7.2.0.
Code4R7,

2
In questo caso non funziona in questo caso: $ a = 270.10 + 20.10; $ b = 290,20; if (abs ($ a- $ b) <PHP_FLOAT_EPSILON) {echo 'same'; }
NemoXP,

@NemoXP perché quelle espressioni producono numeri diversi. echo $a - $b; /* 5.6843418860808E-14 */ echo PHP_FLOAT_EPSILON; /* 2.2204460492503E-16 */La domanda è: come definire "uguale" per la propria applicazione, quanto vicini devono essere considerati uguali.
Andrey,

29

Oppure prova a utilizzare le funzioni matematiche di 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 );

Risultato:

0.17 == 0.17 (core comp oper): bool(false)
0.17 == 0.17 (with bc func)  : bool(true)

2
nel vostro uso del bccomp ti sei perso la "scala", così si sta effettivamente confrontando 0-0 secondo il manuale: php.net/manual/en/function.bccomp.php
stefancarlton

Mi piace questo. La maggior parte delle soluzioni sembra basarsi sull'arrotondamento e sulla perdita di precisione, ma ho a che fare con coordinate di latitudine e longitudine con 12 punti di precisione e questo sembra confrontarle accuratamente senza necessità di modifiche.
Rikaelus,

E le prestazioni? bccomp()accetta le stringhe come argomenti. Ad ogni modo potresti usare PHP_FLOAT_DIGl'argomento scale.
Code4R7,

19

Come detto prima, fai molta attenzione quando fai confronti in virgola mobile (sia uguale a, maggiore di o minore di) in PHP. Tuttavia, se sei sempre interessato a poche cifre significative, puoi fare qualcosa del tipo:

$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'uso dell'arrotondamento al secondo decimale (o 3 o 4) causerà il risultato atteso.


1
Un avvertimento in più, non consiglierei di sporcare la tua base di codice con dichiarazioni come queste. Se vuoi fare un confronto float libero, crea un metodo del genere, loose_float_comparequindi è ovvio cosa sta succedendo.
Michael Butler,

Il nativo di PHP bccomp($a, $b, 2)è superiore alla tua soluzione. In questo esempio, 2 è la precisione. puoi impostarlo su qualsiasi numero di punti mobili che desideri confrontare.
John Miller,

@JohnMiller Non sono in disaccordo con te, ma bccomp non è disponibile per impostazione predefinita. Richiede l'attivazione di un flag di compilazione o l'installazione di un'estensione. Non fa parte del core.
Michael Butler,

17

Sarebbe meglio usare il confronto PHP nativo :

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. 

Restituisce 0 se i due operandi sono uguali, 1 se l'operando di sinistra è più grande dell'operando di destra, -1 altrimenti.


10

Se si hanno valori in virgola mobile da confrontare con l'uguaglianza, un modo semplice per evitare il rischio di strategia di arrotondamento interno del sistema operativo, del linguaggio, del processore e così via è confrontare la rappresentazione in stringhe dei valori.

È possibile utilizzare uno dei seguenti per produrre il risultato desiderato: https://3v4l.org/rUrEq

Casting di tipo stringa

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

Concatenazione di stringhe

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

funzione strval

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

Le rappresentazioni delle stringhe sono molto meno schizzinose dei float quando si tratta di controllare l'uguaglianza.


oppure if (strval ($ a) === strval ($ b)) {…} se non vuoi convertire i valori originali
Ekonoval

Bene, la mia risposta originale era: if (''. $ A === ''. $ B) {…} ma qualcuno l'ha modificata. Quindi ...
Ame Nomade,

1
@Ekonoval Potresti per favore elaborare la tua modifica, sembra che tu stia affermando che l' (string)operazione di cast viene eseguita per riferimento, cambiando la dichiarazione originale? Se è così non è il caso 3v4l.org/Craas
fyrye,

@fyrye Sì, credo di aver sbagliato, entrambi gli approcci danno lo stesso risultato.
Ekonoval,

Aggiornata la risposta per fornire un esempio di utilizzo e tutti gli esempi delle altre modifiche insieme all'originale
fyrye,

4

Se si dispone di un numero limitato e limitato di punti decimali che saranno accettabili, quanto segue funziona bene (sebbene con prestazioni più lente rispetto alla soluzione 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';
}

4

Questo funziona per me su 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
}

3

Per PHP 7.2, puoi lavorare con PHP_FLOAT_EPSILON ( http://php.net/manual/en/reserved.constants.php ):

if(abs($a-$b) < PHP_FLOAT_EPSILON){
   echo 'a and b are same';
}

Buona soluzione Ma: 1- richiede l'aggiornamento di PHP 7.2, che non tutti possono fare facilmente per grandi / vecchi sistemi 2- questo funziona solo per esistenti ==e !=, ma non >, >=, <,<=
evilReiko

2

Se lo scrivi proprio così probabilmente funzionerà, quindi immagino che tu l'abbia semplificato per la domanda. (E mantenere la domanda semplice e concisa è normalmente un'ottima cosa.)

Ma in questo caso immagino che un risultato sia un calcolo e un risultato sia una costante.

Ciò viola una regola cardine della programmazione in virgola mobile: non effettuare mai confronti di uguaglianza.

Le ragioni sono un po 'sottili 1, ma ciò che è importante ricordare è che di solito non funzionano (tranne, ironicamente, per i valori integrali) e che l'alternativa è un confronto sfocato sulla falsariga di:

if abs(a - y) < epsilon



1. Uno dei maggiori problemi riguarda il modo in cui scriviamo i numeri nei programmi. Le scriviamo come stringhe decimali e di conseguenza la maggior parte delle frazioni che scriviamo non hanno rappresentazioni esatte della macchina. Non hanno forme finite esatte perché si ripetono in binario. Ogni frazione di macchina è un numero razionale della forma x / 2 n . Ora, le costanti sono decimali e ogni costante decimale è un numero razionale della forma x / (2 n * 5 m ). I numeri da 5 m sono dispari, quindi non esiste un fattore 2 n per nessuno di essi. Solo quando m == 0 c'è una rappresentazione finita nell'espansione binaria e decimale della frazione. Quindi, 1,25 è esatto perché è 5 / (2 2 * 5 0) ma 0.1 non è perché è 1 / (2 0 * 5 1 ). In effetti, nella serie 1.01 .. 1.99 solo 3 dei numeri sono esattamente rappresentabili: 1,25, 1,50 e 1,75.


DigitalRoss è abbastanza difficile da comprendere pochi termini nel tuo commento, ma sì, è molto istruttivo. E ho intenzione di google questi termini. Grazie :)
Santosh Sonarikar,

Non è abbastanza sicuro fare paragoni su float, purché tu stia arrotondando il risultato ogni volta e ti trovi entro poche cifre significative? In altre paroleround($float, 3) == round($other, 3)
Michael Butler il

2

Ecco la soluzione per il confronto di punti mobili o numeri decimali

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

Trasmetti una decimalvariabile a stringe starai bene.


1

Il confronto dei float per l'uguaglianza ha un algoritmo O (n) ingenuo.

È necessario convertire ciascun valore float in una stringa, quindi confrontare ogni cifra a partire dal lato sinistro della rappresentazione di stringa di ciascun float utilizzando operatori di confronto di numeri interi. PHP inserirà automaticamente la cifra in ciascuna posizione di indice in un numero intero prima del confronto. La prima cifra più grande dell'altra interromperà il ciclo e dichiarerà il float a cui appartiene come il maggiore dei due. In media, ci saranno confronti di 1/2 * n. Per float uguali tra loro, ci saranno n confronti. Questo è lo scenario peggiore per l'algoritmo. Lo scenario migliore è che la prima cifra di ciascun float è diversa, causando un solo confronto.

Non è possibile utilizzare INTEGER COMPARISON OPERATORS su valori float non elaborati con l'intenzione di generare risultati utili. I risultati di tali operazioni non hanno alcun significato perché non si stanno confrontando numeri interi. Stai violando il dominio di ciascun operatore che genera risultati insignificanti. Questo vale anche per il confronto delta.

Utilizzare gli operatori di confronto di numeri interi per cui sono progettati: confronto di numeri interi.

SOLUZIONE SEMPLIFICATA:

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

?>

1

2019

TL; DR

Usa la mia funzione di seguito, in questo modo if(cmpFloats($a, '==', $b)) { ... }

  • Facile da leggere / scrivere / cambiare: cmpFloats($a, '<=', $b)vsbccomp($a, $b) <= -1
  • Non sono necessarie dipendenze.
  • Funziona con qualsiasi versione di PHP.
  • Funziona con numeri negativi.
  • Funziona con il decimale più lungo che puoi immaginare.
  • Unico inconveniente: leggermente più lento di bccomp ()

Sommario

Svelerò il mistero.

$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

Quindi, se provi il seguito, sarà uguale:

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

Come ottenere il valore effettivo di float?

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

Come si può confrontare?

  1. Utilizzare le funzioni BC Math . (avrai ancora molti momenti wtf-aha-gotcha)
  2. Puoi provare la risposta di @ Gladhon, usando PHP_FLOAT_EPSILON (PHP 7.2).
  3. Se si confrontano i float con ==e !=, è possibile digitarli in stringhe, dovrebbe funzionare perfettamente:

Digitare cast con stringa :

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

O typecast con 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"

Avvertimento:

Evita le soluzioni che comportano la manipolazione matematica dei float (moltiplicando, dividendo, ecc.), Quindi confrontandoli, per lo più risolveranno alcuni problemi e presenteranno altri problemi.


Soluzione suggerita

Ho creato la pura funzione PHP (non sono necessarie depenedcies / librerie / estensioni). Controlla e confronta ogni cifra come stringa. Funziona anche con numeri negativi.

/**
 * 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)

1

Le funzioni di @evilReiko hanno alcuni bug come questi:

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

Nella mia funzione ho corretto questi bug, ma in alcuni casi questa funzione restituisce risposte errate:

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

Funzione fissa per float di confronto

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

Rispondi per la tua domanda

$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)

0

Ecco una lezione utile dalla mia biblioteca personale per gestire i numeri in virgola mobile. Puoi tweek a tuo piacimento e inserire qualsiasi soluzione che ti piace nei metodi di 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;
    }
}
?>

0

Risposta semplice:

if( floatval( (string) $a ) >= floatval( (string) $b) ) { //do something }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.