C#的浮点比较函数

时间:2010-10-06 16:14:40

标签: c# .net floating-point

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

14 个答案:

答案 0 :(得分:64)

写一个有用的通用浮点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 || 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;
    }
}

您还可以找到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 || diff < 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 :(得分:22)

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

答案 2 :(得分: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));

答案 3 :(得分:7)

继续Michaeltesting提供的答案,在将原始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;

答案 4 :(得分:6)

小心一些答案......

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

2 - 问题来自浮点数的计算,你可能会失去一些精度。我的意思是像.1这样的数字可能会像.1000000000000001 ==&gt;经过计算。进行某些计算时,结果可能会被截断,以便以双精度表示。截断会带来你可能得到的错误。

3 - 为了防止在比较双值时出现问题,人们会引入一个通常称为epsilon的错误边距。如果2个浮动数字仅具有上下文epsilon ha差异,则它们被视为等于。 Epsilon永远不会加倍.Epsilon。

4 - epsilon永远不会是double.epsilon。它总是比那更重要。许多人认为它是双重的.Epsilon但他们确实是错的。要获得一个好的答案,请参阅:Hans Passant answer。 epsilon基于您的上下文,它取决于您在计算过程中达到的最大数量以及您正在进行的计算次数(截断误差累积)。 Epsilon是您在15个数字中表示的最小数字。

5 - 这是我使用的代码。小心我只使用我的epsilon进行少量计算。否则我将我的epsilon乘以10或100。

6 - 正如SvenL所指出的,我的epsilon可能不够大。我建议阅读SvenL评论。也许,&#34;十进制&#34;可以为你的案件做好工作吗?

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

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

答案 5 :(得分:4)

以下是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);
    }
}

答案 6 :(得分:4)

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

    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 :(得分:1)

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

答案 8 :(得分:1)

怎么样: b - delta < a && a < b + delta

答案 9 :(得分: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;
    }
}

随意改善这个答案。

答案 10 :(得分:0)

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

答案 11 :(得分: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

答案 12 :(得分:0)

if (Math.Abs(1.0 - 1.01) < TOLERANCE) {
//true
}

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

答案 13 :(得分:0)

对于来这里 UNITY specific

的人

Mathf.Approximately这样写

if(Mathf.Approximately(a, b))

基本上等于写作

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

哪里Mathf.Epsilon

<块引用>

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