Python: trabalhando com precisão em números decimais

Python: trabalhando com precisão em números decimais
yan-orestes
yan-orestes

Compartilhe

Imagine que você está trabalhando em um projeto em Python para automatizar o controle de gastos de uma empresa. Para isso, você armazena os valores de ganhos em uma variável chamada ganhos_do_mes e os gastos da empresa em outra variável nomeada gastos_do_mes, e todos esses valores são adicionados em uma função armazena_no_banco_de_dados(), que irá fazer a conexão com o banco de dados do financeiro para salvar essas informações para posterior análise. O código, de uma maneira mais sucinta, ficaria desse modo:

ganhos_do_mes = 99.91 * 5 # Foram vendidos 5 produtos no valor de R$ 99.91
gastos_do_mes = 110.1 * 3 # Foram comprados 3 produtos no valor de R$ 110.1

armazena_no_banco_de_dados(ganhos_do_mes, gastos_do_mes) 

Entretanto, ao realizar a análise de gastos no banco de dados, o time do financeiro informou que houve uma divergência entre os valores retornados no projeto acima e os valores reais de entrada e saída de dinheiro, levantando a possibilidade de um problema no código. Ao analisar os resultados do projeto, obtemos:

ganhos_do_mes = 499.54999999999995
gastos_do_mes = 330.29999999999995

Enquanto os resultados reais, feitos por uma calculadora pelo financeiro, retornam os valores 499.55 e 330.30, respectivamente. Em aplicações financeiras, é comum arredondar valores monetários para duas casas decimais, pois isso simplifica os cálculos e fornece resultados mais fáceis de entender.

Mas e aí, por que o projeto em Python retornou números tão longos ao invés de arredondá-los como fez a calculadora comum?

Famoso gif da Nazaré Tedesco olhando confusa enquanto várias equações matemáticas aparecem na tela. No gif, o rosto de uma mulher branca e loira com olhos pretos observa vários lados, parecendo confusa. Várias equações matemáticas e gráficos aparecem sobrepostos em cor branca, surgindo e desaparecendo de vários pontos.

Pontos Flutuantes e o Problema de Precisão

Toda essa questão dos resultados que recebemos em Python é algo que vem da computação e de como ela lida com números de ponto flutuante (o conhecido float).

No mais baixo nível, computadores funcionam com a diferença de dois estados elétricos - baixa e alta voltagem, ligado e desligado, verdadeiro e falso. Daí temos o binário, o famoso 0 e 1, e é por conta desse sistema que os computadores conseguem ser tão rápidos com algumas coisas.

Entretanto, utilizando o formato binário para os números de ponto flutuante, os computadores não conseguem representar com precisão exata algumas frações (como 0.98 e 0.1). Desse modo, esses números são automaticamente arredondados para o mais próximo que se encaixe na possibilidade do binário, o que resulta em um pequeno erro de precisão.

No geral, esse erro é muito pequeno para ser considerado relevante, mas há situações em que não podemos desconsiderá-lo, como foi dado no exemplo anterior.

Nesse caso, como estamos lidando com dinheiro, precisamos de uma precisão maior no arredondamento do número real. Outros casos em que seria importante ter valores mais arredondados incluem o uso de sistemas com recursos limitados, como em sistemas embarcados, visando economizar recursos de CPU e memória. Além disso, ao exibir resultados em gráficos e relatórios, o arredondamento é interessante para proporcionar legibilidade e evitar confusões.

No geral, se estivéssemos trabalhando com simulações científicas, contabilidade de impostos, entre outros casos que necessitam utilizar valores mais completos em suas operações, o Python, assim como outras linguagens, não arredondaria os números, o que não seria um problema. Ou seja, a decisão de arredondar ou não arredondar depende das necessidades específicas da aplicação e do contexto.

Contudo, como na situação apresentada, precisamos ter valores mais precisos, em até duas casas decimais. Como podemos alterar nosso código para fazer isso?

Banner da Escola de Programação: Matricula-se na escola de Programação. Junte-se a uma comunidade de mais de 500 mil estudantes. Na Alura você tem acesso a todos os cursos em uma única assinatura; tem novos lançamentos a cada semana; desafios práticos. Clique e saiba mais!

Trabalhando com precisão com o tipo Decimal

Por conta dessa necessidade recorrente de lidarmos com números não-inteiros exatos, a maioria das linguagens de programação nos disponibiliza tipos específicos para lidar com isso.

No caso do Java, por exemplo, temos o BigDecimal. No Python, temos todo o módulo decimal e, mais especificamente, o tipo Decimal. Importando-o, seu uso é direto:

from decimal import Decimal

ganhos_do_mes = Decimal('99.91') * 5
print(ganhos_do_mes)

gastos_do_mes = Decimal('110.1') * 3
print(gastos_do_mes)

Em que temos o resultado:

ganhos_do_mes = 499.55
gastos_do_mes = 330.30

Exatamente como foi encontrado na calculadora! A biblioteca decimal oferece números de ponto flutuante com precisão arbitrária. Isso significa que você pode especificar a precisão desejada e realizar cálculos com um número específico de casas decimais e também pode ter controle completo sobre o número de casas decimais a serem mantidas. Isso é útil em cenários onde a precisão precisa ser estritamente controlada, visto que não introduz erros de arredondamento, desde que você configure a precisão apropriada.

E se no código utilizado não for necessário uma precisão extrema?

Função Round

Caso seu projeto não precise trabalhar com uma precisão tão exata, a função round() pode ser uma outra opção apropriada para você, visto que ela é usada para arredondar números de ponto flutuante para um número específico de casas decimais. Ao contrário da biblioteca decimal, ela não oferece precisão arbitrária e nem arredonda o número fornecido de acordo com as regras de arredondamento padrão.

Essa é uma opção para fins de exibição ou para simplificar cálculos, na qual não seria um problema ter possíveis erros de arredondamento dos valores utilizados. Se optássemos por essa abordagem no código anterior, ela ficaria do seguinte modo:

ganhos_do_mes = round(99.91 * 5, 2)  # Arredonda para 2 casas decimais
gastos_do_mes = round(110.1 * 3, 2)   # Arredonda para 2 casas decimais

armazena_no_banco_de_dados(ganhos_do_mes, gastos_do_mes)

Usando a precisão exata quando precisamos

Trabalhar com precisão em Python com números de ponto flutuante pode ser desafiador devido à natureza aproximada desse tipo de dado. A maioria das linguagens de programação tem algum tipo numérico exato para evitar esse problema. No caso do Python, esse tipo é o Decimal, com precisão arbitrária. Com ele, nossos cálculos ganharam a precisão necessária e acabamos com todo o problema inicial.

Uma intuição natural depois de se conhecer os tipos numéricos exatos nas linguagens de programação, como o Decimal, é querer usá-los para tudo. Apesar disso, é importante sempre analisarmos se vale a pena, pois tipos exatos demandam mais tempo de processamento. Caso não tenha a necessidade de uma implementação tão precisa, é possível usar uma função embutida do Python, o round().

Normalmente, um pequeno erro na 10ª casa decimal de um número é irrelevante, e a precisão exata desnecessária. Por isso, temos sempre que analisar o contexto de nosso próprio programa antes de aplicar uma decisão.

Que tal dar um mergulho ainda mais profundo? Deixamos aqui algumas referências que serão de grande ajuda nos seus estudos de Python:

Veja outros artigos sobre Programação