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

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

21.08.2008 21:47:24
Оформить этот ответ на аналогичный вопрос.
grom 21.08.2008 23:30:55
15 ОТВЕТОВ
РЕШЕНИЕ

Я думаю, что лучший подход - это сравнивать 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;
}

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

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

7
21.08.2008 22:09:57
Это выигрывает для точности. Но из-за производительности ... вам придется потратить немного этой точности на повышение скорости. Согласовано?
OJ. 8.10.2008 04:50:39
Если вам нужны удвоения или переносимость: я нашел отличную кроссплатформенную реализацию, которая может справиться как с удвоениями, так и с плавающей точкой в ​​Google Test, и разместил ее здесь: stackoverflow.com/questions/17333/…
skrebbel 6.08.2010 11:43:04

О, дорогой лорд, пожалуйста, не интерпретируйте биты с плавающей точкой как целые, если вы не используете P6 или более раннюю версию.

0
3.07.2012 14:46:43

О, дорогой лорд, пожалуйста, не интерпретируйте биты с плавающей точкой как целые, если вы не используете P6 или более раннюю версию.

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

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

0
21.08.2008 21:59:00
Я подозреваю, что (a==b || (a!=a && b != b))на многих процессорах будет быстрее, чем что-либо, связанное с приведением типа float-bits-to-int, хотя мне действительно больно, что нужны выражения, подобные приведенным выше. Интересно, какую выгоду предлагает решение Nan! = NaN, и сколько оно стоит в потраченном впустую коде и времени отладки?
supercat 3.04.2015 22:21:52

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

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
21.08.2008 22:22:26

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

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

0
3.07.2012 15:00:35

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

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

0
21.08.2008 22:07:11

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

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

0
21.08.2008 22:09:05

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

0
21.08.2008 22:09:43
Работа с (почти) равенством является распространенным случаем в циклах, где вы хотите остановиться, когда все становится «достаточно близко», но когда вы не ожидаете, что они когда-либо точно сходятся.
BCS 10.12.2008 04:06:52

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

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

0
21.08.2008 22:11:27

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

Возможно, я должен объяснить проблему лучше. В 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
10.12.2008 04:07:58
Вы почти всегда получите a! = B из-за точности с плавающей точкой и ошибок округления.
Jim Kramer 1.06.2009 18:11:00

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

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

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

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

0
21.08.2008 22:51:20

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

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

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

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

1
21.08.2008 22:57:00

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

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

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

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

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

0
21.08.2008 23:27:11

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

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

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

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

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

0
9.10.2008 14:07:07

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

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

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

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

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

PS:

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

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

1
11.06.2012 07:33:11