Comparar carrozas en php

157

Quiero comparar dos flotantes en PHP, como en este código de muestra:

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

En este código se devuelve el resultado de la elsecondición en lugar de la ifcondición, a pesar de que $ay $bson iguales. ¿Hay alguna forma especial de manejar / comparar flotantes en PHP?

En caso afirmativo, ayúdame a resolver este problema.

¿O hay un problema con la configuración de mi servidor?

Santosh Sonarikar
fuente
Consigo a and b are same. ¿Es este tu código completo?
Pekka
que version Funciona bien para mí.
gblazex
@Andrey esto probablemente sea porque el caso del mundo real probablemente sea más complejo que el ejemplo citado. ¿Por qué no agregarlo como respuesta?
Pekka
2
¿Leíste la floating-pointdescripción de la etiqueta? stackoverflow.com/tags/floating-point/info Ese es un comportamiento que probablemente encontrarás en cualquier lenguaje de programación, cuando uses números de punto flotante. Ver, por ejemplo, stackoverflow.com/questions/588004/is-javascripts-math-broken
Piskvor abandonó el edificio el

Respuestas:

232

Si lo haces así, deberían ser lo mismo. Pero tenga en cuenta que una característica de los valores de coma flotante es que los cálculos que parecen dar como resultado el mismo valor no necesitan ser idénticos. Entonces, si $aes un literal .17y $bllega allí a través de un cálculo, bien puede ser que sean diferentes, aunque ambos muestren el mismo valor.

Por lo general, nunca compara los valores de coma flotante para una igualdad como esta, necesita usar una diferencia mínima aceptable:

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

Algo como eso.

Joey
fuente
21
¡TENER CUIDADO! Elegir un épsilon fijo es una mala manera solo porque parece pequeño, esta comparación volverá verdadera en muchos errores de precisión cuando los números son pequeños. Una forma correcta sería verificar si el error relativo es menor que el épsilon. abs($a-$b)>abs(($a-$b)/$b)
Piet Bijl
1
@Alexandru: Sé lo que quieres decir, pero PHP no está solo en ese sentido. Debe distinguir dos casos de uso aquí: Mostrar un número a un usuario. En ese caso, la visualización 0.10000000000000000555111512312578270211815834045410156generalmente no tiene sentido y preferirían en su 0.1lugar. Y escribir un número para que pueda leerse de nuevo exactamente de la misma manera. Como puede ver, no es tan claro como parece. Y para que conste, todavía se desea comparar los números de punto flotante, como he mostrado, ya que puede llegar a $ay $ba través de diferentes cálculos que se hacen diferentes.
Joey
2
Todavía hay algunos casos extremos en los que esta prueba falla. Por ejemplo, a=b=0y si aes el valor positivo cero más pequeño posible y bes el valor negativo distinto de cero más pequeño posible, la prueba fallará incorrectamente. Alguna buena información aquí: floating-point-gui.de/errors/comparison
Dom
13
¿Por qué dividir $b? el manual de PHP acaba de hacer if(abs($a-$b) < $epsilon) el manual de MySQL también hizo lo mismoHAVING ABS(a - b) <= 0.0001
Contador م
1
@CaslavSabani: Esto es relativo, no un error absoluto. Aún está rota (sobre todo cuando $a == $b == 0, pero ya es mucho más general que el error absoluto. Si $ay $bse cuentan por millones, entonces su EPSILONtendrían que ser muy diferente que si $ay $bestán en algún lugar cerca 0. Enlace de Ver Dom arriba para una mejor discusión de esto.
Joey
65

Lea primero la advertencia roja en el manual . Nunca debes comparar las carrozas por la igualdad. Debes usar la técnica epsilon.

Por ejemplo:

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

donde PHP_FLOAT_EPSILONes constante representando un número muy pequeño (debe definirlo en versiones anteriores de PHP anteriores a 7.2)

Andrey
fuente
2
Para aclarar, ¿es EPSILON en este caso la máquina épsilon, que es aproximadamente 2.2204460492503E-16? Y, ¿funciona esta comparación para dos carrozas de cualquier magnitud?
Michael Cordingley
1
@MichaelCordingley No, EPSILONaquí hay una constante arbitraria definida por el usuario. PHP no tiene una constante incorporada que represente la idea específica de una arquitectura de epsilon. (Véase también get_defined_constants.)
obispo
55
PHP_FLOAT_EPSILONEl número positivo representable más pequeño x, de modo que x + 1.0! = 1.0. Disponible a partir de PHP 7.2.0.
Code4R7
2
En realidad, esto no funciona en este caso: $ a = 270.10 + 20.10; $ b = 290.20; if (abs ($ a- $ b) <PHP_FLOAT_EPSILON) {echo 'same'; }
NemoXP
@NemoXP porque esas expresiones producen números diferentes. echo $a - $b; /* 5.6843418860808E-14 */ echo PHP_FLOAT_EPSILON; /* 2.2204460492503E-16 */La pregunta es cómo desea definir "igual" para su aplicación, qué tan cerca deben estar los números para ser considerados iguales.
Andrey
29

O intente usar las funciones matemáticas de 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 );

