Pergunta

Qual é o melhor método para comparar pontos flutuantes e duplos do IEEE quanto à igualdade?Já ouvi falar de vários métodos, mas queria ver o que a comunidade pensava.

Foi útil?

Solução

A melhor abordagem que eu acho é comparar ULPs.

bool is_nan(float f)
{
    return (*reinterpret_cast<unsigned __int32*>(&f) & 0x7f800000) == 0x7f800000 && (*reinterpret_cast<unsigned __int32*>(&f) & 0x007fffff) != 0;
}

bool is_finite(float f)
{
    return (*reinterpret_cast<unsigned __int32*>(&f) & 0x7f800000) != 0x7f800000;
}

// if this symbol is defined, NaNs are never equal to anything (as is normal in IEEE floating point)
// if this symbol is not defined, NaNs are hugely different from regular numbers, but might be equal to each other
#define UNEQUAL_NANS 1
// if this symbol is defined, infinites are never equal to finite numbers (as they're unimaginably greater)
// if this symbol is not defined, infinities are 1 ULP away from +/- FLT_MAX
#define INFINITE_INFINITIES 1

// test whether two IEEE floats are within a specified number of representable values of each other
// This depends on the fact that IEEE floats are properly ordered when treated as signed magnitude integers
bool equal_float(float lhs, float rhs, unsigned __int32 max_ulp_difference)
{
#ifdef UNEQUAL_NANS
    if(is_nan(lhs) || is_nan(rhs))
    {
        return false;
    }
#endif
#ifdef INFINITE_INFINITIES
    if((is_finite(lhs) && !is_finite(rhs)) || (!is_finite(lhs) && is_finite(rhs)))
    {
        return false;
    }
#endif
    signed __int32 left(*reinterpret_cast<signed __int32*>(&lhs));
    // transform signed magnitude ints into 2s complement signed ints
    if(left < 0)
    {
        left = 0x80000000 - left;
    }
    signed __int32 right(*reinterpret_cast<signed __int32*>(&rhs));
    // transform signed magnitude ints into 2s complement signed ints
    if(right < 0)
    {
        right = 0x80000000 - right;
    }
    if(static_cast<unsigned __int32>(std::abs(left - right)) <= max_ulp_difference)
    {
        return true;
    }
    return false;
}

Uma técnica semelhante pode ser usada para duplas.O truque é converter os carros alegóricos para que fiquem ordenados (como se fossem números inteiros) e então ver como eles são diferentes.

Não tenho ideia de por que essa maldita coisa está bagunçando meus sublinhados.Editar:Ah, talvez isso seja apenas um artefato da prévia.Tudo bem então.

Outras dicas

A versão atual que estou usando é esta

bool is_equals(float A, float B,
               float maxRelativeError, float maxAbsoluteError)
{

  if (fabs(A - B) < maxAbsoluteError)
    return true;

  float relativeError;
  if (fabs(B) > fabs(A))
    relativeError = fabs((A - B) / B);
  else
    relativeError = fabs((A - B) / A);

  if (relativeError <= maxRelativeError)
    return true;

  return false;
}

Isso parece resolver a maioria dos problemas, combinando tolerância a erros relativa e absoluta.A abordagem ULP é melhor?Se sim, por quê?

@DrPizza:Não sou um guru de desempenho, mas espero que as operações de ponto fixo sejam mais rápidas que as operações de ponto flutuante (na maioria dos casos).

Depende bastante do que você está fazendo com eles.Um tipo de ponto fixo com o mesmo intervalo de um float IEEE seria muitas vezes mais lento (e muitas vezes maior).

Coisas adequadas para carros alegóricos:

Gráficos 3D, física/engenharia, simulação, simulação climática....

Em software numérico, muitas vezes você deseja testar se dois números de ponto flutuante são exatamente igual.LAPACK está cheio de exemplos para tais casos.Claro, o caso mais comum é quando você deseja testar se um número de ponto flutuante é igual a “Zero”, “Um”, “Dois”, “Metade”.Se alguém estiver interessado, posso escolher alguns algoritmos e entrar em mais detalhes.

