Подтвердить что ты не робот

Сравнение значений float/double с использованием оператора ==

Инструмент проверки кода, который я использую, жалуется ниже, когда я начинаю сравнивать два значения float с помощью оператора равенства. Каков правильный способ и как это сделать? Есть ли вспомогательная функция (commons- *), которую я могу использовать повторно?

Описание

Невозможно сравнить значения с плавающей запятой, используя оператор equals (==)

Объяснение

Сравнение значений с плавающей запятой с использованием операторов равенства (==) или неравенства (! =) не всегда точным из-за ошибок округления.

Рекомендация

Сравните два значения float, чтобы узнать, близки ли они по значению.

float a;
float b;

if(a==b)
{
..
}
4b9b3361

Ответ 1

IBM имеет рекомендацию для сравнения двух поплавков с использованием деления, а не вычитания - это облегчает выбор эпсилона, который работает для всех диапазонов ввода.

if (abs(a/b - 1) < epsilon)

Что касается значения epsilon, я бы использовал 5.96e-08 как указано в этой таблице Википедии или, возможно, 2x это значение.

Ответ 2

Он хочет, чтобы вы сравнивали их с точностью, необходимой вам. Например, если вам нужно, чтобы первые 4 десятичные цифры ваших поплавков были равны, вы должны использовать:

if(-0.00001 <= a-b && a-b <= 0.00001)
{
..
}

Или:

if(Math.abs(a-b) < 0.00001){ ... }

Если вы добавляете желаемую точность к разнице двух чисел и сравниваете ее с удвоенной желаемой точностью.

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

a = 5.43421 и b = 5.434205 пройдут сравнение

Ответ 3

private static final float EPSILON = <very small positive number>;

if (Math.abs(a-b) < EPSILON)
   ...

Поскольку плавающая точка предлагает вам переменную, но неконтролируемую точность (то есть вы не можете установить точность, отличную от того, когда вы выбираете между использованием double и float), вам необходимо выбрать свою фиксированную точность для сравнения.

Обратите внимание, что это уже не истинный оператор эквивалентности, поскольку он не является транзитивным. Вы можете легко получить a equals b и b равно c, но a не равно c.

Edit: также обратите внимание, что если a отрицательно, а b - очень большое положительное число, вычитание может переполняться, а результат будет отрицательной бесконечности, но тест все равно будет работать, так как абсолютное значение отрицательного бесконечность - положительная бесконечность, которая будет больше, чем EPSILON.

Ответ 4

Использование commons-lang

org.apache.commons.lang.math.NumberUtils#compare

Также commons-math (в вашей ситуации более подходящее решение):

http://commons.apache.org/math/apidocs/org/apache/commons/math/util/MathUtils.html#equals(double, double)

Ответ 5

Тип float - приблизительное значение - там часть экспоненты и часть с конечной точностью.
Например:

System.out.println((0.6 / 0.2) == 3);  // false

Риск состоит в том, что крошечная ошибка округления может сделать сравнение false, когда математически это должно быть true.

Обходной путь заключается в сравнении поплавков, позволяющих незначительное различие по-прежнему быть "равным":

static float e = 0.00000000000001f;
if (Math.abs(a - b) < e)

Apache commons-math на помощь: MathUtils. (double x, double y, int maxUlps)

Возвращает true, если оба аргумента равны или находятся в пределах допустимой ошибки (включительно). Два числа с плавающей точкой считаются равными, если между ними есть (maxUlps - 1) (или меньше) чисел с плавающей запятой, т.е. Два смежных числа с плавающей запятой считаются равными.

Ответ 6

Здесь фактический код формирует реализацию Commons Math:

private static final int SGN_MASK_FLOAT = 0x80000000;

public static boolean equals(float x, float y, int maxUlps) {
    int xInt = Float.floatToIntBits(x);
    int yInt = Float.floatToIntBits(y);

    if (xInt < 0)
        xInt = SGN_MASK_FLOAT - xInt;

    if (yInt < 0)
        yInt = SGN_MASK_FLOAT - yInt;

    final boolean isEqual = Math.abs(xInt - yInt) <= maxUlps;

    return isEqual && !Float.isNaN(x) && !Float.isNaN(y);
}

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

Ответ 7

Я принял удар, основанный на том, как java реализует == для парных. Сначала он преобразуется в длинную целочисленную форму IEEE 754, а затем выполняет побитовое сравнение. Double также предоставляет статический doubleToLongBits(), чтобы получить целочисленную форму. Используя бит-фридинг, вы можете "закруглить" мантиссу двойника, добавив 1/2 (один бит) и усеченный.

В соответствии с наблюдением суперкатов функция сначала пробует простое сравнение == и только раунды, если это не удается. Вот что я придумал с некоторыми (надеюсь) полезными комментариями.

Я провел некоторое ограниченное тестирование, но не могу сказать, что я пробовал все крайние случаи. Кроме того, я не тестировал производительность. Это не должно быть слишком плохо.

Я только понял, что это по существу то же самое, что и предложение Дмитрия. Возможно, немного более кратким.

