Функции сравнения с плавающей точкой для C # - PullRequest
63 голосов
/ 06 октября 2010

Может ли кто-нибудь указать (или показать) некоторые хорошие общие функции сравнения с плавающей запятой в C # для сравнения значений с плавающей запятой? Я хочу реализовать функции для IsEqual, IsGreater и IsLess. Я также только действительно забочусь о двойниках, не плавая.

Ответы [ 11 ]

66 голосов
/ 06 октября 2010

Написание полезной универсальной плавающей запятой 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;
    }
}
22 голосов
/ 07 октября 2010

Из статьи Брюса Доусона о сравнении чисел вы также можете сравнивать числа как целые числа. Близость определяется младшими значащими битами.

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 );
    }
11 голосов
/ 13 июля 2011

В дополнение к ответу Эндрю Вана: если метод BitConverter слишком медленный, но вы не можете использовать небезопасный код в своем проекте, эта структура примерно в 6 раз быстрее, чем 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;
}

(Кстати, я попытался использовать принятое решение, но оно (по крайней мере, мое преобразование) не прошло некоторые из модульных тестов, также упомянутых в ответе. Например, assertTrue(nearlyEqual(Float.MIN_VALUE, -Float.MIN_VALUE));)

7 голосов
/ 04 июня 2017

Продолжая из ответов, предоставленных Майклом и тестированием , при переводе исходного кода Java в C # важно помнить, что Java и C # определяют свои константы по-разному. В C #, например, отсутствует Java MIN_NORMAL, а определения MinValue сильно отличаются.

Java определяет MIN_VALUE как наименьшее возможное положительное значение, в то время как C # определяет его как наименьшее возможное представимое значение в целом. Эквивалентным значением в C # является Epsilon.

Отсутствие MIN_NORMAL проблематично для прямой трансляции исходного алгоритма - без него вещи начинают ломаться при малых значениях, близких к нулю. MIN_NORMAL в Java следует спецификации 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;
6 голосов
/ 15 февраля 2016

Будьте осторожны с некоторыми ответами ...

1 - Вы можете легко представить любое число с 15 значащими цифрами в памяти с двойным. См. Википедия .

2 - Проблема связана с вычислением плавающих чисел, когда вы можете потерять некоторую точность. Я имею в виду, что после вычисления число типа .1 может стать чем-то вроде .1000000000000001 ==>. Когда вы делаете некоторые вычисления, результаты могут быть усечены, чтобы быть представленными в двойном. Это усечение приносит ошибку, которую вы можете получить.

3 - Чтобы избежать проблемы при сравнении двойных значений, люди вводят погрешность, часто называемую epsilon. Если 2 числа с плавающей запятой имеют только контекстную разницу в эпсилоне, то они считаются равными. Эпсилон никогда не бывает двойным. Эпсилон.

4 - эпсилон никогда не бывает двойным. Это всегда больше, чем это. Многие народы думают, что это двойной. Эпсилон, но они действительно не правы. Чтобы получить отличный ответ, пожалуйста, смотрите: Hans Passant answer . Эпсилон основан на вашем контексте, где он зависит от наибольшего числа, которое вы достигаете во время вычислений, и от количества вычислений, которые вы делаете (накапливается ошибка усечения). Epsilon - это наименьшее число, которое вы можете представить в своем контексте с 15 цифрами.

5 - это код, который я использую. Будьте осторожны, я использую свой эпсилон только для нескольких расчетов. В противном случае я умножаю свой эпсилон на 10 или 100.

6 - Как отмечает SvenL, возможно, мой эпсилон недостаточно велик. Предлагаю прочитать комментарий SvenL. Кроме того, возможно, "десятичная" может сделать работу для вашего случая?

public static class DoubleExtension
    {
        // ******************************************************************
        // Base on Hans Passant Answer on:
        // /1511260/double-epsilon-dlya-ravenstva-bolshe-menshe-menshe-ili-ravno-bolshe-ili-ravno

        /// <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:
        // /1511260/double-epsilon-dlya-ravenstva-bolshe-menshe-menshe-ili-ravno-bolshe-ili-ravno

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

        // ******************************************************************
    }
4 голосов
/ 08 апреля 2014

Вот как я решил эту проблему, используя метод двойного расширения с нулевым значением.

4 голосов
/ 23 декабря 2011

Вот очень расширенная версия класса Саймона Хьюитта:

/// <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 голос
/ 23 июля 2015

Я перевел образец с Майкл Боргвардт .Вот результат:

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 голос
/ 06 октября 2010

А как же: b - delta < a && a < b + delta

1 голос
/ 06 октября 2010

Хотя второй вариант является более общим, первый вариант лучше, когда у вас есть абсолютный допуск и когда вам нужно выполнить многие из этих сравнений.Если это сравнение, скажем, для каждого пикселя в изображении, умножение во вторых параметрах может замедлить ваше выполнение до недопустимых уровней производительности.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...