Resultado:

0.17 == 0.17 (core comp oper): bool(false)
0.17 == 0.17 (with bc func)  : bool(true)
Mario
fuente
2
en su uso de bccomp se ha perdido la "escala", por lo que en realidad está comparando 0 a 0 según el manual: php.net/manual/en/function.bccomp.php
stefancarlton
Me gusta esto La mayoría de las soluciones parecen depender del redondeo y la pérdida de precisión, pero estoy tratando con coordenadas de latitud y longitud con 12 puntos de precisión y esto parece compararlas con precisión sin necesidad de ajustes.
Rikaelus
¿Qué pasa con el rendimiento? bccomp()toma cadenas como argumentos. De todos modos, podría usar PHP_FLOAT_DIGpara el argumento de escala.
Code4R7
19

Como se dijo antes, tenga mucho cuidado al hacer comparaciones de punto flotante (ya sea igual a, mayor que o menor que) en PHP. Sin embargo, si solo está interesado en unos pocos dígitos significativos, puede hacer algo como:

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

El uso de redondeo a 2 decimales (o 3, o 4) causará el resultado esperado.

Michael Butler
fuente
1
Advertencia adicional, no recomendaría llenar su base de código con declaraciones como estas. Si desea hacer una comparación de flotador suelto, haga un método como loose_float_comparepara que sea obvio lo que está sucediendo.
Michael Butler
El nativo de PHP bccomp($a, $b, 2)es superior a su solución. En este ejemplo, el 2 es la precisión. puede configurarlo en cualquier número de puntos flotantes que desee comparar.
John Miller
@JohnMiller No estoy en desacuerdo contigo, pero bccomp no está disponible de forma predeterminada. Requiere que se habilite un indicador de compilación o que se instale una extensión. No es parte del núcleo.
Michael Butler
17

Sería mejor utilizar la comparación nativa de PHP :

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. 

Devuelve 0 si los dos operandos son iguales, 1 si left_operand es más grande que right_operand, -1 en caso contrario.

FieryCat
fuente
10

Si tiene valores de coma flotante para comparar con la igualdad, una forma simple de evitar el riesgo de una estrategia de redondeo interno del sistema operativo, lenguaje, procesador, etc., es comparar la representación de cadena de los valores.

Puede usar cualquiera de los siguientes para producir el resultado deseado: https://3v4l.org/rUrEq

Tipo de cuerda de fundición

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

Concatenación de cuerdas

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

función strval

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

Las representaciones de cadenas son mucho menos delicadas que las flotantes cuando se trata de verificar la igualdad.

Ame Nomade
fuente
o if (strval ($ a) === strval ($ b)) {…} si no desea convertir los valores originales
Ekonoval
Bueno, mi respuesta original fue: if (''. $ A === ''. $ B) {...} pero alguien lo editó. Entonces ...
Ame Nomade
1
@Ekonoval ¿Podría explicar su modificación, parece que está afirmando que la (string)operación de conversión se realiza por referencia, cambiando la declaración original? Si es así, ese no es el caso 3v4l.org/Craas
fyrye
@fyrye Sí, supongo que estaba equivocado, ambos enfoques producen el mismo resultado.
Ekonoval
Se actualizó la respuesta para dar un ejemplo de uso y todos los ejemplos de las otras ediciones junto con el original
fyrye
4

Si tiene un número pequeño y finito de puntos decimales que será aceptable, lo siguiente funciona bien (aunque con un rendimiento más lento que la solución 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
fuente
4

Esto funciona para mí en 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
fuente
3

Para PHP 7.2, puede trabajar 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';
}
Gladhon
fuente
Buena solución. Pero: 1- Requiere la actualización de PHP 7.2, que no todos pueden hacer fácilmente para grandes / viejos sistemas 2- esto sólo funciona para existente ==y !=pero no >, >=, <,<=
evilReiko
2

Si lo escribe así, probablemente funcionará, así que imagino que lo ha simplificado para la pregunta. (Y mantener la pregunta simple y concisa es normalmente algo muy bueno).

Pero en este caso me imagino que un resultado es un cálculo y un resultado es una constante.

Esto viola una regla fundamental de la programación de coma flotante: nunca haga comparaciones de igualdad.

Las razones para esto son un poco sutiles 1, pero lo que es importante recordar es que generalmente no funcionan (excepto, irónicamente, para valores integrales) y que la alternativa es una comparación difusa en la línea de:

