Cálculos com ponto flutuante em PHP, bem vindo ao inferno

Sendo justo: Todas as linguagens tem dificuldade com ponto flutuante, mas eu penso que o PHP exagera no negócio.

Veja esse cálculo:

Vindo de um software para lançamentos contábeis, o PHP se perde no centésimo, veja, não estamos pedindo para calcular Pi até milhonésima casa decimal, são centavos, centésimos!

Quando preciso fazer cálculos precisos em PHP, uso uma dessas abordagens:

BCMath

BCMath (PHP: BC Math – Manual), que é uma biblioteca que traz precisão matemática aos cálculos no PHP. Em regra ela está habilitada no servidor, se não estiver, o manual ensina como fazer.

Em todo o caso, antes de tentar essa abordagem:

if (extension_loaded('bcmath')) {
    echo 'A extensão BCMath está habilitada.';
} else {
    echo 'A extensão BCMath não está habilitada.';
}

Estando carregada, a aplicação:

// Certifique-se de trabalhar com strings para alta precisão
$soma_debito = '500';
$soma_credito = '499.99';

// Use bcsub para subtrair os valores
$diferenca = bcsub($soma_debito, $soma_credito, 2); // O '2' indica o número de casas decimais

// Agora, você pode comparar a diferença com uma string que representa 'zero' com precisão
if (bccomp($diferenca, '0', 2) !== 0) {
    $erros->inclui_erro("Os débitos diferem dos créditos. Debitos: {$soma_debito}, Créditos: {$soma_credito}, Diferença:{$diferenca}");
} elseif (bccomp($soma_debito, $valor, 2) !== 0) {
    $erros->inclui_erro('Valor está diferente dos débitos/créditos');
}

E pronto, temos a desejada precisão.

Transformar em inteiros

Quando trabalho com moeda, sendo as casas decimais fixas, em cálculos simples, os transformo em inteiros e tenho a precisão ideal:

$soma_debito = 50000; // R$ 500,00 em centavos
$soma_credito = 49999; // R$ 499,99 em centavos

// Aqui você compara diretamente os valores inteiros
if ($soma_debito <> $soma_credito) {
    $diferenca = $soma_debito - $soma_credito; // A diferença será um valor inteiro em centavos
    $erros->inclui_erro("Os débitos diferem dos créditos. Debitos: {$soma_debito}, Créditos: {$soma_credito}, Diferença: {$diferenca}");
} elseif ($soma_debito <> $valor) {
    $erros->inclui_erro('Valor está diferente dos débitos/créditos');
}

Um esquema simples é usar number format (PHP: number_format – Manual), eliminando em seguida com expressão regular os pontos e traços, trabalhando com os inteiros.

Quando for seguir o processamento, coloque novamente o marcador de decimal nas duas últimas e casas e siga feliz.

Essa segunda é mais trabalhosa e mais limitada, só use caso seja impossível a primeira abordagem com BCMath.

Serviu para ti? Deixe um comentário, conte sua experiência. Tem uma solução melhor? Compartilhe.


Publicado

em

por

Tags:

Comentários

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *