Compare carros alegóricos em php

157

Eu quero comparar dois carros alegóricos em PHP, como neste código de exemplo:

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

Neste código retorna o resultado da elsecondição em vez da ifcondição, mesmo que $ae $bsão os mesmos. Existe alguma maneira especial de manipular / comparar carros alegóricos em PHP?

Se sim, por favor me ajude a resolver esse problema.

Ou há algum problema com a configuração do meu servidor?

Santosh Sonarikar
fonte
Eu entendo a and b are same. Esse é o seu código completo?
Pekka
qual versão? Isso funciona bem para mim.
gblazex
@ Andrew, provavelmente é porque o caso do mundo real provavelmente será mais complexo do que o exemplo citado. Por que não adicioná-lo como resposta?
Pekka
2
Você leu a floating-pointdescrição da tag? stackoverflow.com/tags/floating-point/info Esse é um comportamento que você provavelmente encontrará em qualquer linguagem de programação ao usar números de ponto flutuante. Veja, por exemplo, stackoverflow.com/questions/588004/is-javascripts-math-broken
Piskvor saiu do prédio

Respostas:

232

Se você fizer assim, eles devem ser os mesmos. Mas observe que uma característica dos valores de ponto flutuante é que os cálculos que parecem resultar no mesmo valor não precisam ser realmente idênticos. Portanto, se $aé um literal .17e $bchega lá através de um cálculo, pode ser que eles sejam diferentes, embora ambos exibam o mesmo valor.

Normalmente, você nunca compara valores de ponto flutuante para igualdade como esta; é necessário usar a menor diferença aceitável:

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

Algo parecido.

Joey
fonte
21
Cuidado! Escolher um epsilon fixo é uma maneira ruim, apenas porque parece pequena, essa comparação retornará verdadeira em muitos erros de precisão quando os números forem pequenos. Uma maneira correta seria verificar se o erro relativo é menor que o epsilon. abs($a-$b)>abs(($a-$b)/$b)
Piet Bijl
1
@ Alexandru: Eu sei o que você quer dizer, mas o PHP não está sozinho nesse aspecto. Você precisa distinguir dois casos de uso aqui: Mostrando um número para um usuário. Nesse caso, a exibição 0.10000000000000000555111512312578270211815834045410156geralmente não faz sentido e eles preferem 0.1. E escrevendo um número para que possa ser lido novamente exatamente da mesma maneira. Como você vê, não é tão nítido quanto você imagina. E para o registro, você ainda quiser comparar números de ponto flutuante como eu têm mostrado porque você pode chegar $ae $batravés de diferentes cálculos que podem torná-los diferentes.
Joey
2
Ainda existem alguns casos extremos nos quais esse teste falha. Como a=b=0e se afor o menor possível, nenhum valor positivo zero e bo menor valor negativo não zero possível, o teste falhará incorretamente. Algumas informações bom aqui: floating-point-gui.de/errors/comparison
Dom
13
Por que dividir $b? o manual do PHP apenas fiz if(abs($a-$b) < $epsilon) o manual do MySQL também fez o mesmoHAVING ABS(a - b) <= 0.0001
Contador م
1
@CaslavSabani: Este é um erro relativo, não absoluto. Ainda está quebrado (especialmente quando $a == $b == 0, mas já é muito mais geral do que erro absoluto. Se $ae $bestá na casa dos milhões, então você EPSILONteria que ser muito diferente do que se $ae $bestiver em algum lugar próximo 0. Consulte o link de Dom acima para uma melhor discussão sobre isso.
Joey
65

Leia o aviso vermelho no manual primeiro. Você nunca deve comparar carros alegóricos pela igualdade. Você deve usar a técnica epsilon.

Por exemplo:

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

onde PHP_FLOAT_EPSILONé constante representando um número muito pequeno (você deve defini-lo nas versões antigas do PHP antes da 7.2)

Andrey
fonte
2
Para esclarecer, neste caso o EPSILON é a máquina epsilon, que é aproximadamente 2.2204460492503E-16? E essa comparação funciona para dois carros alegóricos de qualquer magnitude?
Michael Cordingley
1
@MichaelCordingley Não, EPSILONaqui está uma constante definida pelo usuário arbitrariamente. O PHP não possui uma constante interna que representa a idéia específica de epsilon de uma arquitetura. (Veja também get_defined_constants.)
bispo
5
PHP_FLOAT_EPSILONO menor número positivo representável x, de modo que x + 1,0! = 1,0. Disponível a partir do PHP 7.2.0.
precisa saber é o seguinte
2
Na verdade, isso não funciona neste caso: $ a = 270.10 + 20.10; $ b = 290,20; if (abs ($ a- $ b) <PHP_FLOAT_EPSILON) {echo 'igual'; }
NemoXP 7/11/19
@NemoXP porque essas expressões produzem números diferentes. echo $a - $b; /* 5.6843418860808E-14 */ echo PHP_FLOAT_EPSILON; /* 2.2204460492503E-16 */A questão é como você deseja definir "igual" para o seu aplicativo, quão próximo os números devem ser considerados iguais.
Andrey
29

Ou tente usar as funções matemáticas 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
fonte
2
em seu uso de bccomp você perdeu a "escala" assim você está realmente comparando 0-0 de acordo com o manual: php.net/manual/en/function.bccomp.php
stefancarlton
Estou gostando disso. A maioria das soluções parece depender de arredondamentos e perda de precisão, mas estou lidando com coordenadas de latitude e longitude com 12 pontos de precisão e isso parece compará-las com precisão, sem a necessidade de ajustes.
Rikaelus
E o desempenho? bccomp()aceita strings como argumentos. De qualquer forma, você pode usar PHP_FLOAT_DIGpara o argumento de escala.
precisa saber é o seguinte
19

Como dito anteriormente, tenha muito cuidado ao fazer comparações de ponto flutuante (igual a, maior que ou menor que) em PHP. No entanto, se você estiver interessado apenas em alguns dígitos significativos, poderá fazer 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';
}

