【问题标题】:Floating point comparison functions for C#C# 的浮点比较函数
【发布时间】:2010-10-06 16:14:39
【问题描述】:

有人可以指出(或展示)C# 中一些用于比较浮点值的通用浮点比较函数吗?我想为IsEqual、IsGreater 和IsLess 实现功能。我也只真正关心双打而不是花车。

【问题讨论】:

  • 你的问题是什么?
  • 有人可以指出(或展示)C# 中一些很好的通用浮点比较函数来比较浮点值吗?问题是很多人都给出了部分答案。我正在寻找更完整的东西。
  • 这很危险,当数字变得毫无意义时,它会假装有一个有意义的结果。关注菲利普的帖子。
  • @Hans Passant - 我看不出菲利普的帖子有什么帮助。我并不是说我发现的这个功能是好的或正确的,我正在寻求这方面的帮助。
  • 当在 SO 上询问浮点相等比较时,I was given this advice:“问题是,你真的想要/需要对浮点值进行相等测试吗?也许你应该重新设计你的算法。” 也就是说,一开始就不必做这样的比较,这样你就不用担心如何把它弄对了。

标签: c# .net floating-point


【解决方案1】:

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

对于这种事情,你真的,真的需要一个好的测试套件。这就是我为The Floating-Point Guide所做的,这就是我最终想出的(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;
    }
}

您也可以找到the test suite on the site。

附录:在 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;
    }
}

【讨论】:

  • +float.Epsilon 和 -float.Epsilon 不被认为是相等的,因为它们是最小的非零可表示浮点值。这显然与您描述的Float.MIN_VALUE 的行为不同。你认为这个功能在 C# 中可行吗?此页面上的另一个人遇到了同样的问题:stackoverflow.com/questions/3874627/…
  • float.Epsilon == -float.Epsilon 是false。经过额外的实验后,我发现以下表现最好(未通过 5 次测试):pastebin.com/xC8NddSA
  • ??这些是我认为您所指的两个最小的非零值。我在之前的粘贴中包含了有关失败测试的信息(您可能已经看到了)
  • 查看这 5 个失败案例,它们似乎实际上是有效的(至少对于 C# 实现而言),因为操作数之间的差异明显小于 epsilon 参数。例如,以下内容肯定应该测试为真。 AlmostEqual(1.401298E-20f, -1.401298E-20f, 1E-12f) 我希望它也能通过1E-19f 但以1E-20f 失败。我对吗?如果没有我在粘贴中提出的额外检查 [1],否则我仍然会得到其他失败的测试。
  • 对于 c#,Double.MinValue 不执行所需的操作。它返回具有最大可能绝对值的负数-1.7976931348623157E+308。 double.Epsiilon 看起来对应于Float.Min_Value;没有等同于Min_Normal。也许你想要(1e7)*double.Epsilon之类的东西?
【解决方案2】:

从Bruce Dawson's paper on comparing floats,您还可以将浮点数作为整数进行比较。接近度由最低有效位决定。

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

【讨论】:

  • 有趣。我遇到了一些似乎说这可能是最好的方法的参考资料(与整数类型相比)。上面的 Michael Borgwardt 还链接到 Dawson 的论文。不知道位转换是不是很贵?
  • BitConverter 很慢。我添加了一个更快的版本,但它使用了不安全的代码。
  • 谢谢,我会考虑的,它对发现这个问题的其他人会有用。
  • 有没有办法将绝对误差从浮点数转换为maxDeltaBits,以便此函数的工作方式与abs(a - b) &lt; delta 相似(但当然更准确)?我喜欢这种方法的想法,但我更喜欢一个可以指定最大绝对误差的函数。
  • BitConverter 方法也在不安全的上下文中运行,但它包含验证检查,因此它会比您的直接代码慢,但它是“计算机时间”。
【解决方案3】:

继续 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));)

【讨论】:

  • 我发现那些单元测试失败了,你找到原因了吗?
【解决方案4】:

小心一些答案...

UPDATE 2019-0829,我还包含了微软反编译的代码,应该比我的要好得多。

1 - 您可以轻松地用双精度表示内存中具有 15 个有效数字的任何数字。见Wikipedia。

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

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

