Сравнение поплавков и удвоений IEEE для равенства

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

Ответ 1

Лучший подход, который я считаю, - сравнить ULP.

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

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

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

Ответ 2

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

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? Если да, то почему?

Ответ 3

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

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

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

3D-графика, физика/инженерия, моделирование, моделирование климата....

Ответ 4

В числовом программном обеспечении вы часто хотите проверить, равно ли два числа с плавающей запятой. LAPACK полна примеров для таких случаев. Конечно, наиболее распространенным случаем является то, где вы хотите проверить, равно ли число с плавающей запятой "Zero", "One", "Two", "Half". Если кто-то заинтересован, я могу выбрать некоторые алгоритмы и подробно остановиться.

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

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

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

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

P.S:.

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

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

Ответ 5

Боже мой, пожалуйста, не интерпретируйте бит float как int, если вы не работаете на P6 или ранее.

Ответ 6

Боже мой, пожалуйста, не интерпретируйте бит float как int, если вы не работаете на P6 или ранее.

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

то есть. это цена, которую стоит заплатить.

Ответ 7

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

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

Ответ 8

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

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

Ответ 9

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

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

Ответ 10

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

Ответ 11

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

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

Ответ 12

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

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

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

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

Ответ 13

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

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

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

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

Ответ 14

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

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

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

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

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

Ответ 15

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

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

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

И затем все преобразуется обратно в целые числа для сравнения и т.д.

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