Também no BLAS você geralmente deseja verificar se um número de ponto flutuante é exatamente Zero ou Um.Por exemplo, a rotina dgemv pode calcular operações da forma

  • y = beta*y + alfa*A*x
  • y = beta*y + alfa*A^T*x
  • y = beta*y + alfa*A^H*x

Portanto, se beta for igual a Um, você terá uma "atribuição positiva" e para beta igual a Zero, uma "atribuição simples".Portanto, você certamente pode reduzir o custo computacional se der um tratamento especial a esses casos (comuns).

Claro, você pode projetar as rotinas BLAS de forma a evitar comparações exatas (por exemplo,usando algumas bandeiras).Porém, o LAPACK está repleto de exemplos onde isso não é possível.

P.S.:

  • Certamente há muitos casos em que você não deseja verificar "é exatamente igual".Para muitas pessoas, este pode ser o único caso com o qual terão de lidar.Tudo o que quero salientar é que existem outros casos também.

  • Embora LAPACK seja escrito em Fortran, a lógica é a mesma se você estiver usando outras linguagens de programação para software numérico.

Oh, querido senhor, por favor, não interprete os bits flutuantes como inteiros, a menos que você esteja executando um P6 ou anterior.

Mesmo que isso faça com que ele copie de registros vetoriais para registros inteiros via memória, e mesmo que pare o pipeline, é a melhor maneira de fazer isso que encontrei, na medida em que fornece as comparações mais robustas, mesmo no face de erros de ponto flutuante.

ou sejaé um preço que vale a pena pagar.

Isso parece resolver a maioria dos problemas, combinando tolerância a erros relativa e absoluta.A abordagem ULP é melhor?Se sim, por quê?

ULPs são uma medida direta da “distância” entre dois números de ponto flutuante.Isso significa que eles não exigem que você evoque os valores de erro relativos e absolutos, nem que você tenha certeza de obter esses valores "quase corretos".Com os ULPs, você pode expressar diretamente o quão próximos deseja que os números estejam, e o mesmo limite funciona tão bem para valores pequenos quanto para valores grandes.

Se você tiver erros de ponto flutuante, terá ainda mais problemas do que isso.Embora eu ache que isso depende da perspectiva pessoal.

Mesmo que façamos a análise numérica para minimizar a acumulação de erros, não podemos eliminá-la e podemos ficar com resultados que deveriam ser idênticos (se estivéssemos calculando com reais), mas diferentes (porque não podemos calcular com reais).

Se você está procurando que dois carros alegóricos sejam iguais, então eles devem ser identicamente iguais, na minha opinião.Se você estiver enfrentando um problema de arredondamento de ponto flutuante, talvez uma representação de ponto fixo seja melhor adequada ao seu problema.

Se você está procurando que dois carros alegóricos sejam iguais, então eles devem ser identicamente iguais, na minha opinião.Se você estiver enfrentando um problema de arredondamento de ponto flutuante, talvez uma representação de ponto fixo seja melhor adequada ao seu problema.

Talvez não possamos permitir a perda de alcance ou desempenho que tal abordagem infligiria.

@DrPizza:Não sou um guru de desempenho, mas espero que as operações de ponto fixo sejam mais rápidas que as operações de ponto flutuante (na maioria dos casos).

@Craig H:Claro.Estou totalmente bem em imprimir isso.Se a ou b armazenam dinheiro, eles devem ser representados em ponto fixo.Estou lutando para pensar em um exemplo do mundo real onde tal lógica deveria ser aliada aos carros alegóricos.Coisas adequadas para carros alegóricos:

  • pesos
  • classificações
  • distâncias
  • valores do mundo real (como de um ADC)