static public boolean nearlyEqual(double lhs, double rhs){
    // This rounds to the 6th mantissa bit from the end. So the numbers must have the same sign and exponent and the mantissas (as integers)
    // need to be within 32 of each other (bottom 5 bits of 52 bits can be different). 
    // To allow 'n' bits of difference create an additive value of 1<<(n-1) and a mask of 0xffffffffffffffffL<<n
    // e.g. 4 bits are: additive: 0x10L = 0x1L << 4 and mask: 0xffffffffffffffe0L = 0xffffffffffffffffL << 5
    //int bitsToIgnore = 5;
    //long additive = 1L << (bitsToIgnore - 1);
    //long mask = ~0x0L << bitsToIgnore; 
    //return ((Double.doubleToLongBits(lhs)+additive) & mask) == ((Double.doubleToLongBits(rhs)+additive) & mask);
    return lhs==rhs?true:((Double.doubleToLongBits(lhs)+0x10L) & 0xffffffffffffffe0L) == ((Double.doubleToLongBits(rhs)+0x10L) & 0xffffffffffffffe0L);
}

Следующая модификация обрабатывает изменение в знаке, где значение находится по обе стороны от 0.

return lhs==rhs?true:((Double.doubleToLongBits(lhs)+0x10L) & 0x7fffffffffffffe0L) == ((Double.doubleToLongBits(rhs)+0x10L) & 0x7fffffffffffffe0L);

Ответ 8

Во-первых, несколько замечаний:

  • "Стандартный" способ сделать это - выбрать константу epsilon, но постоянные эпсилоны не работают корректно для всех диапазонов чисел.
  • Если вы хотите использовать константу epsilon sqrt(EPSILON), квадратный корень из epsilon из float.h обычно считается хорошим. (это происходит от печально известной "оранжевой книги", имя которой ускользает от меня в данный момент).
  • Деление плавающей запятой будет медленным, поэтому вы, вероятно, захотите избежать его для сравнений, даже если оно ведет себя как выбор эпсилона, который настраивается для величин чисел.

Чего вы действительно хотите сделать? что-то вроде этого:
Сравните количество отображаемых чисел с плавающей запятой, значения которых отличаются.

Этот код исходит из этой действительно замечательной статьи Брюса Доусона. Статья была обновлена ​​здесь. Главное отличие состоит в том, что старая статья нарушает правило строгого сглаживания. (литье плавающих указателей на int указатель, разыменование, запись, отбрасывание). Хотя пурист C/С++ быстро укажет на недостаток, на практике это работает, и я считаю код более читаемым. Однако в новой статье используются союзы, а C/С++ - для сохранения своего достоинства. Для краткости я даю код, который нарушает строгий псевдоним ниже.

// Usable AlmostEqual function
bool AlmostEqual2sComplement(float A, float B, int maxUlps)
{
    // Make sure maxUlps is non-negative and small enough that the
    // default NAN won't compare as equal to anything.
    assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024);
    int aInt = *(int*)&A;
    // Make aInt lexicographically ordered as a twos-complement int
    if (aInt < 0)
        aInt = 0x80000000 - aInt;
    // Make bInt lexicographically ordered as a twos-complement int
    int bInt = *(int*)&B;
    if (bInt < 0)
        bInt = 0x80000000 - bInt;
    int intDiff = abs(aInt - bInt);
    if (intDiff <= maxUlps)
        return true;
    return false;
}

Основная идея в вышеприведенном коде - сначала заметить, что с учетом формата с плавающей запятой IEEE 754 {sign-bit, biased-exponent, mantissa}, что числа лексикографически упорядочены, если они интерпретируются как знаковые величины ints. То есть бит знака становится знаковым битом, а показатель и показатель всегда полностью превосходит мантиссу в определении величины поплавка и потому что он на первом месте определяется величиной числа, интерпретируемого как int.

Итак, мы интерпретируем битовое представление числа с плавающей запятой как int integer с подписью. Затем мы преобразуем INT с знаковой величиной в два дополнительных ints путем вычитания их из 0x80000000, если число отрицательно. Затем мы просто сравниваем два значения, как и любые подписанные два дополнения ints, и видя, сколько значений они отличаются. Если эта сумма меньше порога, который вы выбираете для количества отображаемых поплавков, значения могут отличаться и считаться равными, то вы говорите, что они "равны". Обратите внимание, что этот метод корректно позволяет "равным" числам отличаться большими значениями для больших плавающих величин и меньшими значениями для поплавков с меньшей величиной.

Ответ 9

Существует много случаев, когда нужно рассматривать два числа с плавающей запятой как равные, только если они абсолютно эквивалентны, и сравнение "дельты" было бы неправильным. Например, если f - чистая функция), и известно, что q = f (x) и y === x, то следует знать, что q = f (y) без необходимости его вычисления. К сожалению, == имеет два недостатка в этом отношении.

  • Если одно значение положительное ноль, а другое отрицательное ноль, они будут сравниваться как равные, даже если они не обязательно эквивалентны. Например, если f (d) = 1/d, a = 0 и b = -1 * a, то a == b, но f (a)!= F (b).

  • Если какое-либо значение является NaN, сравнение всегда будет давать false, даже если одно значение было назначено напрямую от другого.

Хотя есть много случаев, когда проверка чисел с плавающей запятой для точной эквивалентности является правильной и правильной, я не уверен в каких-либо случаях, когда фактическое поведение == следует считать предпочтительным. Возможно, все тесты для эквивалентности должны выполняться с помощью функции, которая фактически проверяет эквивалентность (например, путем сравнения побитовых форм).