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

12 minutos de lectura

¿Cual es la forma mas efectiva para la comparacion flotante
Alex

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

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 un desperdicio de procesamiento.

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

  • > 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 indicó anteriormente, es claro y fácil de entender.

    –Andrew Coleson

    20 de agosto de 2008 a las 5:55

  • Aquí está la forma implementada en Boost Test Library: http://www.boost.org/doc/libs/1_36_0/libs/test/doc/html/utf/testing-tools/floating_point_comparison.html

    – Alessandro Jacopson

    31 de octubre de 2008 a las 13:38

  • Lo único que no es óptimo acerca de 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!

    – 3 años

    27 de marzo de 2010 a las 17:04

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

    – Thomas Matthews

    11 de junio de 2012 a las 2:11

  • “Simplemente hacer esto no es correcto” – Esto es mera basura, por supuesto usando == puede ser perfectamente correcto, pero esto depende completamente del contexto que no se da en la pregunta. Hasta que se conozca ese contexto, == aun sigue siendo el “forma más eficiente”.

    – Christian Raú

    13 de mayo de 2013 a las 7:39


1647647890 389 ¿Cual es la forma mas efectiva para la comparacion flotante
DO.

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

Sin embargo, deberías cambiar un poco tu implementación:

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 un publicación de blog reciente. Disfrutar.

  • @DonReba: Solo si EPSILON Se define como DBL_EPSILON. Normalmente será un valor específico elegido dependiendo de la precisión requerida de la comparación.

    – Nemo157

    20 de diciembre de 2011 a las 20:38

  • EPSILON la comparación no funciona cuando los flotantes son grandes, ya que la diferencia entre flotantes consecutivos también se hace grande. Ver Este artículo.

    – kevintodisco

    23 de octubre de 2013 a las 4:04


  • No es de extrañar que haya Z-fighting en algunos juegos cuando las texturas/objetos lejanos parpadean, como en Battlefield 4. Comparando la diferencia con EPSILON es bastante inútil. Debe comparar con un umbral que tenga sentido para las unidades en cuestión. Además, usa std::abs ya que está sobrecargado para diferentes tipos de punto flotante.

    – Máximo Egorushkin

    19 de febrero de 2014 a las 16:40


  • Voté negativo ya que el código de ejemplo muestra el error típico que la mayoría de los programadores repiten. 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).

    – usuario2261015

    13 de abril de 2017 a las 7:58

  • @SirGuy, publique una respuesta para demostrar cómo hacer esto correctamente, o enlace a una aquí. Me gustaría ver alternativas no basadas en epsilon.

    – Gabriel grapas

    26 de noviembre de 2020 a las 2:36

  • fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon); salvó mi 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.0000000000 donde se puede ver claramente que hay casi una diferencia de 2 entre los dos números) ¡lo cual es bastante bueno! Esto sucede cuando el error de redondeo acumulado desborda la parte decimal del número.

    – rbaleksandar

    12 de octubre de 2016 a las 12:24


  • Muy buen y útil artículo de Bruce Dawson, ¡gracias!

    – BobMorane

    28 de agosto de 2018 a las 19:14

  • Dado que esta pregunta está etiquetada como C++, sus cheques serían más fáciles de leer si se escribieran como std::max(std::abs(a), std::abs(b)) (o con std::min()); std::abs en C++ está sobrecargado con tipos flotantes y dobles, por lo que funciona bien (siempre puede mantener fabs aunque para la legibilidad).

    – Razakhel

    1 oct 2018 a las 17:09


  • definitelyGreaterThan está informando cierto para algo que definitivamente debería ser igual a, es decir no mas grande que.

    – mwpowellhtx

    19 de enero de 2019 a las 21:05

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

    – mwpowellhtx

    20 de enero de 2019 a las 17:31

¿Cual es la forma mas efectiva para la comparacion flotante
skrebel

Descubrí que el Marco de prueba de Google C++ contiene una buena implementación basada en plantillas multiplataforma de AlmostEqual2sComplement que funciona tanto en dobles como 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 de cambiar el código donde se usa 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 sea bueno, pero algunas personas encontraron mejoras. Mejor vaya a obtener la última versión de AlmostEquals directamente desde el código fuente de Google Test, y no el que pegué aquí.

  • +1: Estoy de acuerdo en que este es correcto. Sin embargo, no explica por qué. Mira 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 tanto código, la gente se perderá la respuesta.

    – ruido sin arte

    17 de marzo de 2013 a las 19:07


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

    – JeffCharter

    17/03/2014 a las 21:16

  • ¡Buen descubrimiento! Sin embargo, supongo 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 enlazar a eso también, entonces.

    – Skrebel

    1 de mayo de 2014 a las 12:48


  • ¿Soy el único que obtiene ‘falso’ al comparar los dobles 0 con 1e-16? La representación sesgada de 0 es 9223372036854775808 mientras que la representación sesgada de 1e-16 es 13590969439990876604. ¿Parece ser una discontinuidad en la representación o estoy haciendo algo mal?

    – Gastón

    1 oct 2014 a las 17:17

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

    – Jaege

    25 de noviembre de 2016 a las 3:11

1647647891 343 ¿Cual es la forma mas efectiva para la comparacion flotante
Grom

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

  • +1: Estoy de acuerdo en que este es correcto. Sin embargo, no explica por qué. Mira 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 tanto código, la gente se perderá la respuesta.

    – ruido sin arte

    17 de marzo de 2013 a las 19:07


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

    – JeffCharter

    17/03/2014 a las 21:16

  • ¡Buen descubrimiento! Sin embargo, supongo 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 enlazar a eso también, entonces.

    – Skrebel

    1 de mayo de 2014 a las 12:48


  • ¿Soy el único que obtiene ‘falso’ al comparar los dobles 0 con 1e-16? La representación sesgada de 0 es 9223372036854775808 mientras que la representación sesgada de 1e-16 es 13590969439990876604. ¿Parece ser una discontinuidad en la representación o estoy haciendo algo mal?

    – Gastón

    1 oct 2014 a las 17:17

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

    – Jaege

    25 de noviembre de 2016 a las 3:11

Me doy cuenta de que este es un hilo antiguo, 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 punto flotante La guía de coma flotante: comparación.

Podemos encontrar un artículo algo más práctico en Tolerancias de punto flotante revisadas y notas que hay tolerancia absoluta test, que se reduce a esto en C++:

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

y tolerancia relativa prueba:

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 x y y son 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 ;
}

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad