¿Cuál es la forma más efectiva de flotación y doble comparación?


524

¿Cuál sería la forma más eficiente de comparar dos double o dos floatvalores?

Simplemente hacer esto no es correcto:

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

Pero algo como:

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

Parece que el procesamiento de residuos.

¿Alguien conoce un comparador de flotador más inteligente?


2
> ¿sería más eficiente agregar ... al comienzo de la función? <invoke Knuth>La optimización prematura es la fuente de todos los males. </invoke Knuth>Simplemente vaya con abs (ab) <EPS como se señaló anteriormente, es claro y fácil de entender.
Andrew Coleson


2
Lo único no óptimo sobre la implementación del póster original es que contiene una rama adicional en &&. La respuesta de OJ es óptima. fabs es un intrínseco que es una sola instrucción en x87, y supongo que en casi cualquier otra cosa también. ¡Acepte la respuesta de OJ ya!
3yE

3
Si puede, suelte el punto flotante y use puntos fijos. Ejemplo, use {punto fijo} milímetros en lugar de {punto flotante} metros.
Thomas Matthews

33
"Simplemente hacer esto no es correcto" - Esto es una simple basura, por supuesto, usarlo ==puede ser perfectamente correcto, pero esto depende completamente del contexto no dado en la pregunta. Hasta que se conozca ese contexto, ==sigue siendo la "forma más eficiente" .
Christian Rau

Respuestas:


459

Tenga mucho cuidado con cualquiera de las otras sugerencias. Todo depende del contexto.

He pasado mucho tiempo rastreando errores en un sistema que suponía que a==b|a-b|<epsilon. Los problemas subyacentes fueron:

  1. La presunción implícita en un algoritmo que si a==by b==cluego a==c.

  2. Usando el mismo épsilon para líneas medidas en pulgadas y líneas medidas en milésimas de pulgada (.001 pulgadas). Eso es a==bpero 1000a!=1000b. (Esta es la razón por la cual AlmostEqual2sComplement solicita el epsilon o max ULPS).

  3. ¡El uso del mismo épsilon tanto para el coseno de los ángulos como para la longitud de las líneas!

  4. Usando tal función de comparación para ordenar elementos en una colección. (En este caso, el uso del operador C ++ incorporado == para dobles produjo resultados correctos).

Como dije: todo depende del contexto y del tamaño esperado de ay b.

Por cierto, std::numeric_limits<double>::epsilon()es la "máquina épsilon". Es la diferencia entre 1.0 y el siguiente valor representable por un doble. Supongo que podría usarse en la función de comparación, pero solo si los valores esperados son inferiores a 1. (Esto es en respuesta a la respuesta de @ cdv ...)

Además, si básicamente tiene intaritmética en doubles(aquí usamos dobles para mantener valores int en ciertos casos) su aritmética será correcta. Por ejemplo, 4.0 / 2.0 será lo mismo que 1.0 + 1.0. Esto es siempre que no haga cosas que generen fracciones (4.0 / 3.0) o que no salgan del tamaño de un int.


10
+1 por señalar lo obvio (que a menudo se ignora). Para un método genérico, puede hacer que el épsilon sea relativo fabs(a)+fabs(b)pero con compensación para NaN, suma 0 y desbordamiento, esto se vuelve bastante complejo.
peterchen

44
Debe haber algo que no entiendo. El típico float/ doublees MANTISSA x 2 ^ EXP . epsilondependerá del exponente. Por ejemplo, si la mantisa es de 24 bits y el exponente está firmado con 8 bits, entonces 1/(2^24)*2^127o ~2^103es epsilonpara algunos valores; o esto se refiere a un mínimo de épsilon ?
ruido sin arte

3
Espera un segundo. ¿Es lo que dije lo que querías decir? Estás diciendo por qué |a-b|<epsilon, no es correcto. Agregue este enlace a su respuesta; si está de acuerdo cygnus-software.com/papers/comparingfloats/comparingfloats.htm y puedo eliminar mis comentarios tontos.
ruido sin arte

