Qual é a maneira mais eficaz de comparação flutuante e dupla?


524

Qual seria a maneira mais eficiente de comparar dois doubleou dois floatvalores?

Simplesmente fazer isso não está correto:

bool CompareDoubles1 (double A, double B)
{
   return A == B;
}

Mas algo como:

bool CompareDoubles2 (double A, double B) 
{
   diff = A - B;
   return (diff < EPSILON) && (-diff < EPSILON);
}

Parece processar resíduos.

Alguém conhece um comparador de flutuador mais inteligente?


2
> seria mais eficiente adicionar ... no início da função? <invoke Knuth>Otimização prematura é a raiz de todo o mal. </invoke Knuth>Basta ir com abs (ab) <EPS, como observado acima, é claro e fácil de entender.
Andrew Coleson


2
A única coisa desagradável na implementação do pôster original é que ele contém uma ramificação extra em &&. A resposta de OJ é ótima. fabs é um intrínseco, que é uma única instrução em x87, e suponho que em quase qualquer outra coisa também. Aceite a resposta do JO já!
3yE 27/03

3
Se puder, solte o ponto flutuante e use pontos fixos. Exemplo, use {ponto fixo} milímetros em vez de {ponto flutuante} metros.
Thomas Matthews

33
"Simplesmente fazer isso não está correto" - Isso é mero lixo, é claro que o uso ==pode ser perfeitamente correto, mas isso depende inteiramente do contexto não fornecido na pergunta. Até que esse contexto seja conhecido, ==ainda permanece o "caminho mais eficiente" .
Christian Rau

Respostas:


459

Seja extremamente cuidadoso ao usar qualquer uma das outras sugestões. Tudo depende do contexto.

Passei muito tempo rastreando bugs em um sistema que presumia a==bse |a-b|<epsilon. Os problemas subjacentes foram:

  1. A presunção implícita em um algoritmo que se a==be b==centão a==c.

  2. Usando o mesmo epsilon para linhas medidas em polegadas e linhas medidas em mils (0,001 polegadas). Isso é a==bmas 1000a!=1000b. (É por isso que o AlmostEqual2sComplement solicita o epsilon ou max ULPS).

  3. O uso do mesmo epsilon para o cosseno dos ângulos e o comprimento das linhas!

  4. Usando essa função de comparação para classificar itens em uma coleção. (Nesse caso, usar o operador C ++ interno == para duplas produziu resultados corretos.)

Como eu disse: tudo depende do contexto e do tamanho esperado de ae b.

BTW, std::numeric_limits<double>::epsilon() é a "máquina epsilon". É a diferença entre 1,0 e o próximo valor representável por um duplo. Acho que poderia ser usado na função de comparação, mas apenas se os valores esperados forem menores que 1. (Isso é uma resposta à resposta do @ cdv ...)

Além disso, se você basicamente possui intaritmética doubles(aqui usamos duplos para armazenar valores int em certos casos), sua aritmética estará correta. Por exemplo, 4.0 / 2.0 será o mesmo que 1.0 + 1.0. Isso é contanto que você não faça coisas que resultem em frações (4.0 / 3.0) ou não saia do tamanho de um int.


10
+1 por apontar o óbvio (que geralmente é ignorado). Para um método genérico, você pode fazer o épsilon relativo a, fabs(a)+fabs(b)mas compensando NaN, soma 0 e excesso, isso fica bastante complexo.
Peterchen 7/08

4
Deve haver algo que eu não entendo. O típico float/ doubleé MANTISSA x 2 ^ EXP . epsilonserá dependente do expoente. Por exemplo, se a mantissa tiver 24 bits e o expoente tiver 8 bits, então 1/(2^24)*2^127ou ~2^103é um epsilonpara alguns valores; ou isso se refere a um epsilon mínimo ?
ruído artless

3
Espere um segundo. É o que eu disse o que você quis dizer? Você está dizendo o porquê |a-b|<epsilon, não está correto. Por favor, adicione este link à sua resposta; se você concorda em cygnus-software.com/papers/comparingfloats/comparingfloats.htm e eu posso remover meus comentários idiotas.
ruído artless 17/03/2013

3
Este é um comentário muito longo, não uma resposta em si. Existe (um conjunto de) respostas canônicas para todos os contextos?
Merlyn Morgan-Graham

2
O link de idade parece ser obsoleto, nova página está aqui randomascii.wordpress.com/2012/02/25/...
Marson Mao

174

A comparação com um valor epsilon é o que a maioria das pessoas faz (mesmo em programação de jogos).

Você deve alterar sua implementação um pouco:

bool AreSame(double a, double b)
{
    return fabs(a - b) < EPSILON;
}

Edit: Christer adicionou uma pilha de ótimas informações sobre este tópico em uma postagem recente no blog . Aproveitar.


@OJ: há algo errado com o primeiro exemplo de código? Eu pensei que o único problema estava em uma situação como esta: float a = 3.4; if(a == 3.4){...}ou seja, quando você está comparando um ponto flutuante armazenado com um literal | Nesse caso, os dois números são armazenados e, portanto, terão a mesma representação, se iguais. Qual é o mal em fazer a == b?
Lazer

11
@DonReba: Somente se EPSILONfor definido como DBL_EPSILON. Normalmente, será um valor específico escolhido, dependendo da precisão exigida da comparação.
Nemo157

7
EPSILONA comparação não funciona quando os carros alegóricos são grandes, pois a diferença entre carros alegóricos consecutivos também se torna grande. Veja este artigo .
Kevintodisco

22
Não é de admirar que haja combates em Z em alguns jogos quando texturas / objetos distantes piscam, como no Battlefield 4. Comparar a diferença com EPSILONé praticamente inútil. Você precisa comparar com um limite que faça sentido para as unidades disponíveis. Além disso, use, std::abspois está sobrecarregado para diferentes tipos de ponto flutuante.
Maxim Egorushkin

11
Eu diminuí o voto porque o código de exemplo mostra que o bug típico whis é repetido pela maioria dos programadores. O ponto flutuante é sempre sobre erros relativos, pois é ponto flutuante (não ponto fixo). Portanto, nunca funcionará corretamente com um erro fixo (epsilon).
user2261015

115

Descobri que o Google C ++ Testing Framework contém uma boa implementação baseada em modelo de plataforma cruzada de AlmostEqual2sComplement, que funciona tanto em duplos quanto em flutuadores. Como ele é liberado sob a licença BSD, usá-lo em seu próprio código não deve ser problema, desde que você mantenha a licença. Extraí o código abaixo de http://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.h https://github.com/google/googletest/blob /master/googletest/include/gtest/internal/gtest-internal.h e adicionou a licença na parte superior.

