Solucionado (ver solução)
Solucionado
(ver solução)
3
respostas

Números a mais

Olá! Testei exemplo da aula no interpretador do Python de duas maneiras:

"{:.100f}".format(1.59) 
#resultado: 1.5900000000000000799360577730112709105014801025390625000000000000000000000000000000000000000000000000

"{:.100f}".format(1) 
# resultado: 1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

Achei curioso que quando usamos bastantes casas decimais, como no exemplo do 1.59, aparecem números após o ponto flutuante a mais ao invés de serem apenas 0s. E o engraçado que o isso não aconteceu quando usei para formatar o número 1, com os mesmos parâmetros. Poderiam me explicar, por favor, por que isto ocorre?

Obrigada!

3 respostas
solução!

Oi Larissa! Tudo tranquilo?

Vamos lá.

Fora da programação, quando trabalhamos com números como por exemplo 0.125 nós os chamamos de números decimais e podemos representá-los por meio da soma de algumas frações decimais. Por exemplo: 0.125 = 1/10 + 2/100 + 5/1000.

Agora, no mundo da programação esses números decimais são conhecidos como número de ponto flutuante (float). E o que acontece é que o computador não entende esses números de ponto flutuante como frações decimais, mas apenas na forma de frações binárias. Uma fração binária é aquela que possui a base com números múltiplos de 2.

Por exemplo, vimos que o número 0.125 tem o valor de 1/10 + 2/100 + 5/1000 em frações decimais. No entanto, para o computador, esse mesmo número terá o valor de 0/2 + 0/4 + 1/8, que se formos fazer a conta, possui o mesmo valor da soma de frações anteriores, a diferença é que agora ele foi representado por meio de frações binárias. Tudo bem até aí?

Certo, mas é aí que surge um probleminha. Infelizmente, muitas frações decimais não podem ser representadas precisamente como frações binárias. O resultado é que, em geral, os números decimais de ponto flutuante que você digita acabam sendo armazenados de forma apenas aproximada, na forma de números binários de ponto flutuante.

Por exemplo, o valor decimal 0.1 não pode ser representado exatamente como uma fração de base 2. No sistema de base 2, 1/10 é uma fração binária que se repete infinitamente: 0.0001100110011001100110011001100110011001100110011.... E assim como o 0.1 existem outros números que também não podem ser precisamente representados por frações binárias, então são feitos arredondamentos.

Dessa forma, podemos chegar a conclusão que o número 1 é um número que pode ser precisamente representado por frações binárias, por isso ao apresentar mais casas decimais nele você obteve apenas zeros. Já o número 1.59 provavelmente é uma aproximação, ou seja, ele não é um número que possa ser precisamente representado por frações binárias, e por isso, quando você mostrou mais casas decimais dele, obteve diferentes números.

Caso você queira entender de forma mais detalhada todo esse processo de como seu computador entender os números decimais, vou deixar um link bem bacana da documentação do Python aqui:

Espero que tenha conseguido te ajudar! Qualquer dúvida estarei por aqui :)

Abraços e bons estudos!

*P.S: Acabou que demorou tanto tempo pra explicar que quando terminei a resposta já tinha uma resposta mais bem elaborada acima, rs... Como não sei como excluir postagem, fica aí a tentativa! *

Essa história de float é uma história bem comprida... Já li umas três vezes e sinceramente, acho que vale a pena saber só por alto. O negócio é que o espaço em memória para guardar um número com ponto flutuante preciso seria indesejavelmente grande. Geralmente a gente não precisa de uma precisão absurda quando estamos lidando com número fracionários (que é o objetivo do float), então encontraram uma fórmula matemática que permite armazenar o valor aproximado de um número fracionado utilizando-se razoavelmente poucos termos em uma equação exponencial. Essa equação no entanto não é o número preciso em sí, e ela tende a gerar o "ruído" que você observou nos seus experimentos.

A fórmula separa qualquer número em três termos: 1- A parte fracionária (mantissa); 2- Uma base; 3- Um expoente. Se procurar no wikipédia, eles dão alguns exemplos de números criados a partir desta fórmula (MANTISSA x BASE ^ (elevado a EXPOENTE)), mas sinceramente, acho que isso é mais útil a título de curiosidade e compreensão do que se passa do que para aplicação prática (deixa o computador usar as fórmulas dele, kkk).

Geralmente, a utilização de qualquer método de arredondamento para 1, 2 ou 3 casas decimais antes de fazer comparações é suficiente para resolver este problema. O problema é que esse ruído tende a aumentar quantos mais operações são feitas com o valor. Então se pegar o mercado financeiro, por exemplo, onde esse ruído vai ser multiplicado, subtraído, dividido e somado inúmeras vezes, ele acaba por tornar-se inaceitável.

A solução vai variar do contexto, embora sempre venha a algum custo. O próprio float sacrifica precisão em prol de menor ocupação de memória (mais necessário em situações em que o processamento é limitado, como era o caso em máquinas antigas). No mercado financeiro, exemplo comumente usado pra explicar essa questão, o Python utiliza-se do Decimal ao invés de float pra cálculos. A troca nesse caso é que float é um tipo primitivo (utiliza apenas 4 bytes e tem uma sintaxe bem simples e intuitiva), e o Decimal é uma classe com método próprio, e ocupação significativamente maior em memória (mas não tanto que venha a afetar de maneira perceptível cálculos financeiros, por exemplo). A declaração de um Decimal é feita de forma diferente à que seria usada para um tipo primitivo, o que pode parecer uma complicação desnecessária a início tmb... A declaração poderia ser, por exemplo, "Decimal('0.123')"... Mas a não ser que você realmente precise desse nível de precisão, dificilmente você vai usar essa classe. Outra solução é utilizar números inteiros e considerar deslocamento na hora de enviar o resultado final pro cliente (por exemplo, se deseja 3 casas decimais, considera que 100 é 0.1, que 1000 é 1, etc... Na hora de printar o resultado, divide o número por 1000, operando apenas uma vez com ponto flutuante e garantindo razoavel precisão).

Enfim, essa imprecisão do float geralmente não afeta programas simples e float ainda é a maneira mais direta de utilizar números fracionados no aprendizado, mas sabendo dessa perda de precisão, a solução para o problema vai depender do contexto de cada programa e da viabilidade de se usar arredondamentos, mais processamento, etc.

Não entrei em detalhes maiores porque acho que esse assunto é um pouco enfadonho, e de minha parte pelo menos, matei minha curiosidade com um entendimento superficial a primeira vez que me deparei com o problema... Mas se quiser cavar a fundo, você já sabe por onde começar, ao menos!

Agradeço, Millena e Andre! As respostas foram bem claras e compreendi tal acontecimento. As coisas são o que programamos para que elas sejam. Quanto a precisão, maior custo.

Obrigada novamente.

Um abraço e até a próxima!