Сравнивая IEEE с плавающей и двойной для равенства - PullRequest
9 голосов
/ 22 августа 2008

Каков наилучший метод для сравнения чисел с плавающей точкой IEEE и двойных чисел на равенство? Я слышал о нескольких методах, но я хотел посмотреть, что думает сообщество.

Ответы [ 15 ]

7 голосов
/ 22 августа 2008

Лучший подход, я думаю, состоит в том, чтобы сравнить ULPs .

bool is_nan(float f)
{
    return (*reinterpret_cast<unsigned __int32*>(&f) & 0x7f800000) == 0x7f800000 && (*reinterpret_cast<unsigned __int32*>(&f) & 0x007fffff) != 0;
}

bool is_finite(float f)
{
    return (*reinterpret_cast<unsigned __int32*>(&f) & 0x7f800000) != 0x7f800000;
}

// if this symbol is defined, NaNs are never equal to anything (as is normal in IEEE floating point)
// if this symbol is not defined, NaNs are hugely different from regular numbers, but might be equal to each other
#define UNEQUAL_NANS 1
// if this symbol is defined, infinites are never equal to finite numbers (as they're unimaginably greater)
// if this symbol is not defined, infinities are 1 ULP away from +/- FLT_MAX
#define INFINITE_INFINITIES 1

// test whether two IEEE floats are within a specified number of representable values of each other
// This depends on the fact that IEEE floats are properly ordered when treated as signed magnitude integers
bool equal_float(float lhs, float rhs, unsigned __int32 max_ulp_difference)
{
#ifdef UNEQUAL_NANS
    if(is_nan(lhs) || is_nan(rhs))
    {
        return false;
    }
#endif
#ifdef INFINITE_INFINITIES
    if((is_finite(lhs) && !is_finite(rhs)) || (!is_finite(lhs) && is_finite(rhs)))
    {
        return false;
    }
#endif
    signed __int32 left(*reinterpret_cast<signed __int32*>(&lhs));
    // transform signed magnitude ints into 2s complement signed ints
    if(left < 0)
    {
        left = 0x80000000 - left;
    }
    signed __int32 right(*reinterpret_cast<signed __int32*>(&rhs));
    // transform signed magnitude ints into 2s complement signed ints
    if(right < 0)
    {
        right = 0x80000000 - right;
    }
    if(static_cast<unsigned __int32>(std::abs(left - right)) <= max_ulp_difference)
    {
        return true;
    }
    return false;
}

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

Понятия не имею, почему эта проклятая штуковина портит мне подчеркивание. Изменить: О, возможно, это просто артефакт предварительного просмотра. Тогда все в порядке.

3 голосов
/ 22 августа 2008

Текущая версия, которую я использую, это

bool is_equals(float A, float B,
               float maxRelativeError, float maxAbsoluteError)
{

  if (fabs(A - B) < maxAbsoluteError)
    return true;

  float relativeError;
  if (fabs(B) > fabs(A))
    relativeError = fabs((A - B) / B);
  else
    relativeError = fabs((A - B) / A);

  if (relativeError <= maxRelativeError)
    return true;

  return false;
}

Это, похоже, решает большинство проблем путем сочетания относительной и абсолютной погрешности. Подход ULP лучше? Если так, то почему?

1 голос
/ 11 июня 2012

В числовом программном обеспечении вы часто хотите проверить, равны ли два числа с плавающей точкой точно . LAPACK полон примеров для таких случаев. Конечно, наиболее распространенный случай - это когда вы хотите проверить, равно ли число с плавающей запятой равным «Ноль», «Один», «Два», «Половина». Если кому-то интересно, я могу выбрать некоторые алгоритмы и перейти к деталям.

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

  • y = бета * y + альфа * A * x
  • y = бета * y + альфа * A ^ T * x
  • y = бета * y + альфа * A ^ H * x

Таким образом, если бета равна единице, у вас есть «плюс», а для беты - ноль «простое назначение». Таким образом, вы, безусловно, можете сократить вычислительные затраты, если уделите этим (обычным) случаям особый подход.

Конечно, вы можете разработать подпрограммы BLAS таким образом, чтобы избежать точных сравнений (например, с помощью некоторых флагов). Однако LAPACK полон примеров, когда это невозможно.

P.S:.

  • Есть, конечно, много случаев, когда вы не хотите, чтобы проверка была "точно равна". Для многих это даже может быть единственным случаем, с которым им приходится иметь дело. Все, что я хочу отметить, это то, что есть и другие случаи.

  • Хотя LAPACK написан на Фортране, логика такая же, если вы используете другие языки программирования для числового программного обеспечения.