#Define GTEST_OS_WINDOWS com algum valor (ou altere o código em que é usado para algo que se adapta à sua base de código - afinal, é licenciado em BSD).

Exemplo de uso:

double left  = // something
double right = // something
const FloatingPoint<double> lhs(left), rhs(right);

if (lhs.AlmostEquals(rhs)) {
  //they're equal!
}

Aqui está o código:

// Copyright 2005, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Authors: wan@google.com (Zhanyong Wan), eefacm@gmail.com (Sean Mcafee)
//
// The Google C++ Testing Framework (Google Test)


// This template class serves as a compile-time function from size to
// type.  It maps a size in bytes to a primitive type with that
// size. e.g.
//
//   TypeWithSize<4>::UInt
//
// is typedef-ed to be unsigned int (unsigned integer made up of 4
// bytes).
//
// Such functionality should belong to STL, but I cannot find it
// there.
//
// Google Test uses this class in the implementation of floating-point
// comparison.
//
// For now it only handles UInt (unsigned int) as that's all Google Test
// needs.  Other types can be easily added in the future if need
// arises.
template <size_t size>
class TypeWithSize {
 public:
  // This prevents the user from using TypeWithSize<N> with incorrect
  // values of N.
  typedef void UInt;
};

// The specialization for size 4.
template <>
class TypeWithSize<4> {
 public:
  // unsigned int has size 4 in both gcc and MSVC.
  //
  // As base/basictypes.h doesn't compile on Windows, we cannot use
  // uint32, uint64, and etc here.
  typedef int Int;
  typedef unsigned int UInt;
};

// The specialization for size 8.
template <>
class TypeWithSize<8> {
 public:
#if GTEST_OS_WINDOWS
  typedef __int64 Int;
  typedef unsigned __int64 UInt;
#else
  typedef long long Int;  // NOLINT
  typedef unsigned long long UInt;  // NOLINT
#endif  // GTEST_OS_WINDOWS
};


// This template class represents an IEEE floating-point number
// (either single-precision or double-precision, depending on the
// template parameters).
//
// The purpose of this class is to do more sophisticated number
// comparison.  (Due to round-off error, etc, it's very unlikely that
// two floating-points will be equal exactly.  Hence a naive
// comparison by the == operation often doesn't work.)
//
// Format of IEEE floating-point:
//
//   The most-significant bit being the leftmost, an IEEE
//   floating-point looks like
//
//     sign_bit exponent_bits fraction_bits
//
//   Here, sign_bit is a single bit that designates the sign of the
//   number.
//
//   For float, there are 8 exponent bits and 23 fraction bits.
//
//   For double, there are 11 exponent bits and 52 fraction bits.
//
//   More details can be found at
//   http://en.wikipedia.org/wiki/IEEE_floating-point_standard.
//
// Template parameter:
//
//   RawType: the raw floating-point type (either float or double)
template <typename RawType>
class FloatingPoint {
 public:
  // Defines the unsigned integer type that has the same size as the
  // floating point number.
  typedef typename TypeWithSize<sizeof(RawType)>::UInt Bits;

  // Constants.

  // # of bits in a number.
  static const size_t kBitCount = 8*sizeof(RawType);

  // # of fraction bits in a number.
  static const size_t kFractionBitCount =
    std::numeric_limits<RawType>::digits - 1;

  // # of exponent bits in a number.
  static const size_t kExponentBitCount = kBitCount - 1 - kFractionBitCount;

  // The mask for the sign bit.
  static const Bits kSignBitMask = static_cast<Bits>(1) << (kBitCount - 1);

  // The mask for the fraction bits.
  static const Bits kFractionBitMask =
    ~static_cast<Bits>(0) >> (kExponentBitCount + 1);

  // The mask for the exponent bits.
  static const Bits kExponentBitMask = ~(kSignBitMask | kFractionBitMask);

  // How many ULP's (Units in the Last Place) we want to tolerate when
  // comparing two numbers.  The larger the value, the more error we
  // allow.  A 0 value means that two numbers must be exactly the same
  // to be considered equal.
  //
  // The maximum error of a single floating-point operation is 0.5
  // units in the last place.  On Intel CPU's, all floating-point
  // calculations are done with 80-bit precision, while double has 64
  // bits.  Therefore, 4 should be enough for ordinary use.
  //
  // See the following article for more details on ULP:
  // http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm.
  static const size_t kMaxUlps = 4;

  // Constructs a FloatingPoint from a raw floating-point number.
  //
  // On an Intel CPU, passing a non-normalized NAN (Not a Number)
  // around may change its bits, although the new value is guaranteed
  // to be also a NAN.  Therefore, don't expect this constructor to
  // preserve the bits in x when x is a NAN.
  explicit FloatingPoint(const RawType& x) { u_.value_ = x; }

  // Static methods

  // Reinterprets a bit pattern as a floating-point number.
  //
  // This function is needed to test the AlmostEquals() method.
  static RawType ReinterpretBits(const Bits bits) {
    FloatingPoint fp(0);
    fp.u_.bits_ = bits;
    return fp.u_.value_;
  }

  // Returns the floating-point number that represent positive infinity.
  static RawType Infinity() {
    return ReinterpretBits(kExponentBitMask);
  }

  // Non-static methods

  // Returns the bits that represents this number.
  const Bits &bits() const { return u_.bits_; }

  // Returns the exponent bits of this number.
  Bits exponent_bits() const { return kExponentBitMask & u_.bits_; }

  // Returns the fraction bits of this number.
  Bits fraction_bits() const { return kFractionBitMask & u_.bits_; }

  // Returns the sign bit of this number.
  Bits sign_bit() const { return kSignBitMask & u_.bits_; }

  // Returns true iff this is NAN (not a number).
  bool is_nan() const {
    // It's a NAN if the exponent bits are all ones and the fraction
    // bits are not entirely zeros.
    return (exponent_bits() == kExponentBitMask) && (fraction_bits() != 0);
  }

  // Returns true iff this number is at most kMaxUlps ULP's away from
  // rhs.  In particular, this function:
  //
  //   - returns false if either number is (or both are) NAN.
  //   - treats really large numbers as almost equal to infinity.
  //   - thinks +0.0 and -0.0 are 0 DLP's apart.
  bool AlmostEquals(const FloatingPoint& rhs) const {
    // The IEEE standard says that any comparison operation involving
    // a NAN must return false.
    if (is_nan() || rhs.is_nan()) return false;

    return DistanceBetweenSignAndMagnitudeNumbers(u_.bits_, rhs.u_.bits_)
        <= kMaxUlps;
  }

 private:
  // The data type used to store the actual floating-point number.
  union FloatingPointUnion {
    RawType value_;  // The raw floating-point number.
    Bits bits_;      // The bits that represent the number.
  };

