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

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

Описание

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

объяснение

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

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

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

float a; float b; if(a==b) { .. } 

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

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

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

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

 if(-0.00001 <= ab && ab <= 0.00001) { .. } 

Или:

 if(Math.abs(ab) < 0.00001){ ... } 

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

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

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

 private static final float EPSILON = ; if (Math.abs(ab) < EPSILON) ... 

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

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

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

Использование 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) 

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) (или меньше) чисел с плавающей запятой, то есть два соседних числа с плавающей запятой считаются равными.

Вот реальный код формы реализации 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); } 

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

Я принял удар, основанный на том, как 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< 

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

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

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

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

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

Этот код исходит из этой действительно замечательной статьи Брюса Доусона. Статья была обновлена здесь . Главное отличие состоит в том, что старая статья нарушает правило строгого сглаживания. (литье плавающих указателей на int указатель, разыменование, запись, отбрасывание). Хотя пурист C / C ++ быстро укажет на недостаток, на практике это работает, и я считаю код более читаемым. Однако в новой статье используются союзы, а 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 с знаковой величиной. Затем мы преобразуем int-sign-values ​​в двоичные дополнения ints, вычитая их из 0x80000000, если число отрицательно. Затем мы просто сравниваем два значения, так как мы будем подписывать два дополнения ints и видеть, сколько значений они отличаются. Если эта сумма меньше порога, который вы выбираете для количества отображаемых поплавков, значения могут отличаться и считаться равными, то вы говорите, что они «равны». Обратите внимание, что этот метод правильно позволяет «равным» числам отличаться большими значениями для больших поплавков по величине и меньшими значениями для поплавков меньшей величины.

Есть много случаев, когда нужно считать два числа с плавающей запятой равными, только если они абсолютно эквивалентны, и сравнение «дельты» было бы неправильным. Например, если 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, даже если одно значение было назначено непосредственно из другого.

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

  • Почему значение с плавающей запятой, такое как 3.14, по умолчанию считается в MSVC?
  • Как проверить, что строка обрабатывается двойным?
  • Как преобразовать целое число в float в Java?
  • Каков всеобъемлющий диапазон float и double в Java?
  • C #: преобразовать массив байтов в float
  • Сравнение числа с плавающей точкой с нолем
  • Каково первое целое число, которое плавающий IEEE 754 не может точно представлять?
  • Есть ли функция для округления поплавка на C или мне нужно написать собственное?
  • Спецификатор ширины печати для поддержания точности значения с плавающей запятой
  • Как определить число с плавающей запятой, используя регулярное выражение
  • Float и двойной тип данных в Java
  • Давайте будем гением компьютера.