C#的浮点比较函数

有人可以指向(或显示)一些很好的通用浮点比较函数在C#中比较浮点值吗? 我想为IsEqualIsGreaterIsLess实现函数。 我也只是真正关心双打而不是漂浮。

编写一个有用的通用浮点IsEqual非常非常困难,如果不是完全不可能的话。 您当前的代码将错误地为a==0失败。 该方法应该如何处理这种情况确实是一个定义问题,并且可以说最好是针对特定领域用例量身定制代码。

对于这样的事情,你确实需要一个好的testing套件。 这就是我为“浮点指南”所做的,这就是我最终想到的(Java代码,应该很容易翻译):

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

您也可以在网站上findtesting套件 。

附录:在c#中用于双打的相同代码(如问题中所述)

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

从Bruce Dawson关于比较浮游物的文章中 ,你也可以比较浮游物的整数。 亲密度由最低有效位决定。

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

编辑:BitConverter相对较慢。 如果你愿意使用不安全的代码,那么这里是一个非常快的版本:

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

除了Andrew Wang的回答:如果BitConverter方法太慢,但是你不能在你的项目中使用不安全的代码,这个结构比BitConverter快6倍:

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

(顺便说一下,我尝试使用公认的解决scheme,但它(至less我的转换至less)失败了一些unit testing也提到了答案。例如assertTrue(nearlyEqual(Float.MIN_VALUE, -Float.MIN_VALUE));

这里是西蒙·休伊特(Simon Hewitt)class的一个扩展版本:

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

小心一些答案…

1 – 你可以很容易地用内存中的15位有意义的数字来表示任意数字。 参见维基百科 。

2 – 问题来自浮点数的计算,你可以放松一些精度。 我的意思是,像.1这样的数字可能会变成类似.1000000000000001 ==>之后的计算。 当你做一些计算时,结果可能会被截断,以便以双精度表示。 截断带来了你可能得到的错误。

3 – 为了防止在比较double值时出现问题,人们引入了一个常被称为epsilon的误差。 如果两个浮动数字只有上下文差异,那么他们被认为是等于。 Epsilon从来没有double.Epsilon。

4 – epsilon从来没有double.epsilon。 它总是比这更大。 许多人认为它是双重的。但是它们确实是错误的。 有一个很好的答案,请参阅: Hans Passant答案 。 epsilon是基于你的上下文,取决于你在计算过程中达到的最大数量,以及你正在做的计算次数(截断误差累积)。 Epsilon是您可以用15位数字表示的最小数字。

5 – 这是我使用的代码。 小心,我只使用我的epsilon几个计算。 否则我把我的epsilon乘以10或100。

6 – 正如SvenL所指出的,我的epsilon可能不够大。 我build议阅读SvenL的评论。 另外,也许“十进制”可以为你的情况做的工作?

 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 /// <summary> /// 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 /// <summary> /// 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. /// </summary> /// <param name="value1"></param> /// <param name="value2"></param> /// <param name="precalculatedContextualEpsilon"></param> /// <returns></returns> 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; } // ****************************************************************** /// <summary> /// Mathlab equivalent /// </summary> /// <param name="dividend"></param> /// <param name="divisor"></param> /// <returns></returns> public static double Mod(this double dividend, double divisor) { return dividend - System.Math.Floor(dividend / divisor) * divisor; } // ****************************************************************** } 

这是我如何解决它,用可空双重扩展方法。

  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 

继续Michael提供的答案并进行testing ,将原始Java代码转换为C#时需要注意的一件重要事情是,Java和C#以不同的方式定义它们的常量。 例如,C#缺lessJava的MIN_NORMAL,MinValue的定义差别很大。

Java将MIN_VALUE定义为可能的最小正值,而C#将其定义为总体上可能的最小可表示值。 C#中的等价值是Epsilon。

MIN_NORMAL的缺乏对于原始algorithm的直接翻译是有问题的 – 如果没有它,那么对于接近于零的小值,事情就会开始崩溃。 Java的MIN_NORMAL遵循IEEE规范中尽可能小的数字,而不必将有效位的前导位置为零,考虑到这一点,我们可以为单打和双打定义我们自己的法线(在评论中提到的dbc原始答案)。

以下C#代码可以通过“浮点指南”中给出的所有testing,双版本可以在testing用例中进行所有testing,并对testing用例进行微小的修改,以增加精度。

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

双打的版本是相同的,除了types的变化,而正常是这样定义的。

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

虽然第二种select是比较普遍的,但是如果你有绝对的容忍度,并且必须执行许多这样的比较,第一种select会更好。 如果对图像中的每个像素进行比较,则第二个选项中的乘法运算可能会使执行速度降低到不可接受的性能水平。

我翻译了来自Michael Borgwardt的样本。 这是结果:

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

随意改善这个答案。

我认为你的第二个select是最好的select。 通常在浮点比较中,您通常只关心一个值是否在一个由ε的select控制的另一个值的某个容差范围内。

那么: b - delta < a && a < b + delta