  // Converts an integer from the sign-and-magnitude representation to
  // the biased representation.  More precisely, let N be 2 to the
  // power of (kBitCount - 1), an integer x is represented by the
  // unsigned number x + N.
  //
  // For instance,
  //
  //   -N + 1 (the most negative number representable using
  //          sign-and-magnitude) is represented by 1;
  //   0      is represented by N; and
  //   N - 1  (the biggest number representable using
  //          sign-and-magnitude) is represented by 2N - 1.
  //
  // Read http://en.wikipedia.org/wiki/Signed_number_representations
  // for more details on signed number representations.
  static Bits SignAndMagnitudeToBiased(const Bits &sam) {
    if (kSignBitMask & sam) {
      // sam represents a negative number.
      return ~sam + 1;
    } else {
      // sam represents a positive number.
      return kSignBitMask | sam;
    }
  }

  // Given two numbers in the sign-and-magnitude representation,
  // returns the distance between them as an unsigned number.
  static Bits DistanceBetweenSignAndMagnitudeNumbers(const Bits &sam1,
                                                     const Bits &sam2) {
    const Bits biased1 = SignAndMagnitudeToBiased(sam1);
    const Bits biased2 = SignAndMagnitudeToBiased(sam2);
    return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1);
  }

  FloatingPointUnion u_;
};

EDIT: Este post tem 4 anos. Provavelmente ainda é válido, e o código é bom, mas algumas pessoas encontraram melhorias. É melhor obter a versão mais recente do AlmostEqualscódigo-fonte do Teste do Google, e não a que colei aqui.


3
+1: Concordo que este está correto. No entanto, isso não explica o porquê. Veja aqui: cygnus-software.com/papers/comparingfloats/comparingfloats.htm Li este post do blog depois que escrevi meu comentário sobre a pontuação máxima aqui; Eu acredito que ele diz a mesma coisa e fornece a solução / racional implementada acima. Como há muito código, as pessoas vão perder a resposta.
ruído

Existem algumas coisas desagradáveis ​​que podem acontecer quando ocorrem projeções implícitas, por exemplo, FloatPoint <double> fp (0.03f). Fiz algumas modificações para ajudar a evitar isso. template <typename U> FloatingPoint explícito (const U & x) {if (typeid (U) .name ()! = typeid (RawType) .name ()) {std :: cerr << "Você está fazendo uma conversão implícita com FloatingPoint, Don't "<< std :: endl; assert (typeid (U) .name () == typeid (RawType) .name ()); } u_.valor_ = x; }
JeffCharter

2
Boa descoberta! Acho que seria melhor contribuí-los para o Google Test, de onde esse código foi roubado. Vou atualizar o post para refletir que provavelmente há uma versão mais nova. Se os caras do Google agem com coceira, você poderia colocar, por exemplo, uma essência do GitHub? Vou ligar para isso também, então.
Skrebbel

3
Para o trecho de código mais recente, veja aqui e aqui .
Jaege 25/11/16

1
Eu extraí as linhas necessárias para um arquivo de essência. Qualquer um pode chegar daqui .
Yusuf Tarık Günaydın

111

A comparação de números de ponto flutuante depende do contexto. Como mesmo alterar a ordem das operações pode produzir resultados diferentes, é importante saber o quão "igual" você deseja que os números sejam.

A comparação de números de ponto flutuante de Bruce Dawson é um bom ponto de partida para a comparação de pontos flutuantes.

As seguintes definições são de A arte da programação de computadores de Knuth :

bool approximatelyEqual(float a, float b, float epsilon)
{
    return fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}

bool essentiallyEqual(float a, float b, float epsilon)
{
    return fabs(a - b) <= ( (fabs(a) > fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}

bool definitelyGreaterThan(float a, float b, float epsilon)
{
    return (a - b) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}

bool definitelyLessThan(float a, float b, float epsilon)
{
    return (b - a) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}

Obviamente, a escolha de epsilon depende do contexto e determina o quanto você deseja que os números sejam iguais.

Outro método de comparar números de ponto flutuante é examinar o ULP (unidades em último lugar) dos números. Embora não lide especificamente com comparações, o artigo O que todo cientista da computação deve saber sobre números de ponto flutuante é um bom recurso para entender como o ponto flutuante funciona e quais são as armadilhas, incluindo o que é ULP.


1
obrigado por postar como determinar qual número é menor / maior!
Tomate

1
fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);salvou minha vida. LOL Observe que esta versão (eu não verifiquei se também se aplica às outras) também considera a alteração que pode ocorrer na parte integrante do número do ponto flutuante (exemplo: 2147352577.9999997616 == 2147352576.0000000000onde você pode ver claramente que há quase uma diferença de 2entre os dois números), o que é bastante agradável! Isso acontece quando o erro de arredondamento acumulado excede a parte decimal do número.
Rbaleksandar

Artigo muito agradável e útil de Bruce Dawson, obrigado!
BobMorane

2
Dado que esta pergunta está marcada como C ++, seria mais fácil ler seus cheques sendo escritos como std::max(std::abs(a), std::abs(b))(ou com std::min()); std::absno C ++ é sobrecarregado com tipos float e double, então funciona muito bem (você sempre pode manter fabsa legibilidade).
Razakhel 01/10/1918

1
Acontece que o problema estava no meu código, diferença entre o valor esperado original e a string analisada.
Mwpowellhtx

47

Para uma abordagem mais aprofundada, leia Comparando números de ponto flutuante . Aqui está o trecho de código desse link:

// Usable AlmostEqual function    
bool AlmostEqual2sComplement(float A, float B, int maxUlps)    
{    
    // Make sure maxUlps is non-negative and small enough that the    
    // default NAN won't compare as equal to anything.    
    assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024);    
    int aInt = *(int*)&A;    
    // Make aInt lexicographically ordered as a twos-complement int    
    if (aInt < 0)    
        aInt = 0x80000000 - aInt;    
    // Make bInt lexicographically ordered as a twos-complement int    
    int bInt = *(int*)&B;    
    if (bInt < 0)    
        bInt = 0x80000000 - bInt;    
    int intDiff = abs(aInt - bInt);    
    if (intDiff <= maxUlps)    
        return true;    
    return false;    
}

14
Qual é o valor sugerido de maxUlps?
unj2

6
" *(int*)&A;" Irá violar a regra estrita de aliasing?
osgx 11/08

3
De acordo com o gtest (procure por ULP), 4 é um número aceitável.
May Oakes

4
E aqui estão algumas atualizações do artigo de Bruce Dawson (um dos quais está vinculado na introdução do artigo): randomascii.wordpress.com/2012/02/25/… e randomascii.wordpress.com/2012/06/26/…
Michael Burr