1 голос
/ 22 августа 2008

@ DrPizza: Я не гуру производительности, но я ожидаю, что операции с фиксированной запятой будут быстрее, чем операции с плавающей запятой (в большинстве случаев).

Это скорее зависит от того, что вы делаете с ними. Тип с фиксированной запятой с тем же диапазоном, что и IEEE, будет во много раз медленнее (и во много раз больше).

Вещи, подходящие для поплавков:

3D графика, физика / инженерия, симуляция, симуляция климата ....

0 голосов
/ 09 октября 2008

Int позволяет мне выразить ~ 10 ^ 9 значений (независимо от диапазона), который кажется как достаточно для любой ситуации, когда я будет заботиться о двух из них быть равны. И если этого недостаточно, используйте 64-битная ОС, и у вас есть около 10 ^ 19 различные значения.

Я действительно достиг этого предела ... Я пытался совмещать время в пс и время в тактах в симуляции, где вы легко набираете 10 ^ 10 циклов. Что бы я ни делал, я очень быстро переполнял ничтожный диапазон 64-разрядных целых чисел ... 10 ^ 19 - это не так много, как вы думаете, дай мне 128-битные вычисления сейчас!

Поплавки позволили мне найти решение математических проблем, так как значения переполнились множеством нулей на нижнем конце. Таким образом, вы в основном имели десятичное число с плавающей запятой в числе без потери точности (я хотел бы получить более ограниченное число значений, допускаемых в мантиссе с плавающей запятой, по сравнению с 64-битным целым числом, но крайне нуждающимся в этом диапазоне! ).

А потом все превращается обратно в целые числа для сравнения и т. Д.

Раздражает, и в конце концов я отказался от всей попытки и просто положился на поплавки и <и>, чтобы выполнить работу. Не идеально, но работает для предполагаемого варианта использования.

0 голосов
/ 22 августа 2008

Это скорее зависит от того, кто ты делать с ними. Тип с фиксированной запятой с тем же диапазоном, что и IEEE-поплавок будет во много раз медленнее (и во много раз больше).

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

Int позволяет мне выразить ~ 10 ^ 9 значений (независимо от диапазона), что кажется достаточным для любой ситуации, когда я бы хотел, чтобы два из них были равны. А если этого недостаточно, используйте 64-битную ОС, и вы получите около 10 ^ 19 различных значений.

Я могу выразить значения в диапазоне от 0 до 10 ^ 200 (например) в int, страдает только битовое разрешение (разрешение будет больше 1, но, опять же, ни одно приложение не имеет такого рода диапазон, а также такого рода разрешение).

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

0 голосов
/ 22 августа 2008

@ DrPizza: Я не гуру производительности, но я ожидаю, что операции с фиксированной запятой будут быстрее, чем операции с плавающей запятой (в большинстве случаев).

@ Крейг Х: Конечно. Я полностью в порядке с этим, печатая это. Если a или b хранят деньги, то они должны быть представлены в фиксированной точке. Я изо всех сил пытаюсь придумать пример из реального мира, где такая логика должна быть связана с плавающими. Вещи, подходящие для поплавков:

  • вес
  • занимает
  • 1010 * Расстояния *
  • значения реального мира (например, из АЦП)

Для всех этих вещей, либо вы много затем числа и просто представляете результаты пользователю для человеческой интерпретации, или вы делаете сравнительное утверждение (даже если такое утверждение ", эта вещь находится в пределах 0,001 от этой другой вещи") ). Сравнительное утверждение, подобное моему, полезно только в контексте алгоритма: часть «в пределах 0,001» зависит от того, какой физический вопрос, который вы задаете. Это мой 0,02. Или я должен сказать 2/100-е?

0 голосов
/ 22 августа 2008

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

Возможно, мне следует объяснить проблему лучше. В C ++ следующий код:

#include <iostream>

using namespace std;


int main()
{
  float a = 1.0;
  float b = 0.0;

  for(int i=0;i<10;++i)
  {
    b+=0.1;
  }

  if(a != b)
  {
    cout << "Something is wrong" << endl;
  }

  return 1;
}

печатает фразу "Что-то не так". Вы говорите, что это должно?

0 голосов
/ 22 августа 2008

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

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

0 голосов
/ 22 августа 2008

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

...