2.39. Trabalhando com floats¶
Os números de ponto flutuante parecem decimais comuns, mas alguns de seus comportamentos surpreendem leitores que nunca os encontraram antes – e um desses comportamentos é mais acentuado no MicroPython do que no Python de desktop. Esta página cobre o que esperar e como escrever código com floats que não se comporte mal silenciosamente.
2.39.1. Precisão¶
O float do Python é um número de ponto flutuante binário IEEE 754. Na maioria das versões do MicroPython, ele é de precisão simples (32 bits), enquanto o CPython de desktop usa precisão dupla (64 bits). A precisão simples carrega cerca de sete dígitos decimais de exatidão; a dupla carrega cerca de quinze.
>>> 0.1 + 0.2
0.3000000
>>> 1.0 / 3.0
0.3333333
>>> 1e30 * 1e30
inf
A faixa representável também é mais estreita em ambos os extremos: números com magnitude maior que cerca de 3.4e38 estouram para inf, e números menores que cerca de 1.2e-38 são arredondados para zero.
2.39.2. Comparando floats¶
A armadilha mais comum é testar igualdade com ==:
>>> 0.1 + 0.2 == 0.3
False
Ambas as expressões parecem que deveriam ser iguais, mas o resultado de 0.1 + 0.2 é o valor representável mais próximo, que não é exatamente 0.3. Use em vez disso uma verificação de tolerância – pergunte se dois floats estão próximos o suficiente, em vez de idênticos:
if abs(a - b) < 1e-6:
# close enough
...
A escolha da tolerância depende da escala dos valores. Um valor fixo 1e-6 funciona bem quando os números estão na ordem de 1; uma tolerância relativa é melhor quando os valores variam por ordens de magnitude.
math.isclose() lida com ambos de uma vez:
from math import isclose
isclose(0.1 + 0.2, 0.3) # True
isclose(1.0e6 + 1, 1.0e6) # True (within default tolerance)
Os dois argumentos nomeados controlam qual tipo de “próximo” conta:
rel_tol– tolerância relativa, padrão1e-9. Dois valores correspondem se a diferença entre eles estiver dentro dessa fração do maior deles. Bom para comparações gerais em qualquer escala.abs_tol– tolerância absoluta, padrão0. Dois valores correspondem se a diferença entre eles estiver dentro dessa quantidade fixa.
math.isclose() retorna True se qualquer uma das tolerâncias for atendida. Os padrões são adequados para a maioria dos pares de números diferentes de zero; a armadilha é quando um dos valores pode ser exatamente zero. A verificação de tolerância relativa equivale a “diferença ≤ rel_tol × maior valor”, e o maior valor é zero, então a verificação sempre falha:
>>> isclose(0.0, 1e-12)
False
A verificação de tolerância absoluta não tem esse problema – passe um abs_tol sempre que zero for um valor com o qual você possa estar comparando:
>>> isclose(0.0, 1e-12, abs_tol=1e-9)
True
2.39.3. Desvio de acumulação¶
Somas longas de floats perdem precisão mais rápido no MicroPython do que no CPython, porque todo resultado intermediário é arredondado de volta para a precisão de 32 bits:
total = 0.0
for _ in range(1000000):
total += 0.1
print(total) # noticeably off from 100000.0
Para adições repetidas em que a exatidão importa, dois padrões ajudam:
Acumule em um inteiro sempre que os valores puderem ser escalonados para inteiros – trabalhe em milissegundos em vez de segundos, ou milivolts em vez de volts, e então converta uma única vez no final.
Compute em lotes menores e some os resultados dos lotes, para que cada adição seja entre valores de magnitude semelhante.
O lado dos inteiros não tem esse limite – os inteiros do MicroPython são de precisão arbitrária, assim como os do CPython. Onde você tiver a escolha, prefira aritmética de inteiros para qualquer coisa em que a perda de precisão se acumularia.