4 - 2 个 double 值之间的差异可能大于 double.epsilon。实际双精度值与计算出的值之间的差异取决于您进行了多少次计算以及哪些计算。许多人认为它总是 double.Epsilon 但他们真的错了。要得到一个很好的答案,请参阅:Hans Passant answer。 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:
        // 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;
        }

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

【讨论】:

  • 你和汉斯说得很有道理,而且解释得很好。经过一些测试,我不得不得出结论,1e-15 甚至是偏大的。当将 n 乘以 1/n 到总和值时,即使是 1e-15 的 epsilon 在从 1 到 1e6 的 n 中的 999,282 中“失败”。 Eric Lippert 的Picking your epsilon 似乎是看待问题的另一种方式。
  • @SvenL,谢谢,我在回答中添加了评论。我红色 Eric Lippert 的回答并将记住这一点。
【解决方案5】:

继续Michael 和testing 提供的答案,将原始Java 代码转换为C# 时要记住的重要一点是Java 和C# 以不同方式定义它们的常量。例如,C# 缺少 Java 的 MIN_NORMAL,MinValue 的定义差异很大。

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

缺少 MIN_NORMAL 对原始算法的直接转换是有问题的 - 没有它,对于接近零的小值,事情开始崩溃。 Java 的 MIN_NORMAL 遵循最小可能数字的 IEEE 规范,有效数字的前导位不为零,考虑到这一点,我们可以为单打和双打定义自己的法线(dbc 在原始答案的 cmets 中提到)。

以下单曲版 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;

【讨论】:

  • 如果计算的法线应该是符合 IEEE 标准的 double.Epsilon 和 float.Epsilon(计算的法线不同)的替代品,那么与diff,当 diff 小于正常值时?那么 epsilon 参数是否会简单地用作乘数,逐渐降低精度,或者如果低于 1,则将精度提高到超出计算的法线?如果你愿意的话,我只是看不出如何在两个返回语句中使用相同的 epsilon 值,并且具有相同的“含义”。再说一次,我不是数学天才。
【解决方案6】:

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

    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

【讨论】:

    【解决方案7】:

    这是 Simon Hewitt 课程的扩展版本:

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

    【讨论】:

      【解决方案8】:

      怎么样: b - delta &lt; a &amp;&amp; a &lt; b + delta

      【讨论】:

        【解决方案9】:

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

        请随时改进此答案。

        【讨论】:

        • 这段代码不正确,float.MinValue是float数据类型可以表示的最小值,而不是@987654325可以表示的最小正数值@.
        • 感谢您的重要提示!在发帖之前我应该​​更彻底地考虑......
        • 如果 float.Epsilon 是最小的正浮点值,那么 diff
        【解决方案10】:

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

        【讨论】:

          【解决方案11】:

          虽然第二个选项更通用,但当您有绝对容差并且必须执行许多此类比较时,第一个选项会更好。如果针对图像中的每个像素进行此比较,则第二个选项中的乘法可能会使您的执行速度降低到无法接受的性能水平。

          【讨论】:

          • 性能对我的应用程序来说不是大问题,我更关心的是正确性。
          • 您需要更加努力地与过早的优化本能作斗争。首先让它正常工作,然后才开始考虑让它变得更快(如果它甚至是一个问题的话)。
          【解决方案12】:
          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;
              }
          }
          

          传递给IsEqual 的tolerance 的值是客户端可以决定的。

          IsEqual(1.002, 1.001);          -->   False
          IsEqual(1.002, 1.001, 0.01);    -->   True
          

          【讨论】:

            【解决方案13】:
            if (Math.Abs(1.0 - 1.01) < TOLERANCE) {
            //true
            }
            

            其中TOLERANCE 是您希望达到的数量。例如公差 = 0.01 不会导致真。但是如果你保持它为 0.011,它将导致结果为真,因为差异是触手可及的。

            【讨论】:

              【解决方案14】:

              对于来这里的人UNITY specific

              有Mathf.Approximately这么写

              if(Mathf.Approximately(a, b))
              

              基本上等于写

              if(Mathf.Abs(a - b) <= Mathf.Epsilon)
              

              在哪里Mathf.Epsilon

              浮点数可以有的不为零的最小值。

              【讨论】:

                猜你喜欢
                • 2017-08-19
                • 2012-05-13
                • 2011-10-23
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-04-19
                • 1970-01-01
                相关资源
                最近更新 更多