3
Este es un comentario muy largo, no una respuesta en sí mismo. ¿Hay una (conjunto de) respuestas canónicas para todos los contextos?
Merlyn Morgan-Graham

2
El viejo enlace parece estar obsoleto, la nueva página está aquí randomascii.wordpress.com/2012/02/25/…
Marson Mao

174

La comparación con un valor épsilon es lo que hace la mayoría de las personas (incluso en la programación de juegos).

Sin embargo, debe cambiar su implementación un poco:

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

Editar: Christer ha agregado una gran cantidad de información excelente sobre este tema en una publicación de blog reciente . Disfrutar.


@OJ: ¿hay algún problema con el primer ejemplo de código? Pensé que el único problema estaba en una situación como esta: float a = 3.4; if(a == 3.4){...}es decir, cuando se compara un punto flotante almacenado con un literal | En este caso, ambos números se almacenan, por lo que tendrán la misma representación, si es igual, entonces, ¿cuál es el daño al hacer a == b?
Lazer

11
@DonReba: solo si EPSILONse define como DBL_EPSILON. Normalmente será un valor específico elegido dependiendo de la precisión requerida de la comparación.
Nemo157

77
EPSILONla comparación no funciona cuando los flotadores son grandes, ya que la diferencia entre flotadores consecutivos también se hace grande. Ver este artículo .
kevintodisco

22
No es de extrañar que haya peleas Z en algunos juegos cuando las texturas / objetos parpadean muy lejos, como en Battlefield 4. Comparar la diferencia con EPSILONes bastante inútil. Debe comparar con un umbral que tenga sentido para las unidades disponibles. Además, use std::absya que está sobrecargado para diferentes tipos de punto flotante.
Maxim Egorushkin

11
Voté en contra porque el código de ejemplo muestra que la mayoría de los programadores repiten el error típico de whis. El punto flotante siempre se trata de errores relativos, ya que es un punto flotante (no un punto fijo). Por lo tanto, nunca funcionará correctamente con un error fijo (epsilon).
user2261015

115

Descubrí que Google C ++ Testing Framework contiene una buena implementación multiplataforma basada en plantillas de AlmostEqual2sComplement que funciona tanto en dobles como en flotantes. Dado que se publica bajo la licencia BSD, usarlo en su propio código no debería ser un problema, siempre que conserve la licencia. Extraje el siguiente código 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 y agregó la licencia en la parte superior.

Asegúrese de #definir GTEST_OS_WINDOWS a algún valor (o cambiar el código donde está acostumbrado a algo que se ajuste a su base de código; después de todo, tiene licencia BSD).

Ejemplo de uso:

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

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

Aquí está el 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_;
};

EDITAR: Esta publicación tiene 4 años. Probablemente todavía sea válido, y el código es bueno, pero algunas personas encontraron mejoras. Lo mejor es obtener la última versión AlmostEqualscorrecta del código fuente de Google Test, y no la que pegué aquí.


3
+1: Estoy de acuerdo en que este es correcto. Sin embargo, no explica por qué. Vea aquí: cygnus-software.com/papers/comparingfloats/comparingfloats.htm . Leí esta publicación de blog después de escribir mi comentario sobre la puntuación más alta aquí; Creo que dice lo mismo y proporciona la solución racional que se implementa anteriormente. Debido a que hay mucho código, la gente perderá la respuesta.
ruido sin arte

Hay un par de cosas desagradables que pueden suceder cuando se producen conversiones implícitas, como por ejemplo FloatPoint <double> fp (0.03f). Hice un par de modificaciones a esto para ayudar a prevenir eso. plantilla <typename U> explícito FloatingPoint (const U & x) {if (typeid (U) .name ()! = typeid (RawType) .name ()) {std :: cerr << "Estás haciendo una conversión implícita con FloatingPoint, Don't "<< std :: endl; afirmar (typeid (U) .name () == typeid (RawType) .name ()); } u_.value_ = x; }
JeffCharter

2
¡Buen descubrimiento! Sin embargo, creo que sería mejor contribuir con ellos a Google Test, de donde se robó este código. Actualizaré la publicación para reflejar que probablemente haya una versión más nueva. Si los chicos de Google actúan con picazón, ¿podría ponerlo, por ejemplo, en una esencia de GitHub? Voy a vincular a eso también, entonces.
skrebbel

