c++ algorithm optimization floating-point

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



algorithm optimization (27)

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

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 desperdiciar el procesamiento.

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


`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 se pueden tratar como iguales.

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

return a==b;

Examinaré un enfoque diferente que asume que debe preocuparse por el primer problema y que el segundo es correcto 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 y luego encuentre una solución para satisfacer sus necesidades. No asuma que la respuesta fácil abordará sus necesidades. Si después de tu evaluación encuentras que fabs(ab) < EPSILON será suficiente, perfecto, ¡ fabs(ab) < EPSILON ! Pero tenga en cuenta sus deficiencias y otras posibles soluciones también.


Al darse cuenta de que esto es un hilo antiguo, este artículo es uno de los más directos que he encontrado al comparar números de punto 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 punto flotante: Comparación .

Podemos encontrar un artículo algo más práctico en las tolerancias de punto flotante revisadas y observa 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 y son grandes y falla en el caso relativo cuando son pequeños. Asumiendo 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 ; }


Como han señalado otros, el uso de una épsilon de exponente fijo (como 0.0000001) será inútil para los valores fuera 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 tan cerca como sea posible sin ser bit a bit idéntico. 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 acercan 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 del operando se encuentran dentro de un dominio estrecho (es decir, cerca de algún exponente específico), y si selecciona correctamente un valor épsilon para esa prueba específica. Si saca un número del aire ("¡Hey! 0.00001 es pequeño, por lo que debe ser bueno!"), Está condenado a errores numéricos. He dedicado mucho tiempo a depurar el código numérico erróneo en el que algunos malos tirones en valores aleatorios de épsilon hacen que funcione otro caso de prueba.

Si realiza programación numérica de cualquier tipo y cree que necesita alcanzar epsilons de punto fijo, LEA EL ARTÍCULO DE BRUCE PARA COMPARAR LOS NÚMEROS DE PUNTOS FLOTANTES .

Comparando números de punto flotante


Descubrí que el marco de prueba de Google C ++ contiene una buena implementación multiplataforma 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 y cuando retenga 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 para 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: [email protected] (Zhanyong Wan), [email protected] (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: Este post 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 de AlmostEquals directamente desde el código fuente de Google Test, y no la que pegué aquí.


El código que escribiste está pinchado:

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

El código correcto sería:

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

(... y sí esto es diferente)

Me pregunto si las 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 flotadores 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, puede obtener un mejor resultado al incorporar esta función. Aunque no es probable que mejore mucho ...

Edit: OJ, gracias por corregir tu código. Borré mi comentario en consecuencia


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, debes cambiar tu implementación un poco:

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

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


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

La comparación de números de punto flotante por Bruce Dawson es un buen lugar para comenzar cuando se observa una comparación de punto 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 iguales quieres que sean los números.

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


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


Para un enfoque más profundo, lea cygnus-software.com/papers/comparingfloats/comparingfloats.htm . 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; }


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

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

  1. La presunción implícita en un algoritmo de que si a==b y b==c entonces a==c .

  2. Usando el mismo épsilon para líneas medidas en pulgadas y líneas medidas en mils (.001 pulgadas). Eso es a==b pero 1000a!=1000b . (Esta es la razón por la que 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 los dobles produjo resultados correctos).

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

BTW, std::numeric_limits<double>::epsilon() es la "máquina epsilon". 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 menores que 1. (Esto es en respuesta a la respuesta de @cdv ...)

Además, si básicamente tiene aritmética int 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 y cuando no hagas cosas que resulten en fracciones (4.0 / 3.0) o no vayas fuera del tamaño de un int.


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. Hay dos problemas con las comparaciones de flotación: tiene una precisión limitada y el significado de "aproximadamente cero" depende del contexto (consulte el siguiente punto).
  2. ¿Es 1E-8 aproximadamente igual que 1E-16? Si está buscando datos de sensores ruidosos, entonces probablemente sí, pero si está haciendo una simulación molecular, ¡puede que no! En pocas palabras: siempre debe pensar en el valor de tolerancia en el contexto de una llamada de función específica y no solo hacer que sea una constante genérica codificada en toda la aplicación.
  3. Para las funciones generales de la biblioteca, aún es bueno tener un parámetro con tolerancia predeterminada . Una opción típica es numeric_limits::epsilon() que es igual a FLT_EPSILON en float.h. Sin embargo, esto es problemático porque épsilon para comparar valores como 1.0 si no es el mismo que épsilon para valores como 1E9. El FLT_EPSILON se define para 1.0.
  4. La implementación obvia para verificar si el número está dentro de la tolerancia es fabs(ab) <= epsilon sin embargo, esto no funciona porque el epsilon predeterminado está definido para 1.0. Necesitamos escalar épsilon hacia arriba o hacia abajo en términos de a y b.
  5. Hay dos soluciones para este problema: configuras epsilon proporcional a max(a,b) o puedes obtener los siguientes números representables alrededor de a y luego ver si b cae dentro de ese rango. El primero se llama método "relativo" y luego se llama método ULP.
  6. Ambos métodos realmente fallan al comparar 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; }


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


Qt implementa dos funciones, puedes 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 necesites las siguientes funciones, ya que

Tenga en cuenta que la comparación de valores donde p1 o p2 es 0.0 no funcionará, ni tampoco la comparación de valores donde uno de los valores sea 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; }


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


En términos de la escala de cantidades:

Si epsilonla fracción pequeña 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; }


Mi camino puede no ser correcto pero útil

Convierte ambos float a cadenas y luego compara 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; }

operador overlaoding también se puede hacer


¿Por qué no realizar XOR a nivel de bits? 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í faltan el punto de comparación épsilon. El valor de Epsilon solo depende de la precisión con la que se comparan los números de punto 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, entonces 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 a nivel de bits. Corrígeme si estoy equivocado.


Aquí hay una 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); }

Corriendo 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 tan cerca como sea posible, y aún se comparan como no están cerca. Por lo tanto, para valores superiores a 1.0, también puede utilizar una prueba de igualdad. Los épsilones fijos no lo salvarán cuando compare valores de punto flotante.


Depende de qué tan precisa quieres que sea la comparación. Si desea comparar exactamente el mismo número, vaya con ==. (Casi nunca querrá hacer esto a menos que desee exactamente el mismo número). En cualquier plataforma adecuada, también puede hacer lo siguiente:

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

Como fabssuele ser bastante rápido. Por muy rápido quiero decir que es básicamente un bit Y, así que es mejor que sea rápido.

Y los trucos de enteros para comparar dobles y flotantes son agradables, pero tienden a dificultar el manejo eficaz de las diversas líneas 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 los valores que se usan con frecuencia. (Load-hit-store para los que cuidan).


Desafortunadamente, incluso su código "inútil" 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, puedes escalar este valor a los números que estás 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 como mínimo. En general, sin embargo, querría tener en cuenta el ruido en sus cálculos e ignorar algunos de los bits menos significativos, por lo que una comparación más realista sería:

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

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


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

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

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


Escribo esto para Java, pero tal vez te resulte útil. Utiliza largos en lugar de dobles, pero se encarga de los NaN, 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.


La comparación de propósito general de los números de punto 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 en los que tal truco no funciona. Por ejemplo, considere trabajar con una función Heaviside (paso) de un número en cuestión (las opciones de stock digital vienen a la mente) 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 cambiaría el problema de la barrera original a dos nuevos. Nuevamente, no hay una solución de propósito general para tales problemas y la solución particular puede requerir cambiar el método numérico para lograr la estabilidad.


Me preocuparía mucho por cualquiera de estas respuestas que involucran la resta de puntos flotantes (por ejemplo, fabs (ab) <epsilon). Primero, los números de punto flotante se vuelven más dispersos en magnitudes mayores y en magnitudes suficientemente altas donde el espaciado es mayor que épsilon, también podría estar haciendo a == b. En segundo lugar, restar dos números de punto flotante muy cercanos (como tienden a ser, dado que estás buscando una igualdad cercana) es exactamente cómo obtienes una cancelación catastrófica .

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


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

Este código está destinado a demostrar el concepto, no a 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 hecho 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 ); } };


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

Una mejor comparación doble 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; }


/// 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 epsilon = 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, es probable que sea 1.0000009xxx. Lo probé con 1.0 y 1.0000011 y esta vez obtengo el resultado esperado.