O uso do arredondamento para 2 casas decimais (ou 3 ou 4) causará o resultado esperado.

Michael Butler
fonte
1
Aviso extra, eu não recomendaria encher sua base de código com declarações como estas. Se você quiser fazer uma comparação de flutuação livre, faça um método como loose_float_compareesse, para que fique óbvio o que está acontecendo.
Michael Butler
O nativo do PHP bccomp($a, $b, 2)é superior à sua solução. Neste exemplo, o 2 é a precisão. você pode configurá-lo para qualquer número de pontos flutuantes que deseja comparar.
John Miller
@JohnMiller Não estou discordando de você, mas o bccomp não está disponível por padrão. Ele requer que um sinalizador de compilação seja ativado ou que uma extensão seja instalada. Não faz parte do núcleo.
Michael Butler
17

Seria melhor usar a comparação nativa do 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. 

Retorna 0 se os dois operandos forem iguais, 1 se o left_operand for maior que o right_operand, -1 caso contrário.

FieryCat
fonte
10

Se você tiver valores de ponto flutuante para comparar com igualdade, uma maneira simples de evitar o risco de estratégia de arredondamento interno do sistema operacional, idioma, processador ou assim por diante, é comparar a representação de string dos valores.

Você pode usar qualquer um dos seguintes para produzir o resultado desejado: https://3v4l.org/rUrEq

Fundição do tipo String

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

Concatenação de String

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

função strval

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

As representações de strings são muito menos complicadas do que as flutuações quando se trata de verificar a igualdade.

Ame Nomade
fonte
ou se (strval ($ a) === strval ($ b)) {...} se você não deseja converter os valores originais
Ekonoval
Bem, minha resposta original foi: if (''. $ A === ''. $ B) {…} mas alguém a editou. Então ...
Ame Nomade
1
@Ekonoval Você poderia elaborar sua modificação? Parece que você está afirmando que a (string)operação de conversão é executada por referência, alterando a declaração original? Nesse caso, não é esse o caso 3v4l.org/Craas
fyrye
@ fyrye Sim, eu acho que estava errado, as duas abordagens produzem o mesmo resultado.
Ekonoval 20/04
Atualizado a resposta para dar um exemplo de uso e todos os exemplos das outras edições, juntamente com o original
fyrye
4

