C#的浮点比较功能


70

有人可以指向(或显示)一些不错的C#通用浮点比较函数来比较浮点值吗?我想实施的功能IsEqualIsGreater一个IsLess。我也只真正关心双打而不是浮动。


1
您到底要问什么?
NullUserException 2010年

有人可以指向(或显示)一些不错的C#通用浮点比较函数来比较浮点值吗?问题是很多人都显示了部分答案。我正在寻找更完整的东西。
凯文·盖尔2010年

2
这很危险,当数字变得毫无意义时,它会带来有意义的结果。注意Philip的帖子。
汉斯·帕桑特

1
@Hans Passant-我不知道Philip的帖子如何提供帮助。我并不是说我发现的这个功能是好的还是正确的,我并没有在这方面寻求帮助。
凯文·盖尔

@NullUserException-感谢您添加c#标签。不知道我怎么想的。
凯文·盖尔

Answers:


73

编写有用的通用浮点IsEqual非常非常困难,即使不是完全不可能的。您当前的代码将因严重失败a==0。在这种情况下,该方法的行为确实是一个定义问题,并且可以说,最好针对特定领域用例量身定制代码。

对于这种事情,您确实非常需要一个好的测试套件。这就是我在《浮点指南》中所做的事情,这是我最终想到的(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 || absA + absB < 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;
    }
}

您还可以在网站上找到测试套件

附录: c#中相同的代码用于双打(按问题要求)

public static bool NearlyEqual(double a, double b, double epsilon)
{
    const double MinNormal = 2.2250738585072014E-308d;
    double absA = Math.Abs(a);
    double absB = Math.Abs(b);
    double diff = Math.Abs(a - b);

    if (a.Equals(b))
    { // shortcut, handles infinities
        return true;
    } 
    else if (a == 0 || b == 0 || absA + absB < MinNormal) 
    {
        // a or b is zero or both are extremely close to it
        // relative error is less meaningful here
        return diff < (epsilon * MinNormal);
    }
    else
    { // use relative error
        return diff / (absA + absB) < epsilon;
    }
}

1
+float.Epsilon并且-float.Epsilon不被视为相等,因为它们是可表示的最小浮点值,不为零。这显然不同于您描述的行为Float.MIN_VALUE。您认为此功能在C#中是否可行?此页面上的另一个人也遇到相同的问题:stackoverflow.com/questions/3874627/…–
Lea Hayes

1
float.Epsilon == -float.Epsilonfalse。经过额外的实验,我发现以下各项到目前为止表现最好(失败5项测试):pastebin.com/xC8NddSA
Lea Hayes

1
?? 那是我认为您所指的两个最小的非零值。我在以前的粘贴中包含了有关失败测试的信息(您可能会看到)
Lea Hayes

1
查看这5种失败的情况,似乎它们实际上是有效的(至少对于C#实现而言),因为操作数之间的差明显小于epsilon参数。例如,肯定可以满足以下条件。AlmostEqual(1.401298E-20f, -1.401298E-20f, 1E-12f)我希望它也会通过,1E-19f但不会通过1E-20f。我对吗?如果没有我在粘贴中建议的额外检查[1],否则我仍然会进行其他失败的测试。
Lea Hayes

3
对于c#,Double.MinValue不执行所需的操作。它返回具有最大可能绝对值的负数-1.7976931348623157E+308double.Epsiilon看起来对应于Float.Min_Value; 没有等价的东西Min_Normal。也许您想要类似的东西(1e7)*double.Epsilon
dbc

26

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

1
有趣。我遇到了一些引用,这些引用似乎表明这可能是实现此目的的最佳方法(与整数类型相比)。上面的Michael Borgwardt也链接到Dawson的论文。我想知道位转换是否非常昂贵?
凯文·盖尔

BitConverter很慢。我添加了一个更快的版本,但是它使用了不安全的代码。
安德鲁·王

谢谢,我会考虑的,它将对发现此问题的其他人很有用。
凯文·盖尔2010年

2
有没有一种方法可以将绝对错误从float转换为,maxDeltaBits从而使该函数的工作原理与之相似(但当然更准确)abs(a - b) < delta?我喜欢这种方法的想法,但是我更喜欢一个可以指定最大绝对误差的函数。
Lea Hayes

BitConverter方法也可以在不安全的上下文中运行,但是它包含验证检查,因此它比直接代码要慢,但它是“计算机时间”。
SlaneR

11

进一步了解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;
}

