Funciones de comparación de coma flotante para C #

¿Puede alguien señalar (o mostrar) algunas buenas funciones generales de comparación de coma flotante en C # para comparar valores de coma flotante? Quiero implementar funciones para IsEqual , IsGreater y IsLess . También me importa que los dobles no floten.

Escribir un punto flotante útil de uso general IsEqual es muy, muy difícil, si no absolutamente imposible. Su código actual fallará mal para a==0 . Cómo se debe comportar el método para tales casos es realmente una cuestión de definición, y podría decirse que el código se adaptaría mejor para el caso de uso de dominio específico.

Para este tipo de cosas , realmente necesitas un buen banco de pruebas. Así es como lo hice para The Floating-Point Guide , esto es lo que se me ocurrió al final (el código Java debería ser lo suficientemente fácil de traducir):

 public static boolean nearlyEqual(float a, float b, float epsilon) { final float absA = Math.abs(a); final float absB = Math.abs(b); final float diff = Math.abs(a - b); if (a == b) { // shortcut, handles infinities return true; } else if (a == 0 || b == 0 || diff < Float.MIN_NORMAL) { // a or b is zero or both are extremely close to it // relative error is less meaningful here return diff < (epsilon * Float.MIN_NORMAL); } else { // use relative error return diff / (absA + absB) < epsilon; } } 

También puede encontrar el conjunto de pruebas en el sitio .

Apéndice: Mismo código en c # para dobles (como se preguntó en las preguntas)

 public static bool NearlyEqual(double a, double b, double epsilon) { double absA = Math.Abs(a); double absB = Math.Abs(b); double diff = Math.Abs(a - b); if (a == b) { // shortcut, handles infinities return true; } else if (a == 0 || b == 0 || diff < Double.Epsilon) { // a or b is zero or both are extremely close to it // relative error is less meaningful here return diff < epsilon; } else { // use relative error return diff / (absA + absB) < epsilon; } } 

Del artículo de Bruce Dawson sobre la comparación de carrozas , también puedes comparar carrozas como enteros. La cercanía está determinada por bits menos significativos.

 public static bool AlmostEqual2sComplement( float a, float b, int maxDeltaBits ) { int aInt = BitConverter.ToInt32( BitConverter.GetBytes( a ), 0 ); if ( aInt < 0 ) aInt = Int32.MinValue - aInt; // Int32.MinValue = 0x80000000 int bInt = BitConverter.ToInt32( BitConverter.GetBytes( b ), 0 ); if ( bInt < 0 ) bInt = Int32.MinValue - bInt; int intDiff = Math.Abs( aInt - bInt ); return intDiff <= ( 1 << maxDeltaBits ); } 

EDITAR: BitConverter es relativamente lento. Si está dispuesto a usar un código no seguro, esta es una versión muy rápida:

  public static unsafe int FloatToInt32Bits( float f ) { return *( (int*)&f ); } public static bool AlmostEqual2sComplement( float a, float b, int maxDeltaBits ) { int aInt = FloatToInt32Bits( a ); if ( aInt < 0 ) aInt = Int32.MinValue - aInt; int bInt = FloatToInt32Bits( b ); if ( bInt < 0 ) bInt = Int32.MinValue - bInt; int intDiff = Math.Abs( aInt - bInt ); return intDiff <= ( 1 << maxDeltaBits ); } 

Además de la respuesta de Andrew Wang: si el método BitConverter es demasiado lento pero no puede usar código inseguro en su proyecto, esta estructura es ~ 6 veces más rápida que BitConverter:

 [StructLayout(LayoutKind.Explicit)] public struct FloatToIntSafeBitConverter { public static int Convert(float value) { return new FloatToIntSafeBitConverter(value).IntValue; } public FloatToIntSafeBitConverter(float floatValue): this() { FloatValue = floatValue; } [FieldOffset(0)] public readonly int IntValue; [FieldOffset(0)] public readonly float FloatValue; } 

(Por cierto, traté de usar la solución aceptada pero (al menos mi conversión al menos) falló algunas de las pruebas unitarias también mencionadas en la respuesta. Por ejemplo, assertTrue(nearlyEqual(Float.MIN_VALUE, -Float.MIN_VALUE)); )

Continuando con las respuestas proporcionadas por Michael y las pruebas , una cosa importante a tener en cuenta al traducir el código original de Java a C # es que Java y C # definen sus constantes de manera diferente. C #, por ejemplo, carece del MIN_NORMAL de Java, y las definiciones para MinValue son muy diferentes.

Java define MIN_VALUE como el valor positivo más pequeño posible, mientras que C # lo define como el valor representable más pequeño posible en general. El valor equivalente en C # es Epsilon.

La falta de MIN_NORMAL es problemática para la traducción directa del algoritmo original; sin él, las cosas comienzan a descomponerse para valores pequeños cercanos a cero. El MIN_NORMAL de Java sigue la especificación IEEE del número más pequeño posible sin tener el bit principal del significado como cero, y con eso en mente, podemos definir nuestras propias normales para solteros y dobles (que dbc mencionó en los comentarios a la respuesta original) )

El siguiente código C # para singles pasa todas las pruebas dadas en The Floating Point Guide, y la edición doble pasa todas las pruebas con modificaciones menores en los casos de prueba para tener en cuenta la mayor precisión.

 public static bool ApproximatelyEqualEpsilon(float a, float b, float epsilon) { const float floatNormal = (1 << 23) * float.Epsilon; float absA = Math.Abs(a); float absB = Math.Abs(b); float diff = Math.Abs(a - b); if (a == b) { // Shortcut, handles infinities return true; } if (a == 0.0f || b == 0.0f || diff < floatNormal) { // a or b is zero, or both are extremely close to it. // relative error is less meaningful here return diff < (epsilon * floatNormal); } // use relative error return diff / Math.Min((absA + absB), float.MaxValue) < epsilon; } 

La versión para dobles es idéntica excepto para cambios de tipo y que la normal se define así en su lugar.

 const double doubleNormal = (1L << 52) * double.Epsilon; 

Ten cuidado con algunas respuestas …

1 – Puede representar fácilmente cualquier número con 15 dígitos significativos en la memoria con un doble. Ver Wikipedia .

2 – El problema proviene del cálculo de números flotantes donde se puede perder cierta precisión. Quiero decir que un número como .1 podría convertirse en algo así como .1000000000000001 ==> después del cálculo. Cuando hace un cálculo, los resultados pueden truncarse para representarse en un doble. Ese truncamiento trae el error que podrías obtener.

3 – Para evitar el problema al comparar valores dobles, las personas introducen un margen de error a menudo llamado épsilon. Si 2 números flotantes solo tienen una diferencia contextual epsilon ha, entonces se consideran iguales. Epsilon nunca es doble. Epsilon.

4 – El epsilon nunca es double.epsilon. Siempre es más grande que eso. Muchos pueblos piensan que es doble. Epsilon pero están realmente equivocados. Para tener una buena respuesta, consulte: Hans Passant answer . El épsilon se basa en su contexto, donde depende del mayor número al que llegue durante su cálculo y del número de cálculos que está realizando (error de truncamiento acumulado). Epsilon es el número más pequeño que podrías representar en tu contexto con 15 dígitos.

5 – Este es el código que uso. Tenga cuidado de usar mi épsilon solo por unos pocos cálculos. De lo contrario, multiplico mi épsilon por 10 o 100.

6 – Como lo señaló SvenL, es posible que mi épsilon no sea lo suficientemente grande. Sugiero leer el comentario de SvenL. Además, tal vez “decimal” podría hacer el trabajo para su caso?

 public static class DoubleExtension { // ****************************************************************** // Base on Hans Passant Answer on: // https://stackoverflow.com/questions/2411392/double-epsilon-for-equality-greater-than-less-than-less-than-or-equal-to-gre ///  /// Compare two double taking in account the double precision potential error. /// Take care: truncation errors accumulate on calculation. More you do, more you should increase the epsilon. public static bool AboutEquals(this double value1, double value2) { double epsilon = Math.Max(Math.Abs(value1), Math.Abs(value2)) * 1E-15; return Math.Abs(value1 - value2) <= epsilon; } // ****************************************************************** // Base on Hans Passant Answer on: // https://stackoverflow.com/questions/2411392/double-epsilon-for-equality-greater-than-less-than-less-than-or-equal-to-gre ///  /// Compare two double taking in account the double precision potential error. /// Take care: truncation errors accumulate on calculation. More you do, more you should increase the epsilon. /// You get really better performance when you can determine the contextual epsilon first. ///  ///  ///  ///  ///  public static bool AboutEquals(this double value1, double value2, double precalculatedContextualEpsilon) { return Math.Abs(value1 - value2) <= precalculatedContextualEpsilon; } // ****************************************************************** public static double GetContextualEpsilon(this double biggestPossibleContextualValue) { return biggestPossibleContextualValue * 1E-15; } // ****************************************************************** ///  /// Mathlab equivalent ///  ///  ///  ///  public static double Mod(this double dividend, double divisor) { return dividend - System.Math.Floor(dividend / divisor) * divisor; } // ****************************************************************** } 

Aquí hay una versión muy expandida de la clase de Simon Hewitt:

 ///  /// Safely converts a  to an  for floating-point comparisons. ///  [StructLayout(LayoutKind.Explicit)] public struct FloatToInt : IEquatable, IEquatable, IEquatable, IComparable, IComparable, IComparable { ///  /// Initializes a new instance of the  class. ///  /// The  value to be converted to an . public FloatToInt(float floatValue) : this() { FloatValue = floatValue; } ///  /// Gets the floating-point value as an integer. ///  [FieldOffset(0)] public readonly int IntValue; ///  /// Gets the floating-point value. ///  [FieldOffset(0)] public readonly float FloatValue; ///  /// Indicates whether the current object is equal to another object of the same type. ///  ///  /// true if the current object is equal to the  parameter; otherwise, false. ///  /// An object to compare with this object. public bool Equals(FloatToInt other) { return other.IntValue == IntValue; } ///  /// Indicates whether the current object is equal to another object of the same type. ///  ///  /// true if the current object is equal to the  parameter; otherwise, false. ///  /// An object to compare with this object. public bool Equals(float other) { return IntValue == new FloatToInt(other).IntValue; } ///  /// Indicates whether the current object is equal to another object of the same type. ///  ///  /// true if the current object is equal to the  parameter; otherwise, false. ///  /// An object to compare with this object. public bool Equals(int other) { return IntValue == other; } ///  /// Compares the current object with another object of the same type. ///  ///  /// A value that indicates the relative order of the objects being compared. The return value has the following meanings: Value Meaning Less than zero This object is less than the  parameter.Zero This object is equal to . Greater than zero This object is greater than . ///  /// An object to compare with this object. public int CompareTo(FloatToInt other) { return IntValue.CompareTo(other.IntValue); } ///  /// Compares the current object with another object of the same type. ///  ///  /// A value that indicates the relative order of the objects being compared. The return value has the following meanings: Value Meaning Less than zero This object is less than the  parameter.Zero This object is equal to . Greater than zero This object is greater than . ///  /// An object to compare with this object. public int CompareTo(float other) { return IntValue.CompareTo(new FloatToInt(other).IntValue); } ///  /// Compares the current object with another object of the same type. ///  ///  /// A value that indicates the relative order of the objects being compared. The return value has the following meanings: Value Meaning Less than zero This object is less than the  parameter.Zero This object is equal to . Greater than zero This object is greater than . ///  /// An object to compare with this object. public int CompareTo(int other) { return IntValue.CompareTo(other); } ///  /// Indicates whether this instance and a specified object are equal. ///  ///  /// true if  and this instance are the same type and represent the same value; otherwise, false. ///  /// Another object to compare to. 2 public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (obj.GetType() != typeof(FloatToInt)) { return false; } return Equals((FloatToInt)obj); } ///  /// Returns the hash code for this instance. ///  ///  /// A 32-bit signed integer that is the hash code for this instance. ///  /// 2 public override int GetHashCode() { return IntValue; } ///  /// Implicitly converts from a  to an . ///  /// A . /// An integer representation of the floating-point value. public static implicit operator int(FloatToInt value) { return value.IntValue; } ///  /// Implicitly converts from a  to a . ///  /// A . /// The floating-point value. public static implicit operator float(FloatToInt value) { return value.FloatValue; } ///  /// Determines if two  instances have the same integer representation. ///  /// A . /// A . /// true if the two  have the same integer representation; otherwise, false. public static bool operator ==(FloatToInt left, FloatToInt right) { return left.IntValue == right.IntValue; } ///  /// Determines if two  instances have different integer representations. ///  /// A . /// A . /// true if the two  have different integer representations; otherwise, false. public static bool operator !=(FloatToInt left, FloatToInt right) { return !(left == right); } } 

Así es como lo resolví, con el método de doble extensión que se puede anotar.

  public static bool NearlyEquals(this double? value1, double? value2, double unimportantDifference = 0.0001) { if (value1 != value2) { if(value1 == null || value2 == null) return false; return Math.Abs(value1.Value - value2.Value) < unimportantDifference; } return true; } 

...

  double? value1 = 100; value1.NearlyEquals(100.01); // will return false value1.NearlyEquals(100.000001); // will return true value1.NearlyEquals(100.01, 0.1); // will return true 

Aunque la segunda opción es más general, la primera opción es mejor cuando tiene una tolerancia absoluta, y cuando tiene que ejecutar muchas de estas comparaciones. Si esta comparación es, por ejemplo, para cada píxel en una imagen, la multiplicación en las segundas opciones puede ralentizar su ejecución a niveles inaceptables de rendimiento.

Traducí la muestra de Michael Borgwardt . Este es el resultado:

 public static bool NearlyEqual(float a, float b, float epsilon){ float absA = Math.Abs (a); float absB = Math.Abs (b); float diff = Math.Abs (a - b); if (a == b) { return true; } else if (a == 0 || b == 0 || diff < float.Epsilon) { // a or b is zero or both are extremely close to it // relative error is less meaningful here return diff < epsilon; } else { // use relative error return diff / (absA + absB) < epsilon; } } 

Siéntase libre de mejorar esta respuesta.

Creo que tu segunda opción es la mejor opción. Generalmente, en la comparación de coma flotante, a menudo solo le importa que un valor esté dentro de una tolerancia determinada de otro valor, controlado por la selección de épsilon.

Qué hay de: b - delta < a && a < b + delta