3
Para obtener el fragmento de código más reciente, consulte aquí y aquí .
Jaege

1
He extraído las líneas necesarias a un archivo Gist. Cualquiera puede llegar desde aquí .
Yusuf Tarık Günaydın

111

La comparación de números de coma flotante depende del contexto. Dado que incluso cambiar el orden de las operaciones puede producir resultados diferentes, es importante saber qué tan "igual" quiere que sean los números.

La comparación de números de coma flotante por Bruce Dawson es un buen lugar para comenzar cuando se busca la comparación de coma flotante.

Las siguientes definiciones son de El arte de la programación de computadoras 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);
}

Por supuesto, elegir épsilon depende del contexto y determina qué tan igual quiere que sean los números.

Otro método para comparar números de coma flotante es mirar el ULP (unidades en último lugar) de los números. Si bien no se trata específicamente de comparaciones, el documento Lo que todo informático debe saber sobre los números de coma flotante es un buen recurso para comprender cómo funciona la coma flotante y cuáles son las trampas, incluido lo que es ULP.


1
¡Gracias por publicar cómo determinar qué número es más pequeño / más grande!
Tomate

1
fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);me salvó la vida LOL Tenga en cuenta que esta versión (no he comprobado si se aplica a las demás también) también considera el cambio que podría ocurrir en la parte integral del número de coma flotante (ejemplo: 2147352577.9999997616 == 2147352576.0000000000donde puede ver claramente que hay casi una diferencia de 2entre los dos números) que es bastante bueno! Esto sucede cuando el error de redondeo acumulado desborda la parte decimal del número.
rbaleksandar

Muy bonito y útil artículo de Bruce Dawson, ¡gracias!
BobMorane

2
Dado que esta pregunta está etiquetada como C ++, sus cheques serían más fáciles de leer si se escriben como std::max(std::abs(a), std::abs(b))(o con std::min()); std::absen C ++ está sobrecargado con tipos flotantes y dobles, por lo que funciona bien ( fabsaunque siempre puede mantenerse legible).
Razakhel el

1
Resulta que el problema estaba en mi código, la diferencia entre el valor esperado original y la cadena analizada.
mwpowellhtx

47

Para un enfoque más profundo, lea Comparar números de punto flotante . Aquí está el fragmento de código de ese enlace:

// 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
¿Cuál es el valor sugerido de maxUlps?
unj2

66
¿Violará " *(int*)&A;" la estricta regla de alias?
osgx

3
Según gtest (búsqueda de ULP), 4 es un número aceptable.
Mayo Oakes

44
Y aquí hay un par de actualizaciones del artículo de Bruce Dawson (uno de los cuales está vinculado en la introducción del artículo): randomascii.wordpress.com/2012/02/25/… y randomascii.wordpress.com/2012/06/26/…
Michael Burr

3
Me llevó un tiempo descubrir qué era ULP: Unidades en el último lugar
JeffCharter

27

Al darse cuenta de que este es un hilo viejo, pero este artículo es uno de los más sencillos que he encontrado al comparar números de coma flotante y si desea explorar más, también tiene referencias más detalladas y el sitio principal cubre una gama completa de problemas tratar con números de coma flotante La guía de coma flotante: comparación .

Podemos encontrar un artículo algo más práctico en Tolerancias de punto flotante revisado y señala que hay una prueba de tolerancia absoluta , que se reduce a esto en C ++:

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

y prueba de tolerancia 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 ;
}

El artículo señala que la prueba absoluta falla cuando xy yson grandes y falla en el caso relativo cuando son pequeños. Suponiendo que la tolerancia absoluta y relativa es la misma, una prueba combinada se vería así:

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

La forma portátil de obtener epsilon en C ++ es

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

Entonces la función de comparación se convierte en

#include <cmath>
#include <limits>

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

34
Probablemente querrás un múltiplo de ese épsilon.
user7116