(顺便说一句,我尝试使用接受的解决方案,但它(至少是我的转换)未能通过答案中也提到的某些单元测试。例如assertTrue(nearlyEqual(Float.MIN_VALUE, -Float.MIN_VALUE));


我发现那些单元测试失败了,您能找出原因吗?
Lea Hayes

9

迈克尔测试提供的答案之后,将原始Java代码转换为C#时要记住的重要一点是Java和C#定义了不同的常量。例如,C#缺少Java的MIN_NORMAL,并且MinValue的定义差异很大。

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

缺少MIN_NORMAL对于直接转换原始算法会造成问题-如果没有它,则对于零附近的较小值,事情会开始崩溃。Java的MIN_NORMAL遵循IEEE最小可能数的规范,而有效位的前导位不为零,因此请记住,我们可以为单打和双打定义自己的法线(在原始答案的注释中提到了dbc )。

以下针对单打的C#代码通过了《浮点指南》中给出的所有测试,而双打的版本通过了对测试用例进行的少量修改以说明提高的精度的所有测试。

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

除了类型更改外,双打的版本是相同的,而法线的定义是这样的。

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

2
如果计算出的法线被认为是IEEE兼容的double.Epsilon和float.Epsilon的替代(计算出的法线与之不同),为什么当与diff比较时,它们与指定的epsilon相乘,当diff小于正常吗?然后epsilon参数是否会简单地用作乘法器,从而逐渐降低精度,或者如果精度低于1,则将精度提高到计算出的法线之外?我只是看不到如何在两个return语句中使用相同的epsilon值,如果可以的话,它们具有相同的“含义”。再说一次,我不是数学天才。
塔科马塔尔奥特曼

9

注意一些答案...

更新2019-0829,我还包括了Microsoft反编译的代码,它应该比我的要好得多。

1-您可以轻松地在内存中用双精度数表示任何包含15个有效数字的数字。参见维基百科

2-问题来自浮点数的计算,您可能在其中失去一些精度。我的意思是,像.1这样的数字在计算后可能变成像.1000000000000001 ==>之类的东西。当您进行一些计算时,结果可能会被截断以便用双精度表示。截断带来了您可能会得到的错误。

3-为了防止在比较双精度值时出现问题,人们引入了误差容限,通常称为epsilon。如果2个浮点数只有上下文的epsilon作为差值,则将它们视为相等。double.Epsilon是double值与其neigbor(下一个或上一个)值之间的最小数字。

4-2个double值之间的差可能大于double.epsilon。实际double值和计算出的double值之间的差异取决于您进行了多少次计算以及进行了哪些计算。许多人认为它总是双倍Epsilon,但他们确实错了。要获得很好的答案,请参阅:Hans Passant答案。epsilon取决于您的上下文,它取决于您在计算过程中达到的最大数目以及正在执行的计算数目(截断误差累积)。

5-这是我使用的代码。请注意,我仅将我的epsilon用于少量计算。否则我将epsilon乘以10或100。

6-如SvenL所述,我的epsilon可能不够大。我建议阅读SvenL评论。另外,也许“十进制”可以胜任您的情况?

微软反编译的代码:

// Decompiled with JetBrains decompiler
// Type: MS.Internal.DoubleUtil
// Assembly: WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// MVID: 33C590FB-77D1-4FFD-B11B-3D104CA038E5
// Assembly location: C:\Windows\Microsoft.NET\assembly\GAC_MSIL\WindowsBase\v4.0_4.0.0.0__31bf3856ad364e35\WindowsBase.dll

using MS.Internal.WindowsBase;
using System;
using System.Runtime.InteropServices;
using System.Windows;

namespace MS.Internal
{
  [FriendAccessAllowed]
  internal static class DoubleUtil
  {
    internal const double DBL_EPSILON = 2.22044604925031E-16;
    internal const float FLT_MIN = 1.175494E-38f;

    public static bool AreClose(double value1, double value2)
    {
      if (value1 == value2)
        return true;
      double num1 = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * 2.22044604925031E-16;
      double num2 = value1 - value2;
      if (-num1 < num2)
        return num1 > num2;
      return false;
    }

    public static bool LessThan(double value1, double value2)
    {
      if (value1 < value2)
        return !DoubleUtil.AreClose(value1, value2);
      return false;
    }

    public static bool GreaterThan(double value1, double value2)
    {
      if (value1 > value2)
        return !DoubleUtil.AreClose(value1, value2);
      return false;
    }

    public static bool LessThanOrClose(double value1, double value2)
    {
      if (value1 >= value2)
        return DoubleUtil.AreClose(value1, value2);
      return true;
    }

    public static bool GreaterThanOrClose(double value1, double value2)
    {
      if (value1 <= value2)
        return DoubleUtil.AreClose(value1, value2);
      return true;
    }

    public static bool IsOne(double value)
    {
      return Math.Abs(value - 1.0) < 2.22044604925031E-15;
    }

    public static bool IsZero(double value)
    {
      return Math.Abs(value) < 2.22044604925031E-15;
    }

    public static bool AreClose(Point point1, Point point2)
    {
      if (DoubleUtil.AreClose(point1.X, point2.X))
        return DoubleUtil.AreClose(point1.Y, point2.Y);
      return false;
    }

    public static bool AreClose(Size size1, Size size2)
    {
      if (DoubleUtil.AreClose(size1.Width, size2.Width))
        return DoubleUtil.AreClose(size1.Height, size2.Height);
      return false;
    }

    public static bool AreClose(Vector vector1, Vector vector2)
    {
      if (DoubleUtil.AreClose(vector1.X, vector2.X))
        return DoubleUtil.AreClose(vector1.Y, vector2.Y);
      return false;
    }

    public static bool AreClose(Rect rect1, Rect rect2)
    {
      if (rect1.IsEmpty)
        return rect2.IsEmpty;
      if (!rect2.IsEmpty && DoubleUtil.AreClose(rect1.X, rect2.X) && (DoubleUtil.AreClose(rect1.Y, rect2.Y) && DoubleUtil.AreClose(rect1.Height, rect2.Height)))
        return DoubleUtil.AreClose(rect1.Width, rect2.Width);
      return false;
    }

    public static bool IsBetweenZeroAndOne(double val)
    {
      if (DoubleUtil.GreaterThanOrClose(val, 0.0))
        return DoubleUtil.LessThanOrClose(val, 1.0);
      return false;
    }

    public static int DoubleToInt(double val)
    {
      if (0.0 >= val)
        return (int) (val - 0.5);
      return (int) (val + 0.5);
    }

    public static bool RectHasNaN(Rect r)
    {
      return DoubleUtil.IsNaN(r.X) || DoubleUtil.IsNaN(r.Y) || (DoubleUtil.IsNaN(r.Height) || DoubleUtil.IsNaN(r.Width));
    }

    public static bool IsNaN(double value)
    {
      DoubleUtil.NanUnion nanUnion = new DoubleUtil.NanUnion();
      nanUnion.DoubleValue = value;
      ulong num1 = nanUnion.UintValue & 18442240474082181120UL;
      ulong num2 = nanUnion.UintValue & 4503599627370495UL;
      if (num1 == 9218868437227405312UL || num1 == 18442240474082181120UL)
        return num2 > 0UL;
      return false;
    }

    [StructLayout(LayoutKind.Explicit)]
    private struct NanUnion
    {
      [FieldOffset(0)]
      internal double DoubleValue;
      [FieldOffset(0)]
      internal ulong UintValue;
    }
  }
}

我的代码:

public static class DoubleExtension
    {
        // ******************************************************************
        // Base on Hans Passant Answer on:
        // /programming/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:
        // /programming/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;
        }

        // ******************************************************************
    }