Se você tiver um número pequeno e finito de casas decimais aceitáveis, o seguinte funcionará bem (embora com desempenho mais lento que a solução 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
fonte
4

Isso funciona para mim no 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
fonte
3

Para o PHP 7.2, você pode trabalhar com o 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
fonte
Boa solução. Mas: 1- exige atualização PHP 7.2, que nem todo mundo pode fazer facilmente para grandes / sistemas antigos 2- Isto funciona apenas para existente ==e !=, mas não >, >=, <,<=
evilReiko
2

Se você escrever assim, provavelmente funcionará, então imagino que você o simplificou para a pergunta. (E manter a pergunta simples e concisa é normalmente uma coisa muito boa.)

Mas neste caso, imagino que um resultado é um cálculo e um resultado é uma constante.

Isso viola uma regra fundamental da programação de ponto flutuante: nunca faça comparações de igualdade.

As razões para isso são um pouco sutis 1, mas o que é importante lembrar é que elas geralmente não funcionam (exceto, ironicamente, por valores integrais) e que a alternativa é uma comparação nebulosa ao longo das linhas de:

if abs(a - y) < epsilon



1. Um dos principais problemas envolve a maneira como escrevemos números em programas. Nós as escrevemos como cadeias decimais e, como resultado, a maioria das frações que escrevemos não possui representações exatas da máquina. Eles não têm formas finitas exatas porque se repetem em binário. Toda fração de máquina é um número racional da forma x / 2 n . Agora, as constantes são decimais e cada constante decimal é um número racional da forma x / (2 n * 5 m ). Os números de 5 m são ímpares, portanto, não há um fator 2 n para nenhum deles. Somente quando m == 0 há uma representação finita na expansão binária e decimal da fração. Portanto, 1,25 é exato porque é 5 / (2 2 * 5 0), mas 0,1 não é porque é 1 / (2 0 * 5 1 ). De fato, na série 1.01 .. 1,99, apenas 3 dos números são exatamente representáveis: 1,25, 1,50 e 1,75.

DigitalRoss
fonte
O DigitalRoss é bastante difícil de entender alguns termos no seu comentário, mas sim, é muito informativo. E vou pesquisar esses termos no Google. Obrigado :)
Santosh Sonarikar
Não é seguro fazer comparações em carros alegóricos, desde que você arredonde o resultado todas as vezes e com alguns dígitos significativos? Em outras palavrasround($float, 3) == round($other, 3)
Michael Butler
2

Aqui está a solução para comparar pontos flutuantes ou números decimais

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

Lance uma decimalvariável para stringe você ficará bem.

Natalie Rey
fonte
1

A comparação de flutuadores para igualdade possui um algoritmo O (n) ingênuo.

Você deve converter cada valor de flutuação em uma sequência e comparar cada dígito começando no lado esquerdo da representação de sequência de cada flutuação usando operadores de comparação de números inteiros. O PHP autocast o dígito em cada posição do índice para um número inteiro antes da comparação. O primeiro dígito maior que o outro interromperá o loop e declarará o float ao qual ele pertence como o maior dos dois. Em média, haverá comparações de 1/2 * n. Para carros alegóricos iguais entre si, não haverá n comparações. Este é o pior cenário para o algoritmo. O melhor cenário é que o primeiro dígito de cada flutuador seja diferente, causando apenas uma comparação.

Você não pode usar OPERADORES DE COMPARAÇÃO INTEIRO em valores flutuantes brutos com a intenção de gerar resultados úteis. Os resultados dessas operações não têm significado porque você não está comparando números inteiros. Você está violando o domínio de cada operador que gera resultados sem sentido. Isso vale para comparação delta também.

Use operadores de comparação de números inteiros para o que eles foram projetados: comparar números inteiros.

SOLUÇÃO 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
fonte
1

2019

TL; DR

Use minha função abaixo, assim if(cmpFloats($a, '==', $b)) { ... }

  • Fácil de ler / escrever / alterar: cmpFloats($a, '<=', $b)vsbccomp($a, $b) <= -1
  • Não são necessárias dependências.
  • Funciona com qualquer versão do PHP.
  • Funciona com números negativos.
  • Funciona com o decimal mais longo que você possa imaginar.
  • Desvantagem: um pouco mais lenta que o bccomp ()

Resumo

Vou desvendar o mistério.

$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

Portanto, se você tentar o abaixo, será igual:

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

Como obter o valor real de float?

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

Como você pode comparar?

  1. Use as funções BC Math . (você ainda terá muitos momentos wtf-aha-gotcha)
  2. Você pode tentar a resposta de @ Gladhon, usando PHP_FLOAT_EPSILON (PHP 7.2).
  3. Se comparar floats com ==e !=, você pode convertê-los em strings, deve funcionar perfeitamente:

Digite elenco com string :

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

Ou tipecast com 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"

Aviso:

Evite soluções que envolvam manipular carros alegóricos matematicamente (multiplicar, dividir etc.) e depois compará-los; em geral, eles resolverão alguns problemas e introduzirão outros.


Solução sugerida

Eu criei a função PHP pura (sem dependências / bibliotecas / extensões necessárias). Verifica e compara cada dígito como sequência. Também funciona com 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
fonte
1

A função do @evilReiko possui alguns erros como estes:

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

Na minha função, eu corrigi esses erros, mas de qualquer maneira, em alguns casos, essa função retorna respostas erradas:

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

Função fixa para comparar carros alegóricos

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

Responda a sua pergunta

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

Aqui está uma classe útil da minha biblioteca pessoal para lidar com números de ponto flutuante. Você pode ajustá-lo ao seu gosto e inserir qualquer solução que desejar nos métodos 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
fonte
0

Resposta simples:

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