11
¿No puedes usar std :: abs? AFAIK, std :: abs también está sobrecargado para dobles. Por favor, avísame si me equivoco.
kolistivra

3
@kolistivra, estás equivocado. La 'f' en 'fabs' no significa el tipo float. Probablemente esté pensando en las funciones C fabsf () y fabsl ().
jcoffland

99
En realidad, por los motivos descritos en el artículo de Bruce, épsilon cambia a medida que el valor de coma flotante aumenta. Vea la parte donde dice "Para números mayores que 2.0, la brecha entre flotadores aumenta y si compara flotadores usando FLT_EPSILON, entonces solo está haciendo una verificación de igualdad más costosa y menos obvia".
bobobobo

55
Sé que esto es antiguo, pero std :: abs está sobrecargado para los tipos de coma flotante en cmath.
mholzmann

18

Terminé pasando bastante tiempo revisando material en este gran hilo. Dudo que todos quieran pasar tanto tiempo, así que destacaría el resumen de lo que aprendí y la solución que implementé.

Sumario rápido

  1. ¿Es 1e-8 aproximadamente igual a 1e-16? Si está buscando datos de sensores ruidosos, entonces probablemente sí, pero si está haciendo simulación molecular, ¡entonces puede que no! En pocas palabras: siempre debes pensar en en el valor tolerancia en el contexto de una llamada de función específica y no solo en una constante genérica codificada en toda la aplicación.
  2. Para las funciones generales de la biblioteca, todavía es bueno tener parámetros con tolerancia predeterminada . Una opción típica es numeric_limits::epsilon()la misma que FLT_EPSILON en float.h. Sin embargo, esto es problemático porque epsilon para comparar valores como 1.0 no es lo mismo que epsilon para valores como 1E9. El FLT_EPSILON se define para 1.0.
  3. La implementación obvia para verificar si el número está dentro de la tolerancia es fabs(a-b) <= epsilonSin embargo, la esto no funciona porque el epsilon predeterminado está definido para 1.0. Necesitamos escalar epsilon hacia arriba o hacia abajo en términos de ay b.
  4. Hay dos soluciones a este problema: puede configurar epsilon proporcional a max(a,b) o puede obtener los siguientes números representables alrededor de ay luego ver si b cae dentro de ese rango. El primero se llama método "relativo" y luego se llama método ULP.
  5. Ambos métodos realmente fallan de todos modos cuando se compara con 0. En este caso, la aplicación debe proporcionar la tolerancia correcta.

Implementación de funciones de utilidad (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;
}

isDefinitelyLessThancontroles diff < tolerance, lo que significa que ayb son casi iguales (y, por lo tanto, a no es definitivamente menor que b). ¿No tiene más sentido comprobar la diferencia de tolerancia en ambos casos? O quizás agregue un orEqualToargumento que controle si la verificación de igualdad aproximada debe devolver verdadero o no.
Matt Chambers

14

El código que escribiste tiene errores:

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

El código correcto sería:

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

(... y sí, esto es diferente)

Me pregunto si los fabs no te harían perder la evaluación perezosa en algún caso. Yo diría que depende del compilador. Es posible que desee probar ambos. Si son equivalentes en promedio, tome la implementación con fabs.

Si tiene alguna información sobre cuál de los dos flotantes es más probable que sea más grande que otro, puede jugar en el orden de la comparación para aprovechar mejor la evaluación perezosa.

Finalmente, podría obtener mejores resultados al incluir esta función. Sin embargo, no es probable que mejore mucho ...

Editar: DO, gracias por corregir su código. Borré mi comentario en consecuencia


13