1
您和汉斯很有道理,并能很好地解释它。经过一些测试,我必须得出一个结论,那1e-15就是在很大的方面。当将n / n乘以1 / n时,甚至1e-15的epsilon在1到1e6的n中的999282中也“失败”。埃里克·利珀特(Eric Lippert)挑选您的epsilon似乎是解决问题的另一种方法。
SvenL

1
@SvenL,谢谢,我在回答中添加了评论。我用红色的埃里克·利珀特(Eric Lippert)回答,请牢记这一点。
埃里克·厄勒

5

这是我使用可为空的双扩展方法解决问题的方法。

    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

4

这是西蒙·休伊特课程的扩展版本:

/// <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

我认为您的第二选择是最好的选择。通常,在浮点比较中,您通常只关心一个值在另一个值的一定公差范围内,这取决于选择epsilon。


1

尽管第二个选项比较笼统,但是当您具有绝对公差并且必须执行许多这样的比较时,第一个选项会更好。如果说对图像中的每个像素进行此比较,则第二个选项中的相乘可能会将您的执行速度减慢到无法接受的性能水平。


性能对于我的应用程序来说不是一个大问题,我更关心正确性。
凯文·盖尔2010年

1
您需要更努力地应对过早的优化本能。首先使其正确运行,然后才开始考虑使其快速运行(如果这根本不是问题)。
Michael Borgwardt 2010年


1

我翻译了迈克尔·伯格沃德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;
    }
}

随时改进此答案。


1
该代码是错误的,float.MinValue是可以由float数据类型表示的最小值,而不是可以由表示的最小正值float
Brian Fairservice

谢谢你的重要提示!在发布之前,我应该更仔细地考虑……
测试

如果float.Epsilon是最低的正浮点值,则diff <float.Epsilon将始终返回false,除非diff正好为0,而a == b已经处理过。
塔科马塔尔奥特曼

0
static class FloatUtil {

    static bool IsEqual(float a, float b, float tolerance = 0.001f) {
      return Math.Abs(a - b) < tolerance;
    }

    static bool IsGreater(float a, float b) {
      return a > b;
    }

    static bool IsLess(float a, float b) {
      return a < b;
    }
}

客户可以决定将其值tolerance传递给IsEqual谁。

IsEqual(1.002, 1.001);          -->   False
IsEqual(1.002, 1.001, 0.01);    -->   True
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.