Правильное сравнение значений типа double с использованием псевдонимов с целочисленными представлениями и ULP

Я пытался избежать эпсилон-сравнений для сравнения типов с плавающей запятой. Лучшее решение, которое я мог придумать, использовало разницу в ULP (единица в последнем месте), хотя Эта статья было гораздо лучшее решение с использованием целочисленных представлений (/// указать мои собственные комментарии):

/* See
Tricks With the Floating-Point Format
for the potential portability problems with the union and bit-fields below.
*/

#include <stdint.h> // For int32_t, etc.

union Float_t
{
Float_t(float num = 0.0f) : f(num) {}
// Portable extraction of components.
bool Negative() const { return i < 0; }
int32_t RawMantissa() const { return i & ((1 << 23) - 1); }
int32_t RawExponent() const { return (i >> 23) & 0xFF; }

int32_t i; /// Perhaps overflow when using doubles?
float f;

#ifdef _DEBUG
struct
{   // Bitfields for exploration. Do not use in production code.
uint32_t mantissa : 23; /// 52 for double?
uint32_t exponent : 8; /// 11 for double?
uint32_t sign : 1;
} parts;
#endif
};

bool AlmostEqualUlps(float A, float B, int maxUlpsDiff)
{
Float_t uA(A);
Float_t uB(B);

// Different signs means they do not match.
if (uA.Negative() != uB.Negative())
{
// Check for equality to make sure +0==-0
if (A == B)
return true;
return false;
}

// Find the difference in ULPs.
int ulpsDiff = abs(uA.i - uB.i);
if (ulpsDiff <= maxUlpsDiff)
return true;

return false;
}

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

Кто-нибудь знает, что будет лучшим способом решения этой проблемы?


Прежде чем кто-либо решит пометить это как дубликат: не делайте, потому что единственный подобный вопрос был предназначен для javascript, а ответ C ++ был:

bool IsAlmostEqual(double A, double B)
{
//http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
long long aInt = reinterpret_cast<long long&>(A);
if (aInt < 0) aInt = -9223372036854775808LL - aInt;
long long bInt = reinterpret_cast<long long&>(B);
if (bInt < 0) bInt = -9223372036854775808LL - bInt;
return (std::abs(aInt - bInt) <= 10000);
}

Который не использует ULP, но какая-то связь, и я не уверен, что -9223372036854775808LL - aInt есть у всех (возможно там, где переполняется int64).

1

Решение

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

int main()
{
Float_t x;
Float_t y;
x.i = 0x7F800000;
y.i = 0x7F7FFFFF;
AlmostEqualUlps(x.f, y.f, 1); // return true
}

Тем не мение, x.f на самом деле бесконечность и y.f является FLT_MAX, Разница по любому определению — бесконечность. Если это не ваше предполагаемое поведение, то есть считать, что конечное число и бесконечность почти равны. Ваша реализация ULP полностью отклонена от нормы. На самом деле для двух чисел, указанных выше, ULP даже не имеет четкого определения.

Другим примером будет 0x7F800000 (или любое число, близкое к этому, в зависимости от вашего maxULP) и 0x7F800001 (NaN). В отличие от приведенного выше примера, нет даже аргумента в пользу того, что их следует считать «почти равными».

С другой стороны, вы отклоняете любые пары с разными знаками как недостаточно близкие, в то время как на самом деле между -FLT_MIN а также FLT_MIN, который можно считать «почти равным». Например, 0x80000001 и 0x1 отличаются на 2ULP, но если вы установите maxULP 2 в вашей функции, он вернет false.

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

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]