`return fabs (a - b) <EPSILON;

Esto está bien si:

  • el orden de magnitud de sus entradas no cambia mucho
  • un número muy pequeño de signos opuestos puede ser tratado como igual

Pero de lo contrario te llevará a problemas. Los números de doble precisión tienen una resolución de aproximadamente 16 decimales. Si los dos números que está comparando son de mayor magnitud que EPSILON * 1.0E16, entonces también podría estar diciendo:

return a==b;

Examinaré un enfoque diferente que asume que debe preocuparse por el primer problema y supondrá que el segundo está bien para su aplicación. Una solución sería 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;
}

Esto es costoso computacionalmente, pero a veces es lo que se requiere. Esto es lo que tenemos que hacer en mi empresa porque tratamos con una biblioteca de ingeniería y las entradas pueden variar en unas pocas docenas de órdenes de magnitud.

De todos modos, el punto es este (y se aplica a prácticamente todos los problemas de programación): evalúe cuáles son sus necesidades, luego encuentre una solución para satisfacer sus necesidades: no asuma que la respuesta fácil abordará sus necesidades. Si después de su evaluación encuentra que eso fabs(a-b) < EPSILONserá suficiente, perfecto, ¡úselo! Pero tenga en cuenta sus deficiencias y otras posibles soluciones también.


3
Aparte de los errores tipográficos (s / - /, / coma que falta en fmax ()), esta implementación tiene un error para los números cercanos a cero que están dentro de EPSILON, pero aún no del todo. Por ejemplo, AreSame (1.0E-10, 1.0E-9) informa falso porque el error relativo es enorme. Tienes la oportunidad de ser el héroe en tu empresa.
brlcad

1
@brlcad No obtuvo el punto de coma flotante . 1.0E-10 y 1.0E-9 difieren en la magnitud de 10. Por lo tanto, es cierto que no son lo mismo. el punto flotante siempre se trata de errores relativos . Si tiene un sistema que considera que 1.0E-10 y 1.0E-9 son casi iguales, ya que ambos están "bastante cerca de cero" (lo que suena razonable para los humanos pero no es matemáticamente nada), entonces EPSILON debe ajustarse según corresponda para tal sistema.
user2261015

8

Como otros han señalado, usar un épsilon de exponente fijo (como 0.0000001) será inútil para valores alejados del valor de épsilon. Por ejemplo, si sus dos valores son 10000.000977 y 10000, entonces NO hay valores de punto flotante de 32 bits entre estos dos números: 10000 y 10000.000977 están lo más cerca posible que pueda ser sin ser idéntico bit por bit. Aquí, un épsilon de menos de 0,0009 no tiene sentido; también podrías usar el operador de igualdad directa.

Del mismo modo, a medida que los dos valores se aproximan a epsilon en tamaño, el error relativo crece al 100%.

Por lo tanto, tratar de mezclar un número de punto fijo como 0.00001 con valores de punto flotante (donde el exponente es arbitrario) es un ejercicio sin sentido. Esto solo funcionará si puede estar seguro de que los valores de operando se encuentran dentro de un dominio estrecho (es decir, cerca de algún exponente específico), y si selecciona correctamente un valor de épsilon para esa prueba específica. Si saca un número del aire ("¡Hey! 0.00001 es pequeño, ¡eso debe ser bueno!"), Está condenado a errores numéricos. He pasado mucho tiempo depurando códigos numéricos incorrectos donde algunos idiotas pobres arrojan valores aleatorios de epsilon para hacer que otro caso de prueba funcione.

Si hace programación numérica de cualquier tipo y cree que necesita alcanzar épsilons de punto fijo, LEA EL ARTÍCULO DE BRUCE SOBRE COMPARAR NÚMEROS DE PUNTO FLOTANTE .

Comparación de números de coma flotante


5

Qt implementa dos funciones, quizás puedas aprender de ellas:

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)));
}

Y es posible que necesite las siguientes funciones, ya que

Tenga en cuenta que comparar valores donde p1 o p2 es 0.0 no funcionará, ni comparar valores donde uno de los valores es NaN o infinito. Si uno de los valores es siempre 0.0, use qFuzzyIsNull en su lugar. Si es probable que uno de los valores sea 0.0, una solución es agregar 1.0 a ambos valores.

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

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

3

La comparación de propósito general de los números de coma flotante generalmente no tiene sentido. Cómo comparar realmente depende de un problema en cuestión. En muchos problemas, los números están suficientemente discretos para permitir compararlos dentro de una tolerancia dada. Desafortunadamente, hay tantos problemas, donde tal truco realmente no funciona. Por ejemplo, considere trabajar con una función Heaviside (paso) de un número en cuestión (me vienen a la mente las opciones de acciones digitales) cuando sus observaciones están muy cerca de la barrera. Realizar una comparación basada en la tolerancia no serviría de mucho, ya que efectivamente cambiaría el problema de la barrera original a dos nuevas. Nuevamente, no existe una solución de propósito general para tales problemas y la solución particular podría requerir ir tan lejos como cambiar el método numérico para lograr la estabilidad.


3

Desafortunadamente, incluso su código "derrochador" es incorrecto. EPSILON es el valor más pequeño que se podría agregar a 1.0 y cambiar su valor. El valor 1.0 es muy importante: los números más grandes no cambian cuando se agregan a EPSILON. Ahora, puede escalar este valor a los números que está comparando para saber si son diferentes o no. La expresión correcta para comparar dos dobles es:

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

Esto es mínimo. Sin embargo, en general, desearía tener en cuenta el ruido en sus cálculos e ignorar algunos de los bits menos significativos, por lo que se vería una comparación más realista:

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

Si el rendimiento de comparación es muy importante para usted y conoce el rango de sus valores, entonces debería usar números de punto fijo.


2
“EPSILON es el valor más pequeño que se puede agregar a 1.0 y cambiar su valor”: en realidad, este honor es para el sucesor de 0.5 * EPSILON (en el modo predeterminado de redondeo al más cercano). blog.frama-c.com/index.php?post/2013/05/09/FLT_EPSILON
Pascal Cuoq

¿Por qué crees que EPSILONen la pregunta es DBL_EPSILONo FLT_EPSILON? El problema está en su propia imaginación, donde sustituyó DBL_EPSILON(que de hecho sería la elección incorrecta) en un código que no lo usó.
Ben Voigt

@BenVoigt, tienes razón, era algo que tenía en mente en ese momento, e interpreté la pregunta desde esa perspectiva.
Don Reba

2

Mi clase basada en respuestas publicadas previamente. Muy similar al código de Google, pero utilizo un sesgo que empuja todos los valores de NaN por encima de 0xFF000000. Eso permite una verificación más rápida de NaN.

Este código está destinado a demostrar el concepto, no ser una solución general. El código de Google ya muestra cómo calcular todos los valores específicos de la plataforma y no quería duplicar todo eso. He realizado pruebas limitadas en este 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

Aquí está la prueba de que el uso std::numeric_limits::epsilon()no es la respuesta: falla para valores mayores que uno:

Prueba de mi comentario anterior:

#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);
}

Correr produce esta salida:

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

Tenga en cuenta que en el segundo caso (uno y solo mayor que uno), los dos valores de entrada están lo más cerca posible, y aún se comparan como no cercanos. Por lo tanto, para valores superiores a 1.0, también podría usar una prueba de igualdad. Los épsilons fijos no lo salvarán al comparar valores de punto flotante.


Creo que, return *(reinterpret_cast<double*>(&x));aunque generalmente funciona, es un comportamiento indefinido.
Jaap Versteegh

Punto justo, aunque este código es ilustrativo, es suficiente para demostrar el problema numeric_limits<>::epsilony el punto de piso IEEE 754.
Steve Hollasch

También es un punto justo, pero no es aconsejable publicar en el desbordamiento de la pila esperando ese tipo de información. El código se copiará a ciegas, lo que hace cada vez más difícil erradicar este patrón muy común, junto con el truco sindical, que debería evitarse como deberían hacerlo todos los UD.
Jaap Versteegh

1

Encontré otra implementación interesante en: 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

Sería muy cuidadoso con cualquiera de estas respuestas que impliquen la resta de punto flotante (por ejemplo, fabs (ab) <epsilon). Primero, los números de coma flotante se vuelven más dispersos a mayores magnitudes y a magnitudes suficientemente altas donde el espacio es mayor que épsilon, también podría estar haciendo a == b. En segundo lugar, restar dos números de punto flotante muy cercanos (ya que estos tienden a ser, dado que está buscando una igualdad cercana) es exactamente cómo se obtiene la cancelación catastrófica .

Si bien no es portátil, creo que la respuesta de grom hace el mejor trabajo para evitar estos problemas.


1
+1 para buena información. Sin embargo, no veo cómo podría estropear la comparación de igualdad al aumentar el error relativo; En mi humilde opinión, el error se vuelve significativo solo en el resultado de la resta, sin embargo, su orden de magnitud en relación con el de los dos operandos que se restan aún debe ser lo suficientemente confiable como para juzgar la igualdad. A menos que la resolución deba ser mayor en general, pero en ese caso la única solución es pasar a una representación de coma flotante con bits más significativos en la mantisa.
sehe

Restar dos números casi iguales NO conduce a una cancelación catastrófica; de hecho, no introduce ningún error en absoluto (qv Teorema de Sterbenz). Cancelación catastrófica ocurre antes, durante el cálculo de ay bellos mismos. No hay absolutamente ningún problema con el uso de resta en coma flotante como parte de una comparación fuzzy (aunque como han dicho otros, un valor de epsilon absoluta puede o puede no ser apropiado para un caso de uso dado.)
Sneftel

0

En realidad, hay casos en el software numérico en los que desea verificar si dos números de coma flotante son exactamente iguales. Publiqué esto en una pregunta similar

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

Por lo tanto, no puede decir que "CompareDoubles1" está mal en general.


En realidad, es una referencia muy sólida a una buena respuesta, aunque es muy especializado limitar a cualquier persona sin conocimientos científicos de computación o análisis numérico (es decir, LAPACK, BLAS) para que no comprenda la integridad. O, en otras palabras, supone que ha leído algo como la introducción de Recetas numéricas o el Análisis numérico de Burden & Faires.
mctylr

0

Depende de cuán preciso desee que sea la comparación. Si desea comparar exactamente el mismo número, simplemente vaya con ==. (Casi nunca quieres hacer esto a menos que realmente quieras exactamente el mismo número). En cualquier plataforma decente también puedes hacer lo siguiente:

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

ya que fabstiende a ser bastante rápido. Por bastante rápido me refiero a que es básicamente un Y bit a bit, así que es mejor que sea rápido.

Y los trucos de números enteros para comparar dobles y flotantes son buenos, pero tienden a dificultar el manejo efectivo de las diferentes tuberías de CPU. Y definitivamente no es más rápido en ciertas arquitecturas en orden en estos días debido al uso de la pila como un área de almacenamiento temporal para valores que se usan con frecuencia. (Load-hit-store para quienes se preocupan).


0

En términos de la escala de cantidades:

Si epsilonla pequeña fracción de la magnitud de la cantidad (es decir, el valor relativo) en cierto sentido físico Ay Btipos es comparable en el mismo sentido, creo que lo siguiente es bastante correcto:

#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

Yo 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
Eso no epsilones para lo que sirve.
Sneftel

1
Por qué no? ¿Puedes explicarlo?
debuti

2
@debuti epsilones simplemente la distancia entre 1 y el siguiente número representable después de 1. En el mejor de los casos, ese código solo está tratando de verificar si los dos números son exactamente iguales entre sí, pero debido a que los no poderes de 2 se multiplican por epsilon, Ni siquiera está haciendo eso correctamente.
Sneftel

2
Ah, y std::fabs(std::min(v1, v2))es incorrecto: para las entradas negativas, elige la que tiene la mayor magnitud.
Sneftel

0

Escribo esto para Java, pero tal vez lo encuentres útil. Utiliza largos en lugar de dobles, pero se ocupa de NaNs, subnormales, 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; }

Tenga en cuenta que después de varias operaciones de punto flotante, el número puede ser muy diferente de lo que esperamos. No hay código para arreglar eso.


0

¿Qué tal esto?

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

He visto varios enfoques, pero nunca he visto esto, ¡así que tengo curiosidad por escuchar cualquier comentario también!


Esto no funciona para 1.99999999 y 1.99999998
Mehdi

@Mehdi que acabo de probar con repl.it/repls/SvelteSimpleNumerator#main.cpp y parece que se comporta como se esperaba, pero ¿tal vez tiene una implementación de compilador particular para hacer referencia que no hace esto?
derke hace

-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;
}

Utilicé esta función para mi pequeño proyecto y funciona, pero tenga en cuenta lo siguiente:

El error de doble precisión puede crear una sorpresa para ti. Digamos que épsilon = 1.0e-6, entonces 1.0 y 1.000001 NO deben considerarse iguales de acuerdo con el código anterior, pero en mi máquina la función los considera iguales, esto se debe a que 1.000001 no se puede traducir con precisión a un formato binario, probablemente sea 1.0000009xxx. Lo pruebo con 1.0 y 1.0000011 y esta vez obtengo el resultado esperado.


-1

Esta es otra solución con lambda:

#include <cmath>
#include <limits>

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

Esto es exactamente lo mismo que muchas de las otras respuestas, excepto que es una lambda y no tiene explicación, por lo que esto no agrega mucho valor como respuesta.
stijn

-2

Mi camino puede no ser correcto pero útil

Convierta ambos flotantes en cadenas y luego compare cadenas

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;

}

la superposición del operador también se puede hacer


+1: oye, no voy a hacer programación de juegos con esto, pero la idea de flotadores de ida y vuelta surgió varias veces en el blog de Bruce Dawson (¿tratado?: D) sobre el tema, y ​​si estás atrapado en una habitación y alguien te pone una pistola en la cabeza y te dice "oye, tienes que comparar dos flotadores con X cifras significativas, tienes 5 minutos, ¡IR!" este es probablemente uno a tener en cuenta. ;)
shelleybutterfly

@shelleybutterfly De nuevo, la pregunta era la forma más eficiente de comparar dos números de coma flotante.
Tommy Andersen

@TommyA lol tal vez, pero apuesto a que los viajes de ida y vuelta fueron rechazados por razones no relacionadas con la eficiencia. Aunque mi intuición es que sería bastante ineficiente en comparación con las matemáticas de HW fp, pero también dice que es poco probable que los algoritmos en el software fp tengan una gran diferencia al menos. Espero ansiosamente el análisis que hiciste mostrando que las preocupaciones de eficiencia en ese caso son significativas. Además, a veces menos que óptimo puede ser una respuesta valiosa, y como fue rechazado, a pesar de ser una técnica válida que incluso fue mencionada por el blog de Dawson sobre el tema, por lo que pensé que merecía un voto positivo.
shelleybutterfly

-2

No puedes comparar dos doublecon un fijo EPSILON. Dependiendo del valor de double, EPSILONvaría.

Una mejor doble comparación sería:

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 una manera más genérica:

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

44
Este método tiene muchas debilidades, como si los números ay bya son más pequeños que la epsilon()diferencia aún pueden ser significativos. Por el contrario, si los números son muy grandes, incluso un par de bits de error harán que la comparación falle incluso si desea que los números se consideren iguales. Esta respuesta es exactamente el tipo de algoritmo de comparación "genérico" que desea evitar.
SirGuy

-3

¿Por qué no realizar bitor XOR? Dos números de coma flotante son iguales si sus bits correspondientes son iguales. Creo que la decisión de colocar los bits de exponente antes de la mantisa se hizo para acelerar la comparación de dos flotadores. Creo que muchas respuestas aquí están perdiendo el punto de comparación epsilon. El valor de Epsilon solo depende de con qué precisión se comparan los números de coma flotante. Por ejemplo, después de hacer algo de aritmética con flotantes, obtienes dos números: 2.5642943554342 y 2.5642943554345. No son iguales, pero para la solución solo importan 3 dígitos decimales, por lo que son iguales: 2.564 y 2.564. En este caso, elige epsilon igual a 0.001. La comparación de Epsilon también es posible con XOR bit a bit. Corrígeme si estoy equivocado.


No agregue la misma respuesta a varias preguntas. Responda la mejor y marque el resto como duplicados. Ver meta.stackexchange.com/questions/104227/…
Bhargav Rao

No creo que sea posible la "comparación de épsilon" utilizando solo ExOr (y una o dos comparaciones), incluso restringido a representaciones normalizadas en el mismo formato.
barba gris
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.