if abs(a - y) < epsilon



1. Uno de los principales problemas implica la forma en que escribimos números en los programas. Los escribimos como cadenas decimales y, como resultado, la mayoría de las fracciones que escribimos no tienen representaciones exactas de la máquina. No tienen formas finitas exactas porque se repiten en binario. Cada fracción de máquina es un número racional de la forma x / 2 n . Ahora, las constantes son decimales y cada constante decimal es un número racional de la forma x / (2 n * 5 m ). Los números de 5 m son impares, por lo que no hay un factor 2 n para ninguno de ellos. Solo cuando m == 0 hay una representación finita tanto en la expansión binaria como decimal de la fracción. Entonces, 1.25 es exacto porque es 5 / (2 2 * 5 0) pero 0.1 no es porque sea 1 / (2 0 * 5 1 ). De hecho, en la serie 1.01 .. 1.99 solo 3 de los números son exactamente representables: 1.25, 1.50 y 1.75.

DigitalRoss
fuente
DigitalRoss es bastante difícil de entender pocos términos en su comentario, pero sí, es muy informativo. Y voy a googlear estos términos. Gracias :)
Santosh Sonarikar
¿No es lo suficientemente seguro hacer comparaciones en flotadores, siempre que redondee el resultado cada vez y se encuentre dentro de unos pocos dígitos significativos? En otras palabrasround($float, 3) == round($other, 3)
Michael Butler
2

Aquí está la solución para comparar puntos flotantes o números decimales

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

Lanza una decimalvariable a stringy estarás bien.

Natalie Rey
fuente
1

La comparación de flotadores para igualdad tiene un ingenuo algoritmo O (n).

Debe convertir cada valor flotante en una cadena, luego comparar cada dígito comenzando desde el lado izquierdo de la representación de cadena flotante utilizando operadores de comparación de enteros. PHP emitirá automáticamente el dígito en cada posición de índice a un entero antes de la comparación. El primer dígito más grande que el otro romperá el bucle y declarará el flotante al que pertenece como el mayor de los dos. En promedio, habrá 1/2 * n comparaciones. Para carrozas iguales entre sí, habrá n comparaciones. Este es el peor de los casos para el algoritmo. El mejor de los casos es que el primer dígito de cada flotante sea diferente, causando solo una comparación.

No puede utilizar OPERADORES DE COMPARACIÓN INTEGRALES en valores flotantes sin procesar con la intención de generar resultados útiles. Los resultados de tales operaciones no tienen sentido porque no está comparando enteros. Está violando el dominio de cada operador que genera resultados sin sentido. Esto también es válido para la comparación delta.

Utilice operadores de comparación de enteros para lo que están diseñados: comparar enteros.

SOLUCIÓN SIMPLIFICADA

<?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
fuente
1

2019

TL; DR

Usa mi función a continuación, así if(cmpFloats($a, '==', $b)) { ... }

  • Fácil de leer / escribir / cambiar: cmpFloats($a, '<=', $b)vsbccomp($a, $b) <= -1
  • No se necesitan dependencias.
  • Funciona con cualquier versión de PHP.
  • Funciona con números negativos.
  • Funciona con el decimal más largo que puedas imaginar.
  • Desventaja: Ligeramente más lento que bccomp ()

Resumen

Revelaré el misterio.

$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

Entonces, si prueba lo siguiente, será igual:

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

¿Cómo obtener el valor real del flotador?

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

¿Cómo puedes comparar?

  1. Utilice las funciones de BC Math . (Todavía obtendrás muchos momentos wtf-aha-gotcha)
  2. Puede probar la respuesta de @ Gladhon usando PHP_FLOAT_EPSILON (PHP 7.2).
  3. Si compara flotantes con ==y !=, puede convertirlos en cadenas, debería funcionar perfectamente:

Escriba cast con cadena :

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

O encasillado 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"

Advertencia:

Evite soluciones que impliquen manipular flotantes matemáticamente (multiplicar, dividir, etc.) y luego comparar, en su mayoría resolverán algunos problemas e introducirán otros problemas.


Solución sugerida

He creado una función PHP pura (no se necesitan dependencias / bibliotecas / extensiones). Comprueba y compara cada dígito como una cadena. También funciona con números negativos.

/**
 * 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)
evilReiko
fuente
1

La función de @evilReiko tiene algunos errores como estos:

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

En mi función, he corregido estos errores, pero de todos modos, en algunos casos, esta función devuelve respuestas incorrectas:

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

Función fija para comparar flotadores

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

Responde a tu pregunta

$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
fuente
0

Aquí hay una clase útil de mi biblioteca personal para tratar con números de coma flotante. Puede modificarlo a su gusto e insertar cualquier solución que desee en los métodos de clase :-).

/**
 * 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
fuente
0

Respuesta simple:

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