Para todas essas coisas, você usa números e simplesmente apresenta os resultados ao usuário para interpretação humana, ou faz uma declaração comparativa (mesmo que tal afirmação seja "esta coisa está dentro de 0,001 desta outra coisa").Uma declaração comparativa como a minha só é útil no contexto do algoritmo:a parte "dentro de 0,001" depende do que físico pergunta que você está fazendo.Esse é o meu 0,02.Ou devo dizer 2/100?

Em vez disso, depende do que você está fazendo com eles.Um tipo de ponto fixo com o mesmo intervalo que um flutuador IEEE seria muitas vezes mais lento (e muitas vezes maior).

Ok, mas se eu quiser uma resolução de bits infinitamente pequena, volto ao meu ponto original:== e != não têm significado no contexto de tal problema.

Um int me permite expressar ~10^9 valores (independentemente do intervalo), o que parece suficiente para qualquer situação em que eu me importaria com o fato de dois deles serem iguais.E se isso não for suficiente, use um sistema operacional de 64 bits e você terá cerca de 10 ^ 19 valores distintos.

Posso expressar valores em um intervalo de 0 a 10 ^ 200 (por exemplo) em um int, é apenas a resolução de bits que sofre (a resolução seria maior que 1, mas, novamente, nenhum aplicativo tem esse tipo de intervalo também como esse tipo de resolução).

Para resumir, acho que em todos os casos ou se está representando um continuum de valores, caso em que != e == são irrelevantes, ou se está representando um conjunto fixo de valores, que pode ser mapeado para um int (ou outro fixo -tipo de precisão).

Um int permite expressar ~ 10^9 valores (independentemente do intervalo) que parecem suficientes para qualquer situação em que eu me importe com dois deles sendo iguais.E se isso não for suficiente, use um sistema operacional de 64 bits e você terá cerca de 10^19 valores distintos.

Na verdade, cheguei a esse limite...Eu estava tentando conciliar tempos em ps e tempo em ciclos de clock em uma simulação onde você atinge facilmente 10 ^ 10 ciclos.Não importa o que eu fiz, rapidamente ultrapassei o insignificante intervalo de números inteiros de 64 bits ...10 ^ 19 não é tanto quanto você pensa, dê-me computação de 128 bits agora!

Os floats me permitiram obter uma solução para os problemas matemáticos, pois os valores transbordavam com muitos zeros na extremidade inferior.Então você basicamente tinha um ponto decimal flutuando no número sem perda de precisão (eu poderia gostar do número distinto mais limitado de valores permitidos na mantissa de um float em comparação com um int de 64 bits, mas precisava desesperadamente do intervalo! ).

E então as coisas foram convertidas novamente em números inteiros para comparação, etc.

Irritante, e no final desisti de toda a tentativa e confiei apenas em carros alegóricos e < e > para realizar o trabalho.Não é perfeito, mas funciona para o caso de uso previsto.

Se você está procurando que dois carros alegóricos sejam iguais, então eles devem ser identicamente iguais, na minha opinião.Se você estiver enfrentando um problema de arredondamento de ponto flutuante, talvez uma representação de ponto fixo seja melhor adequada ao seu problema.

Talvez eu devesse explicar melhor o problema.Em C++, o seguinte código:

#include <iostream>

using namespace std;


int main()
{
  float a = 1.0;
  float b = 0.0;

  for(int i=0;i<10;++i)
  {
    b+=0.1;
  }

  if(a != b)
  {
    cout << "Something is wrong" << endl;
  }

  return 1;
}

imprime a frase "Algo está errado".Você está dizendo que deveria?

Oh, querido senhor, por favor, não interprete os bits flutuantes como inteiros, a menos que você esteja executando um P6 ou anterior.

é a melhor maneira de fazer isso que encontrei, na medida em que fornece as comparações mais robustas, mesmo diante de erros de ponto flutuante.

Se você tiver erros de ponto flutuante, terá ainda mais problemas do que isso.Embora eu ache que isso depende da perspectiva pessoal.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top