Funções de comparação de ponto flutuante para c #

Alguém pode apontar (ou mostrar) algumas boas funções gerais de comparação de ponto flutuante em C # para comparar valores de ponto flutuante? Eu quero implementar funções para IsEqual , IsGreater um IsLess . Eu também só me importo com duplos não flutua.

   

Escrever um ponto flutuante útil para fins gerais IsEqual é muito, muito difícil, se não completamente impossível. Seu código atual irá falhar mal por a==0 . A maneira como o método deve se comportar para esses casos é realmente uma questão de definição e, possivelmente, o código seria melhor adaptado para o caso de uso de domínio específico.

Para esse tipo de coisa, você realmente precisa de uma boa suíte de testes. Foi assim que o fiz para o The Floating-Point Guide , foi isso que eu criei no final (código Java, deve ser fácil de traduzir):

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

Você também pode encontrar a suíte de testes no site .

Apêndice: O mesmo código em c # para duplos (como solicitado em perguntas)

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

Do artigo de Bruce Dawson sobre comparação de carros alegóricos , você também pode comparar carros alegóricos como números inteiros. A proximidade é determinada pelos 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 ); } 

EDIT: BitConverter é relativamente lento. Se você está disposto a usar códigos não seguros, então aqui está uma versão muito 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 ); } 

Além da resposta de Andrew Wang: se o método BitConverter é muito lento mas você não pode usar código não seguro em seu projeto, esta estrutura é ~ 6x mais 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; } 

(Aliás, tentei usar a solução aceita, mas (bem, minha conversão, pelo menos) falhou em alguns dos testes unitários também mencionados na resposta, por exemplo, assertTrue(nearlyEqual(Float.MIN_VALUE, -Float.MIN_VALUE)); )

Continuando com as respostas fornecidas por Michael e testando , uma coisa importante a ter em mente ao traduzir o código Java original para C # é que Java e C # definem suas constantes de forma diferente. C #, por exemplo, não possui o MIN_NORMAL do Java, e as definições para MinValue diferem muito.

Java define MIN_VALUE como o menor valor positivo possível, enquanto C # o define como o menor valor representável possível em geral. O valor equivalente em C # é Epsilon.

A falta de MIN_NORMAL é problemática para a tradução direta do algoritmo original – sem isso, as coisas começam a quebrar para valores pequenos próximos de zero. O MIN_NORMAL do Java segue a especificação IEEE do menor número possível sem ter o bit inicial do significando como zero e, com isso em mente, podemos definir nossas próprias normais para singles e duplas (que dbc mencionou nos comentários para a resposta original ).

O seguinte código C # para singles passa por todos os testes dados no The Floating Point Guide, e a edição dupla passa todos os testes com pequenas modificações nos casos de teste para dar conta da precisão aumentada.

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

A versão para duplas é idêntica, salvo para alterações de tipo e que a normal é definida desta forma.

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

Tenha cuidado com algumas respostas …

1 – Você poderia facilmente representar qualquer número com 15 dígitos significativos na memory com um duplo. Veja a Wikipedia .

2 – O problema vem do cálculo de números flutuantes onde você pode perder alguma precisão. Quero dizer que um número como .1 poderia se tornar algo como .1000000000000001 ==> após o cálculo. Quando você faz algum cálculo, os resultados podem ser truncados para serem representados em um duplo. Esse truncamento traz o erro que você poderia obter.

3 – Para evitar o problema ao comparar valores duplos, as pessoas introduzem uma margem de erro muitas vezes chamada epsilon. Se 2 números flutuantes tiverem apenas uma diferença epsilon ha contextual, eles serão considerados iguais. Epsilon nunca é o dobro.Epsilon.

4 – O épsilon nunca é double.epsilon. É sempre maior que isso. Muitos povos pensam que é duplo.Epsilon, mas eles estão realmente errados. Para ter uma ótima resposta, por favor veja: Hans Passant answer . O epsilon é baseado em seu contexto, onde depende do maior número que você alcança durante o cálculo e do número de cálculos que você está fazendo (erro de truncamento acumulado). Epsilon é o menor número que você poderia representar no seu contexto com 15 dígitos.

5 – Esse é o código que eu uso. Tenha cuidado para usar meu epsilon apenas para alguns cálculos. Caso contrário, multiplico meu épsilon por 10 ou 100.

6 – Como observado por SvenL, é possível que o meu épsilon não seja grande o suficiente. Eu sugiro ler o comentário de SvenL. Além disso, talvez “decimal” poderia fazer o trabalho para o seu 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; } // ****************************************************************** } 

Aqui está uma versão muito expandida da class 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); } } 

Aqui está como eu resolvi isso, com o método de extensão dupla anulável.

  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 

Embora a segunda opção seja mais geral, a primeira opção é melhor quando você tem uma tolerância absoluta e quando precisa executar muitas dessas comparações. Se essa comparação for para todos os pixels de uma imagem, a multiplicação nas segundas opções poderá atrasar sua execução em níveis inaceitáveis ​​de desempenho.

Eu traduzi a amostra de Michael Borgwardt . Este é o 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; } } 

Sinta-se à vontade para melhorar esta resposta.

Eu acho que sua segunda opção é a melhor aposta. Geralmente, na comparação de ponto flutuante, muitas vezes você só se importa que um valor esteja dentro de uma certa tolerância de outro valor, controlado pela seleção de épsilon.

Que tal: b - delta < a && a < b + delta