É sabido que comparar carros alegóricos pela igualdade é um pouco complicado devido a problemas de arredondamento e precisão.
Por exemplo: https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
Qual é a maneira recomendada de lidar com isso no Python?
Certamente existe uma função de biblioteca padrão para isso em algum lugar?
python
floating-point
Gordon Wrigley
fonte
fonte
all
,any
,max
,min
são cada basicamente one-liners, e eles não são apenas fornecidos em uma biblioteca, eles são builtin funções. Portanto, as razões do BDFL não são essa. A única linha de código que a maioria das pessoas escreve é bastante pouco sofisticada e geralmente não funciona, o que é um forte motivo para fornecer algo melhor. É claro que qualquer módulo que forneça outras estratégias também deverá fornecer advertências descrevendo quando elas são apropriadas e, mais importante, quando não são. A análise numérica é difícil, não é uma grande desgraça que os designers de linguagem geralmente não tentem ferramentas para ajudá-lo.Respostas:
O Python 3.5 adiciona as funções
math.isclose
ecmath.isclose
, conforme descrito no PEP 485 .Se você estiver usando uma versão anterior do Python, a função equivalente é fornecida na documentação .
rel_tol
é uma tolerância relativa, é multiplicada pela maior das magnitudes dos dois argumentos; à medida que os valores aumentam, também aumenta a diferença permitida entre eles, enquanto os considera iguais.abs_tol
é uma tolerância absoluta aplicada como é em todos os casos. Se a diferença for menor que uma dessas tolerâncias, os valores serão considerados iguais.fonte
a
oub
é anumpy
array
,numpy.isclose
funciona.rel_tol
é uma tolerância relativa , é multiplicada pela maior das magnitudes dos dois argumentos; à medida que os valores aumentam, também aumenta a diferença permitida entre eles, enquanto os considera iguais.abs_tol
é uma tolerância absoluta aplicada como é em todos os casos. Se a diferença for menor que uma dessas tolerâncias, os valores serão considerados iguais.isclose
função (acima) não é uma implementação completa .isclose
sempre segue o critério menos conservador. Eu apenas menciono isso porque esse comportamento é contra-intuitivo para mim. Se eu especificasse dois critérios, sempre esperaria que a menor tolerância substituísse a maior.Algo tão simples como o seguinte não é bom o suficiente?
fonte
abs(f1-f2) < tol*max(abs(f1),abs(f2))
. Esse tipo de tolerância relativa é a única maneira significativa de comparar flutuadores em geral, pois geralmente são afetados por erros de arredondamento nas pequenas casas decimais.>>> abs(0.04 - 0.03) <= 0.01
:, ele produzFalse
. Eu usoPython 2.7.10 [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
abs(f1 - f2) <= allowed_error
não funciona conforme o esperado.Concordo que a resposta de Gareth é provavelmente a mais apropriada como função / solução leve.
Mas pensei que seria útil observar que, se você estiver usando o NumPy ou estiver considerando, existe uma função empacotada para isso.
Um pequeno aviso: instalar o NumPy pode ser uma experiência não trivial, dependendo da sua plataforma.
fonte
pip
no Windows.Use o
decimal
módulo do Python , que fornece aDecimal
classe.Dos comentários:
fonte
Não estou ciente de nada na biblioteca padrão do Python (ou em outro lugar) que implemente a
AlmostEqual2sComplement
função de Dawson . Se esse é o tipo de comportamento que você deseja, será necessário implementá-lo. (Nesse caso, em vez de usar os hacks inteligentes e bit a bit de Dawson, você provavelmente faria melhor em usar testes mais convencionais da formaif abs(a-b) <= eps1*(abs(a)+abs(b)) + eps2
ou similar. Para obter um comportamento semelhante ao Dawson, você pode dizer algo comoif abs(a-b) <= eps*max(EPS,abs(a),abs(b))
um pequeno reparo fixoEPS
; isso não é exatamente o mesmo que Dawson, mas é semelhante em espírito.fonte
eps1
eeps2
defina uma tolerância relativa e uma absoluta: você está preparado para permitira
eb
diferir aproximadamenteeps1
o tamanho da vantagemeps2
.eps
é uma tolerância única; você está preparado para permitira
eb
diferir aproximadamenteeps
o tamanho delas, com a condição de que qualquer coisa de tamanhoEPS
ou menor seja assumida como sendo de tamanhoEPS
. Se você consideraEPS
o menor valor não-anormal do seu tipo de ponto flutuante, isso é muito semelhante ao comparador de Dawson (exceto por um fator de 2 ^ # bits porque Dawson mede a tolerância em ulps).A sabedoria comum de que números de ponto flutuante não podem ser comparados quanto à igualdade é imprecisa. Os números de ponto flutuante não são diferentes dos números inteiros: se você avaliar "a == b", será verdadeiro se eles forem números idênticos e falsos caso contrário (com o entendimento de que dois NaNs não são, obviamente, números idênticos).
O problema real é este: se eu fiz alguns cálculos e não tenho certeza de que os dois números que devo comparar estão exatamente corretos, então o que? Esse problema é o mesmo para o ponto flutuante e para números inteiros. Se você avaliar a expressão inteira "7/3 * 3", ela não será comparada com "7 * 3/3".
Então, suponha que perguntássemos "Como comparo números inteiros para igualdade?" em tal situação. Não há uma resposta única; o que você deve fazer depende da situação específica, principalmente o tipo de erro que você possui e o que deseja obter.
Aqui estão algumas opções possíveis.
Se você deseja obter um resultado "verdadeiro" se os números matematicamente exatos forem iguais, tente usar as propriedades dos cálculos realizados para provar que você obtém os mesmos erros nos dois números. Se isso for possível, e você comparar dois números que resultam de expressões que dariam números iguais se calculados exatamente, você será "verdadeiro" na comparação. Outra abordagem é que você pode analisar as propriedades dos cálculos e provar que o erro nunca excede uma certa quantia, talvez uma quantia absoluta ou uma quantia relativa a uma das entradas ou uma das saídas. Nesse caso, você pode perguntar se os dois números calculados diferem no máximo nesse valor e retornar "true" se estiverem dentro do intervalo. Se você não pode provar um erro vinculado, você pode adivinhar e esperar o melhor. Uma maneira de adivinhar é avaliar muitas amostras aleatórias e ver que tipo de distribuição você obtém nos resultados.
Obviamente, como apenas definimos o requisito de que você seja "verdadeiro" se os resultados matematicamente exatos forem iguais, deixamos em aberto a possibilidade de você ser "verdadeiro", mesmo que sejam desiguais. (De fato, podemos satisfazer o requisito sempre retornando "true". Isso simplifica o cálculo, mas geralmente é indesejável; portanto, discutirei como melhorar a situação abaixo.)
Se você deseja obter um resultado "falso" se os números matematicamente exatos forem desiguais, é necessário provar que sua avaliação dos números gera números diferentes se os números matematicamente exatos forem desiguais. Isso pode ser impossível para fins práticos em muitas situações comuns. Então, vamos considerar uma alternativa.
Um requisito útil pode ser a obtenção de um resultado "falso" se os números matematicamente exatos diferirem mais do que uma certa quantia. Por exemplo, talvez vamos calcular para onde a bola jogada em um jogo de computador viajou e queremos saber se ela atingiu um taco. Nesse caso, certamente queremos ser "verdadeiros" se a bola bater no taco, e queremos ser "falsos" se a bola estiver longe do taco, e podemos aceitar uma resposta "verdadeira" incorreta se a bola uma simulação matematicamente exata perdeu o bastão, mas está a um milímetro de atingi-lo. Nesse caso, precisamos provar (ou adivinhar / estimar) que nosso cálculo da posição da bola e da posição do taco tem um erro combinado de no máximo um milímetro (para todas as posições de interesse). Isso nos permitiria retornar sempre "
Portanto, como você decide o que retornar ao comparar números de ponto flutuante depende muito da sua situação específica.
Quanto à maneira de provar limites de erro para cálculos, isso pode ser um assunto complicado. Qualquer implementação de ponto flutuante usando o padrão IEEE 754 no modo arredondado para o mais próximo retorna o número de ponto flutuante mais próximo do resultado exato para qualquer operação básica (principalmente multiplicação, divisão, adição, subtração, raiz quadrada). (Em caso de empate, arredondar para que o bit mais baixo seja o mesmo.) (Seja particularmente cuidadoso com a raiz quadrada e a divisão; sua implementação de idioma pode usar métodos que não estão em conformidade com o IEEE 754 para esses.) Devido a esse requisito, sabemos o seguinte: erro em um único resultado é no máximo 1/2 do valor do bit menos significativo. (Se fosse mais, o arredondamento teria sido para um número diferente que esteja dentro da metade do valor.)
Continuar a partir daí fica substancialmente mais complicado; a próxima etapa é executar uma operação em que uma das entradas já possui algum erro. Para expressões simples, esses erros podem ser seguidos através dos cálculos para atingir um limite no erro final. Na prática, isso é feito apenas em algumas situações, como trabalhar em uma biblioteca de matemática de alta qualidade. E, é claro, você precisa de um controle preciso sobre exatamente quais operações são executadas. Linguagens de alto nível geralmente oferecem muita folga ao compilador, portanto, você pode não saber em que ordem as operações são executadas.
Há muito mais que poderia ser (e é) escrito sobre esse tópico, mas eu tenho que parar por aí. Em resumo, a resposta é: Não há rotina de biblioteca para essa comparação porque não há uma solução única que atenda à maioria das necessidades que vale a pena colocar em uma rotina de biblioteca. (Se comparar com um intervalo de erro relativo ou absoluto é suficiente para você, você pode fazê-lo simplesmente sem uma rotina de biblioteca.)
fonte
(7/3*3 == 7*3/3)
. É impressoFalse
.from __future__ import division
. Se você não fizer isso, não haverá números de ponto flutuante e a comparação será entre dois números inteiros.Se você quiser usá-lo no contexto testing / TDD, diria que esta é uma maneira padrão:
fonte
math.isclose () foi adicionado ao Python 3.5 para isso ( código fonte ). Aqui está uma porta para o Python 2. A diferença do one-liner do Mark Ransom é que ele pode manipular "inf" e "-inf" corretamente.
fonte
Achei a seguinte comparação útil:
fonte
str(.1 + .2) == str(.3)
retorna False. O método descrito acima funciona apenas para o Python 2. #Em alguns casos em que você pode afetar a representação do número de origem, é possível representá-los como frações, em vez de flutuadores, usando numerador e denominador inteiro. Dessa forma, você pode ter comparações exatas.
Consulte o módulo Fração de frações para obter detalhes.
fonte
Gostei da sugestão de @Sesquipedal, mas com modificações (um caso de uso especial em que ambos os valores são 0 retorna Falso). No meu caso, eu estava no Python 2.7 e apenas usei uma função simples:
fonte
Útil para o caso em que você deseja garantir que 2 números sejam os mesmos 'até precisão', não há necessidade de especificar a tolerância:
Encontre a precisão mínima dos 2 números
Arredonde ambos para a precisão mínima e compare
Conforme escrito, funciona apenas para números sem o 'e' em sua representação de sequência (significando 0.9999999999995e-4 <número <= 0.9999999999995e11)
Exemplo:
fonte
isclose(1.0, 1.1)
produzFalse
eisclose(0.1, 0.000000000001)
retornaTrue
.Para comparar até um determinado decimal sem
atol/rtol
:fonte
Talvez isso seja um truque feio, mas funciona muito bem quando você não precisa mais do que a precisão de flutuação padrão (cerca de 11 casas decimais).
A função round_to usa o método format da classe str interna para arredondar o float para uma string que representa o float com o número de casas decimais necessárias e, em seguida, aplica o eval à string float arredondada para voltar para o tipo numérico flutuante.
A função is_close aplica apenas uma condição simples ao float arredondado.
Atualizar:
Conforme sugerido por @stepehjfox, uma maneira mais limpa de criar uma função rount_to , evitando "eval", é usando a formatação aninhada :
Seguindo a mesma ideia, o código pode ser ainda mais simples usando as excelentes novas f-strings (Python 3.6+):
Assim, poderíamos até agrupar tudo em uma função simples e limpa 'is_close' :
fonte
eval()
para obter formatação parametrizada. Algo comoreturn '{:.{precision}f'.format(float_num, precision=decimal_precision)
deve fazê-loreturn '{:.{precision}}f'.format(float_num, precision=decimal_precision)
Em termos de erro absoluto, você pode apenas verificar
Algumas informações sobre por que o float age de maneira estranha no Python https://youtu.be/v4HhvoNLILk?t=1129
Você também pode usar math.isclose para erros relativos
fonte