3
Levei um tempo para descobrir o que era o ULP: Unidades em Último Lugar
JeffCharter

27

Percebendo que este é um tópico antigo, mas este artigo é um dos mais diretos que encontrei na comparação de números de ponto flutuante e, se você quiser explorar mais, ele também tem referências mais detalhadas e o site principal cobre uma gama completa de problemas Lidar com números de ponto flutuante O Guia de ponto flutuante: Comparação .

Podemos encontrar um artigo um pouco mais prático nas tolerâncias de ponto flutuante revisitadas e observa que existe um teste de tolerância absoluta , que se resume a isso no C ++:

bool absoluteToleranceCompare(double x, double y)
{
    return std::fabs(x - y) <= std::numeric_limits<double>::epsilon() ;
}

e teste de tolerância relativa :

bool relativeToleranceCompare(double x, double y)
{
    double maxXY = std::max( std::fabs(x) , std::fabs(y) ) ;
    return std::fabs(x - y) <= std::numeric_limits<double>::epsilon()*maxXY ;
}

As notas do artigo que o teste absoluta falha quando xe ysão grandes e não no caso relativo quando eles são pequenos. Supondo que a tolerância absoluta e relativa seja a mesma, um teste combinado seria assim:

bool combinedToleranceCompare(double x, double y)
{
    double maxXYOne = std::max( { 1.0, std::fabs(x) , std::fabs(y) } ) ;

    return std::fabs(x - y) <= std::numeric_limits<double>::epsilon()*maxXYOne ;
}

25

A maneira portátil de obter epsilon em C ++ é

#include <limits>
std::numeric_limits<double>::epsilon()

Então a função de comparação se torna

#include <cmath>
#include <limits>

bool AreSame(double a, double b) {
    return std::fabs(a - b) < std::numeric_limits<double>::epsilon();
}

34
Você provavelmente desejará um múltiplo desse epsilon.
precisa saber é o seguinte

11
Você não pode simplesmente usar std :: abs? AFAIK, std :: abs também está sobrecarregado para duplos. Por favor, avise-me se eu estiver errado.
Kolistivra

3
@kolistivra, você está errado. O 'f' em 'fabs' não significa o tipo float. Você provavelmente está pensando nas funções C fabsf () e fabsl ().
Jcoffland

9
Na verdade, pelas razões descritas no artigo de Bruce, o epsilon muda à medida que o valor do ponto flutuante aumenta. Veja a parte em que ele diz: "Para números maiores que 2,0, a diferença entre flutuadores aumenta e se você comparar flutuadores usando FLT_EPSILON, estará apenas fazendo uma verificação de igualdade mais cara e menos óbvia".
bobobobo

5
Eu sei que isso é antigo, mas std :: abs está sobrecarregado para tipos de ponto flutuante no cmath.
mholzmann

18

Acabei passando bastante tempo analisando material nesse ótimo tópico. Duvido que todo mundo queira gastar tanto tempo, para destacar o resumo do que aprendi e a solução que implementei.

Resumo Rápido

  1. 1e-8 é aproximadamente o mesmo que 1e-16? Se você estiver olhando para dados ruidosos do sensor, provavelmente sim, mas se estiver fazendo simulação molecular, talvez não! Conclusão: você sempre precisa pensar no valor da tolerância no contexto de uma chamada de função específica e não apenas torná-la constante codificada em todo o aplicativo genérica.
  2. Para funções gerais da biblioteca, ainda é bom ter parâmetro com tolerância padrão . Uma escolha típica numeric_limits::epsilon()é a mesma que FLT_EPSILON em float.h. No entanto, isso é problemático porque o epsilon para comparar valores como 1,0 não é o mesmo que o epsilon para valores como 1E9. O FLT_EPSILON está definido para 1.0.
  3. A implementação óbvia para verificar se o número está dentro da tolerância é, fabs(a-b) <= epsilonno entanto, isso não funciona, porque o epsilon padrão está definido para 1.0. Precisamos escalar epsilon para cima ou para baixo nos termos de a e b.
  4. Existem duas soluções para esse problema: você define o épsilon proporcional a max(a,b)ou pode obter os próximos números representáveis ​​em torno de a e depois ver se b cai nesse intervalo. O primeiro é chamado método "relativo" e mais tarde é chamado método ULP.
  5. Ambos os métodos realmente falham de qualquer maneira ao comparar com 0. Nesse caso, o aplicativo deve fornecer a tolerância correta.

Implementação de funções de utilitário (C ++ 11)

//implements relative method - do not use for comparing with zero
//use this most of the time, tolerance needs to be meaningful in your context
template<typename TReal>
static bool isApproximatelyEqual(TReal a, TReal b, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
    TReal diff = std::fabs(a - b);
    if (diff <= tolerance)
        return true;

    if (diff < std::fmax(std::fabs(a), std::fabs(b)) * tolerance)
        return true;

    return false;
}

//supply tolerance that is meaningful in your context
//for example, default tolerance may not work if you are comparing double with float
template<typename TReal>
static bool isApproximatelyZero(TReal a, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
    if (std::fabs(a) <= tolerance)
        return true;
    return false;
}


//use this when you want to be on safe side
//for example, don't start rover unless signal is above 1
template<typename TReal>
static bool isDefinitelyLessThan(TReal a, TReal b, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
    TReal diff = a - b;
    if (diff < tolerance)
        return true;

    if (diff < std::fmax(std::fabs(a), std::fabs(b)) * tolerance)
        return true;

    return false;
}
template<typename TReal>
static bool isDefinitelyGreaterThan(TReal a, TReal b, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
    TReal diff = a - b;
    if (diff > tolerance)
        return true;

    if (diff > std::fmax(std::fabs(a), std::fabs(b)) * tolerance)
        return true;

    return false;
}

//implements ULP method
//use this when you are only concerned about floating point precision issue
//for example, if you want to see if a is 1.0 by checking if its within
//10 closest representable floating point numbers around 1.0.
template<typename TReal>
static bool isWithinPrecisionInterval(TReal a, TReal b, unsigned int interval_size = 1)
{
    TReal min_a = a - (a - std::nextafter(a, std::numeric_limits<TReal>::lowest())) * interval_size;
    TReal max_a = a + (std::nextafter(a, std::numeric_limits<TReal>::max()) - a) * interval_size;

    return min_a <= b && max_a >= b;
}

isDefinitelyLessThancheques diff < tolerance, o que significa que aeb são quase iguais (e, portanto, a não é definitivamente menor que b). Não faz mais sentido verificar a tolerância de diferenças nos dois casos? Ou talvez adicione um orEqualToargumento que controla se a verificação aproximada da igualdade deve retornar verdadeira ou não.
Matt Chambers

14

O código que você escreveu está com erros:

return (diff < EPSILON) && (-diff > EPSILON);

O código correto seria:

return (diff < EPSILON) && (diff > -EPSILON);

(... e sim, isso é diferente)

Gostaria de saber se fabs não faria você perder a avaliação preguiçosa em algum caso. Eu diria que depende do compilador. Você pode tentar os dois. Se eles são equivalentes em média, leve a implementação com fabs.

Se você tiver alguma informação sobre qual dos dois flutuadores tem maior probabilidade de ser maior que os outros, você pode jogar na ordem da comparação para aproveitar melhor a avaliação lenta.

Finalmente, você pode obter melhores resultados inserindo esta função. Não é provável que melhore muito ...

Edit: JO, obrigado por corrigir seu código. Eu apaguei meu comentário de acordo


13

`retornar fabs (a - b) <EPSILON;

Isso é bom se:

  • a ordem de magnitude de suas entradas não muda muito
  • um número muito pequeno de sinais opostos pode ser tratado como igual

Mas, caso contrário, isso o levará a problemas. Os números de precisão dupla têm uma resolução de cerca de 16 casas decimais. Se os dois números que você está comparando são maiores em magnitude que o EPSILON * 1.0E16, então você pode estar dizendo:

return a==b;

Examinarei uma abordagem diferente, que pressupõe que você precisa se preocupar com o primeiro problema e suponha que o segundo seja bom para o seu aplicativo. Uma solução seria algo como:

#define VERYSMALL  (1.0E-150)
#define EPSILON    (1.0E-8)
bool AreSame(double a, double b)
{
    double absDiff = fabs(a - b);
    if (absDiff < VERYSMALL)
    {
        return true;
    }

    double maxAbs  = max(fabs(a) - fabs(b));
    return (absDiff/maxAbs) < EPSILON;
}

Isso é caro computacionalmente, mas às vezes é o que é necessário. É isso que temos que fazer na minha empresa, porque lidamos com uma biblioteca de engenharia e as entradas podem variar em algumas dezenas de ordens de magnitude.

De qualquer forma, o ponto é este (e se aplica a praticamente todos os problemas de programação): avalie quais são suas necessidades e, em seguida, encontre uma solução para atender às suas necessidades - não pense que a resposta fácil atenderá às suas necessidades. Se após sua avaliação você achar que isso fabs(a-b) < EPSILONé suficiente, perfeito - use-o! Mas esteja ciente de suas deficiências e de outras soluções possíveis também.


3
Além dos erros de digitação (s / - /, / falta de vírgula em fmax ()), esta implementação possui um bug para números próximos de zero que estão dentro do EPSILON, mas ainda não muito VERYSMALL ainda. Por exemplo, AreSame (1.0E-10, 1.0E-9) relata falso porque o erro relativo é enorme. Você é o herói da sua empresa.
Brlcad 21/10/10

1
@ brlcad Você não conseguiu o ponto de ponto flutuante . 1.0E-10 e 1.0E-9 diferem pela magnitude de 10. Portanto, é verdade que eles não são os mesmos. ponto flutuante é sempre sobre erros relativos . Se você possui um sistema que considera 1.0E-10 e 1.0E-9 quase iguais, uma vez que ambos são "bem próximos de zero" (o que parece razoável para os seres humanos, mas não é matematicamente), então o EPSILON precisa ser ajustado conforme apropriado para esse sistema.
user2261015

8

Como outros já apontaram, o uso de um epsilon de expoente fixo (como 0,0000001) será inútil para valores distantes do valor do epsilon. Por exemplo, se seus dois valores são 10000.000977 e 10000, então NÃO existem valores de ponto flutuante de 32 bits entre esses dois números - 10000 e 10000.000977 estão tão próximos quanto você pode obter sem serem idênticos bit a bit. Aqui, um epsilon inferior a 0,0009 não tem sentido; você também pode usar o operador de igualdade direta.

Da mesma forma, à medida que os dois valores se aproximam do tamanho epsilon, o erro relativo aumenta para 100%.

Portanto, tentar misturar um número de ponto fixo, como 0,00001, com valores de ponto flutuante (onde o expoente é arbitrário) é um exercício inútil. Isso só funcionará se você puder ter certeza de que os valores do operando estão em um domínio estreito (ou seja, próximo a algum expoente específico) e se você selecionar corretamente um valor épsilon para esse teste específico. Se você puxar um número do ar ("Ei! 0.00001 é pequeno, então isso deve ser bom!"), Você estará fadado a erros numéricos. Passei muito tempo depurando códigos numéricos ruins, em que algum pobre schmuck lança valores aleatórios de epsilon para fazer com que outro caso de teste funcione.

Se você faz programação numérica de qualquer tipo e acredita que precisa buscar epsilons de ponto fixo, LEIA O ARTIGO DE BRUCE SOBRE COMPARAÇÃO DE NÚMEROS DE PONTOS FLUTUANTES .

Comparando números de ponto flutuante


5

O Qt implementa duas funções, talvez você possa aprender com elas:

static inline bool qFuzzyCompare(double p1, double p2)
{
    return (qAbs(p1 - p2) <= 0.000000000001 * qMin(qAbs(p1), qAbs(p2)));
}

static inline bool qFuzzyCompare(float p1, float p2)
{
    return (qAbs(p1 - p2) <= 0.00001f * qMin(qAbs(p1), qAbs(p2)));
}

E você pode precisar das seguintes funções, pois

Observe que comparar valores em que p1 ou p2 é 0,0 não funcionará, nem comparar valores em que um dos valores é NaN ou infinito. Se um dos valores for sempre 0,0, use qFuzzyIsNull. Se um dos valores provavelmente for 0,0, uma solução é adicionar 1,0 a ambos os valores.

static inline bool qFuzzyIsNull(double d)
{
    return qAbs(d) <= 0.000000000001;
}

static inline bool qFuzzyIsNull(float f)
{
    return qAbs(f) <= 0.00001f;
}

3

A comparação de uso geral de números de ponto flutuante geralmente não faz sentido. Como comparar realmente depende de um problema em questão. Em muitos problemas, os números são suficientemente discretizados para permitir compará-los dentro de uma determinada tolerância. Infelizmente, existem tantos problemas em que esse truque realmente não funciona. Por exemplo, considere trabalhar com uma função Heaviside (etapa) de um número em questão (opções de ações digitais vêm à mente) quando suas observações estiverem muito próximas da barreira. Realizar comparações baseadas em tolerância não faria muito bem, pois efetivamente mudaria o problema da barreira original para duas novas. Novamente, não há uma solução de uso geral para esses problemas e a solução específica pode exigir uma alteração no método numérico para obter estabilidade.


3

Infelizmente, mesmo o seu código "desperdiçador" está incorreto. EPSILON é o menor valor que pode ser adicionado a 1,0 e alterar seu valor. O valor 1.0 é muito importante - números maiores não mudam quando adicionados ao EPSILON. Agora, você pode dimensionar esse valor para os números que você está comparando para saber se são diferentes ou não. A expressão correta para comparar duas duplas é:

if (fabs(a - b) <= DBL_EPSILON * fmax(fabs(a), fabs(b)))
{
    // ...
}

Isso é no mínimo. Em geral, porém, você deve considerar o ruído em seus cálculos e ignorar alguns dos bits menos significativos; portanto, uma comparação mais realista seria semelhante a:

if (fabs(a - b) <= 16 * DBL_EPSILON * fmax(fabs(a), fabs(b)))
{
    // ...
}

Se o desempenho da comparação for muito importante para você e você souber o intervalo de seus valores, use números de ponto fixo.


2
“EPSILON é o menor valor que pode ser adicionado a 1,0 e alterar seu valor”: Na verdade, essa homenagem vai para o sucessor de 0,5 * EPSILON (no modo padrão arredondar para o mais próximo). blog.frama-c.com/index.php?post/2013/05/09/FLT_EPSILON
Pascal Cuoq

Por que você acha que EPSILONna pergunta é DBL_EPSILONou FLT_EPSILON? O problema está em sua própria imaginação, onde você substituiu DBL_EPSILON(o que de fato seria a escolha errada) no código que não o usou.
amigos estão dizendo sobre ben

@BenVoigt, você está certo, era algo em minha mente na época, e eu interpretei a pergunta dessa maneira.
Don Reba

2

Minha turma com base nas respostas postadas anteriormente. Muito parecido com o código do Google, mas eu uso um viés que empurra todos os valores de NaN acima de 0xFF000000. Isso permite uma verificação mais rápida do NaN.

Este código pretende demonstrar o conceito, não ser uma solução geral. O código do Google já mostra como calcular todos os valores específicos da plataforma e eu não queria duplicar tudo isso. Eu fiz testes limitados nesse código.

typedef unsigned int   U32;
//  Float           Memory          Bias (unsigned)
//  -----           ------          ---------------
//   NaN            0xFFFFFFFF      0xFF800001
//   NaN            0xFF800001      0xFFFFFFFF
//  -Infinity       0xFF800000      0x00000000 ---
//  -3.40282e+038   0xFF7FFFFF      0x00000001    |
//  -1.40130e-045   0x80000001      0x7F7FFFFF    |
//  -0.0            0x80000000      0x7F800000    |--- Valid <= 0xFF000000.
//   0.0            0x00000000      0x7F800000    |    NaN > 0xFF000000
//   1.40130e-045   0x00000001      0x7F800001    |
//   3.40282e+038   0x7F7FFFFF      0xFEFFFFFF    |
//   Infinity       0x7F800000      0xFF000000 ---
//   NaN            0x7F800001      0xFF000001
//   NaN            0x7FFFFFFF      0xFF7FFFFF
//
//   Either value of NaN returns false.
//   -Infinity and +Infinity are not "close".
//   -0 and +0 are equal.
//
class CompareFloat{
public:
    union{
        float     m_f32;
        U32       m_u32;
    };
    static bool   CompareFloat::IsClose( float A, float B, U32 unitsDelta = 4 )
                  {
                      U32    a = CompareFloat::GetBiased( A );
                      U32    b = CompareFloat::GetBiased( B );

                      if ( (a > 0xFF000000) || (b > 0xFF000000) )
                      {
                          return( false );
                      }
                      return( (static_cast<U32>(abs( a - b ))) < unitsDelta );
                  }
    protected:
    static U32    CompareFloat::GetBiased( float f )
                  {
                      U32    r = ((CompareFloat*)&f)->m_u32;

                      if ( r & 0x80000000 )
                      {
                          return( ~r - 0x007FFFFF );
                      }
                      return( r + 0x7F800000 );
                  }
};

2

Aqui está a prova de que o uso std::numeric_limits::epsilon()não é a resposta - ele falha em valores maiores que um:

Prova do meu comentário acima:

#include <stdio.h>
#include <limits>

double ItoD (__int64 x) {
    // Return double from 64-bit hexadecimal representation.
    return *(reinterpret_cast<double*>(&x));
}

void test (__int64 ai, __int64 bi) {
    double a = ItoD(ai), b = ItoD(bi);
    bool close = std::fabs(a-b) < std::numeric_limits<double>::epsilon();
    printf ("%.16f and %.16f %s close.\n", a, b, close ? "are " : "are not");
}

int main()
{
    test (0x3fe0000000000000L,
          0x3fe0000000000001L);

    test (0x3ff0000000000000L,
          0x3ff0000000000001L);
}

A execução produz esta saída:

0.5000000000000000 and 0.5000000000000001 are  close.
1.0000000000000000 and 1.0000000000000002 are not close.

Observe que, no segundo caso (um e apenas maior que um), os dois valores de entrada estão o mais próximo possível e ainda comparam como não estão próximos. Portanto, para valores maiores que 1,0, você também pode usar apenas um teste de igualdade. Épsilons fixos não o salvarão ao comparar valores de ponto flutuante.


Acredito que, return *(reinterpret_cast<double*>(&x));embora geralmente funcione, é de fato um comportamento indefinido.
Jaap Versteegh

Ponto justo, embora este código seja ilustrativo, é suficiente para demonstrar a questão numeric_limits<>::epsilone o ponto de piso IEEE 754.
Steve Hollasch

Também é um ponto justo, mas não é aconselhável publicar no estouro de pilha esperando esse tipo de insight. O código será copiado às cegas, tornando cada vez mais difícil erradicar esse padrão muito comum - junto com o truque de união - que deve ser evitado como todos os UD.
Jaap Versteegh

1

Encontrei outra implementação interessante em: https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon

#include <cmath>
#include <limits>
#include <iomanip>
#include <iostream>
#include <type_traits>
#include <algorithm>



template<class T>
typename std::enable_if<!std::numeric_limits<T>::is_integer, bool>::type
    almost_equal(T x, T y, int ulp)
{
    // the machine epsilon has to be scaled to the magnitude of the values used
    // and multiplied by the desired precision in ULPs (units in the last place)
    return std::fabs(x-y) <= std::numeric_limits<T>::epsilon() * std::fabs(x+y) * ulp
        // unless the result is subnormal
        || std::fabs(x-y) < std::numeric_limits<T>::min();
}

int main()
{
    double d1 = 0.2;
    double d2 = 1 / std::sqrt(5) / std::sqrt(5);
    std::cout << std::fixed << std::setprecision(20) 
        << "d1=" << d1 << "\nd2=" << d2 << '\n';

    if(d1 == d2)
        std::cout << "d1 == d2\n";
    else
        std::cout << "d1 != d2\n";

    if(almost_equal(d1, d2, 2))
        std::cout << "d1 almost equals d2\n";
    else
        std::cout << "d1 does not almost equal d2\n";
}

0

Eu ficaria muito cauteloso com qualquer uma dessas respostas que envolva subtração de ponto flutuante (por exemplo, fabs (ab) <epsilon). Primeiro, os números de ponto flutuante tornam-se mais esparsos em magnitudes maiores e em magnitudes altas o suficiente, onde o espaçamento é maior que epsilon. Segundo, subtrair dois números de ponto flutuante muito próximos (como esses tenderão a ser, considerando que você está procurando uma igualdade quase) é exatamente como você obtém o cancelamento catastrófico .

Embora não seja portátil, acho que a resposta do grom faz o melhor trabalho para evitar esses problemas.


1
+1 para obter boas informações. No entanto, não vejo como você pode atrapalhar a comparação de igualdade aumentando o erro relativo; IMHO, o erro se torna significativo apenas no resultado da subtração, no entanto, sua ordem de magnitude em relação à dos dois operandos subtraídos ainda deve ser confiável o suficiente para julgar a igualdade. A menos que a resolução precise ser maior em geral, mas nesse caso a única solução é passar para uma representação de ponto flutuante com bits mais significativos na mantissa.
Veja

Subtrair dois números quase iguais NÃO leva ao cancelamento catastrófico - na verdade, ele não introduz nenhum erro (o Teorema de Sterbenz). Cancelamento catastrófica ocorre mais cedo, durante o cálculo de ae beles mesmos. Não há absolutamente nenhum problema com o uso de subtração de ponto flutuante como parte de uma comparação difusa (embora, como já foi dito, um valor de epsilon absoluta pode ou não ser apropriado para um determinado caso de uso.)
Sneftel

0

Na verdade, existem casos no software numérico em que você deseja verificar se dois números de ponto flutuante são exatamente iguais. Postei isso em uma pergunta semelhante

https://stackoverflow.com/a/10973098/1447411

Portanto, você não pode dizer que "CompareDoubles1" está errado em geral.


Na verdade, é uma referência muito sólida a uma boa resposta, embora seja muito especializado limitar qualquer pessoa sem experiência em computação científica ou análise numérica (isto é, LAPACK, BLAS) a não entender a totalidade. Ou, em outras palavras, pressupõe que você leu algo como a introdução de Receitas Numéricas ou Análise Numérica da Burden & Faires.
Mctylr

0

Depende de quão precisa você deseja que a comparação seja. Se você deseja comparar exatamente o mesmo número, basta ir com ==. (Você quase nunca deseja fazer isso, a menos que realmente deseje exatamente o mesmo número.) Em qualquer plataforma decente, você também pode fazer o seguinte:

diff= a - b; return fabs(diff)<EPSILON;

como fabstende a ser bem rápido. Com bastante rapidez, quero dizer que é basicamente um AND bit a bit, então é melhor que seja rápido.

E truques inteiros para comparar duplas e flutuantes são bons, mas tendem a dificultar o manuseio eficaz dos vários pipelines da CPU. E definitivamente não é mais rápido em certas arquiteturas em ordem atualmente devido ao uso da pilha como uma área de armazenamento temporário para valores que estão sendo usados ​​com freqüência. (Carregar-bater-armazenar para aqueles que se importam.)


0

Em termos da escala de quantidades:

Se epsiloné a pequena fração da magnitude da quantidade (ou seja, valor relativo) em certo sentido físico e / Ae Btipos é comparável no mesmo sentido, do que eu penso, que o seguinte é bastante correto:

#include <limits>
#include <iomanip>
#include <iostream>

#include <cmath>
#include <cstdlib>
#include <cassert>

template< typename A, typename B >
inline
bool close_enough(A const & a, B const & b,
                  typename std::common_type< A, B >::type const & epsilon)
{
    using std::isless;
    assert(isless(0, epsilon)); // epsilon is a part of the whole quantity
    assert(isless(epsilon, 1));
    using std::abs;
    auto const delta = abs(a - b);
    auto const x = abs(a);
    auto const y = abs(b);
    // comparable generally and |a - b| < eps * (|a| + |b|) / 2
    return isless(epsilon * y, x) && isless(epsilon * x, y) && isless((delta + delta) / (x + y), epsilon);
}

int main()
{
    std::cout << std::boolalpha << close_enough(0.9, 1.0, 0.1) << std::endl;
    std::cout << std::boolalpha << close_enough(1.0, 1.1, 0.1) << std::endl;
    std::cout << std::boolalpha << close_enough(1.1,    1.2,    0.01) << std::endl;
    std::cout << std::boolalpha << close_enough(1.0001, 1.0002, 0.01) << std::endl;
    std::cout << std::boolalpha << close_enough(1.0, 0.01, 0.1) << std::endl;
    return EXIT_SUCCESS;
}

0

Eu uso este código:

bool AlmostEqual(double v1, double v2)
    {
        return (std::fabs(v1 - v2) < std::fabs(std::min(v1, v2)) * std::numeric_limits<double>::epsilon());
    }

2
Não epsiloné para isso que serve.
Sneftel 26/05/19

1
Por que não? Você pode explicar isso?
debuti

2
@debuti epsiloné apenas a distância entre 1 e o próximo número representável após 1. Na melhor das hipóteses, esse código está apenas tentando verificar se os dois números são exatamente iguais um ao outro, mas, como os não-poderes de 2 estão sendo multiplicados por epsilon, nem está fazendo isso corretamente.
Sneftel 13/06/19

2
Ah, e std::fabs(std::min(v1, v2))está incorreto - para entradas negativas, escolhe aquela com maior magnitude.
30919 Sneftel

0

Eu escrevo isso para java, mas talvez você ache útil. Ele usa longos em vez de duplos, mas cuida de NaNs, subnormais, etc.

public static boolean equal(double a, double b) {
    final long fm = 0xFFFFFFFFFFFFFL;       // fraction mask
    final long sm = 0x8000000000000000L;    // sign mask
    final long cm = 0x8000000000000L;       // most significant decimal bit mask
    long c = Double.doubleToLongBits(a), d = Double.doubleToLongBits(b);        
    int ea = (int) (c >> 52 & 2047), eb = (int) (d >> 52 & 2047);
    if (ea == 2047 && (c & fm) != 0 || eb == 2047 && (d & fm) != 0) return false;   // NaN 
    if (c == d) return true;                            // identical - fast check
    if (ea == 0 && eb == 0) return true;                // ±0 or subnormals
    if ((c & sm) != (d & sm)) return false;             // different signs
    if (abs(ea - eb) > 1) return false;                 // b > 2*a or a > 2*b
    d <<= 12; c <<= 12;
    if (ea < eb) c = c >> 1 | sm;
    else if (ea > eb) d = d >> 1 | sm;
    c -= d;
    return c < 65536 && c > -65536;     // don't use abs(), because:
    // There is a posibility c=0x8000000000000000 which cannot be converted to positive
}
public static boolean zero(double a) { return (Double.doubleToLongBits(a) >> 52 & 2047) < 3; }

Lembre-se de que, após várias operações de ponto flutuante, o número pode ser muito diferente do que esperamos. Não há código para corrigir isso.


0

Que tal agora?

template<typename T>
bool FloatingPointEqual( T a, T b ) { return !(a < b) && !(b < a); }

Eu já vi várias abordagens - mas nunca vi isso, por isso estou curioso para ouvir comentários também!


Isso não funciona para 1.99999999 e 1.99999998
Mehdi

@ Mehdi Acabei de tentar com repl.it/repls/SvelteSimpleNumerator#main.cpp e parece se comportar conforme o esperado - mas talvez você tenha uma implementação de compilador específica para referência que não faz isso?
derke

-1
/// testing whether two doubles are almost equal. We consider two doubles
/// equal if the difference is within the range [0, epsilon).
///
/// epsilon: a positive number (supposed to be small)
///
/// if either x or y is 0, then we are comparing the absolute difference to
/// epsilon.
/// if both x and y are non-zero, then we are comparing the relative difference
/// to epsilon.
bool almost_equal(double x, double y, double epsilon)
{
    double diff = x - y;
    if (x != 0 && y != 0){
        diff = diff/y; 
    }

    if (diff < epsilon && -1.0*diff < epsilon){
        return true;
    }
    return false;
}

Usei essa função para meu pequeno projeto e ele funciona, mas observe o seguinte:

Erro de precisão dupla pode criar uma surpresa para você. Digamos que epsilon = 1.0e-6, então 1.0 e 1.000001 NÃO devam ser considerados iguais de acordo com o código acima, mas na minha máquina a função os considera iguais, isso ocorre porque 1.000001 não pode ser traduzido com precisão para um formato binário, provavelmente é 1.0000009xxx. Eu o testo com 1.0 e 1.0000011 e desta vez obtenho o resultado esperado.


-1

Esta é outra solução com o lambda:

#include <cmath>
#include <limits>

auto Compare = [](float a, float b, float epsilon = std::numeric_limits<float>::epsilon()){ return (std::fabs(a - b) <= epsilon); };

É exatamente o mesmo que muitas das outras respostas, exceto que é um lambda e não tem explicação, portanto, isso não agrega muito valor como resposta.
stijn 29/04

-2

Meu caminho pode não estar correto, mas útil

Converta ambas as flutuações em seqüências de caracteres e faça comparações de seqüências

bool IsFlaotEqual(float a, float b, int decimal)
{
    TCHAR form[50] = _T("");
    _stprintf(form, _T("%%.%df"), decimal);


    TCHAR a1[30] = _T(""), a2[30] = _T("");
    _stprintf(a1, form, a);
    _stprintf(a2, form, b);

    if( _tcscmp(a1, a2) == 0 )
        return true;

    return false;

}

a sobreposição do operador também pode ser feita


+1: ei, eu não vou fazer programação de jogos com isso, mas a idéia de carros alegóricos surgiu várias vezes no blog de Bruce Dawson (tratado?: D) sobre o assunto, e se você estiver preso uma sala e alguém coloca uma arma na sua cabeça e diz "ei, você tem que comparar dois carros alegóricos com valores significativos de X, você tem 5 minutos, VAI!" este é provavelmente um a considerar. ;)
shelleybutterfly

@shelleybutterfly Então, novamente, a questão era a maneira mais eficiente de comparar dois números de ponto flutuante.
Tommy Andersen

@ TommyA lol talvez, mas eu aposto que o round-trip foi diminuído por razões não relacionadas à eficiência. Embora minha intuição seja a de que seria bastante ineficiente em comparação com a matemática HW fp, mas também diz que os algoritmos no software fp dificilmente terão uma grande diferença pelo menos. Aguardo ansiosamente a análise que você fez, mostrando que as preocupações de eficiência nesse caso são significativas. Além disso, às vezes menos do que o ideal ainda pode ser uma resposta valiosa e, como foi rebaixada - apesar de ser uma técnica válida que foi mencionada no blog de Dawson sobre o assunto, achei que merecia uma votação positiva.
Shelleybutterfly

-2

Você não pode comparar dois doublecom um fixo EPSILON. Dependendo do valor de double, EPSILONvaria.

Uma comparação dupla melhor seria:

bool same(double a, double b)
{
  return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b
    && std::nextafter(a, std::numeric_limits<double>::max()) >= b;
}

-2

De uma maneira mais genérica:

template <typename T>
bool compareNumber(const T& a, const T& b) {
    return std::abs(a - b) < std::numeric_limits<T>::epsilon();
}

4
Este método tem muitos pontos fracos, como se os números ae bjá forem menores do epsilon()que a diferença ainda puderem ser significativos. Por outro lado, se os números forem muito grandes, mesmo alguns bits de erro farão com que a comparação falhe, mesmo que você queira que os números sejam considerados iguais. Essa resposta é exatamente o tipo de algoritmo de comparação "genérico" que você deseja evitar.
precisa saber é o seguinte

-3

Por que não executar o XOR bit a bit? Dois números de ponto flutuante são iguais se os bits correspondentes forem iguais. Acho que a decisão de colocar os bits do expoente antes da mantissa foi tomada para acelerar a comparação de dois carros alegóricos. Eu acho que muitas respostas aqui estão perdendo o objetivo da comparação epsilon. O valor de epsilon depende apenas de quais números de precisão de ponto flutuante são comparados. Por exemplo, depois de fazer uma aritmética com flutuadores, você obtém dois números: 2.5642943554342 e 2.5642943554345. Eles não são iguais, mas, para a solução, apenas 3 dígitos decimais são importantes; portanto, são iguais: 2.564 e 2.564. Nesse caso, você escolhe epsilon igual a 0,001. A comparação épsilon também é possível com o XOR bit a bit. Corrija-me se eu estiver enganado.


Não adicione a mesma resposta a várias perguntas. Responda à melhor e marque o restante como duplicado. Veja meta.stackexchange.com/questions/104227/…
Bhargav Rao

Eu não acho que a "comparação epsilon" seja possível usando apenas o ExOr (e uma ou duas comparações), mesmo restrito a representações normalizadas no mesmo formato.
greybeard
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.