Powtórne porównanie punktów zmiennoprzecinkowych

Ten temat pojawił się wiele razy w StackOverflow, ale wierzę, że jest to nowe podejście. Tak, przeczytałemArtykuły Bruce'a Dawsona iCo powinien wiedzieć każdy informatyk na temat arytmetyki zmiennoprzecinkowej ita miła odpowiedź.

Jak rozumiem, w typowym systemie istnieją cztery podstawowe problemy przy porównywaniu liczb zmiennoprzecinkowych dla równości:

Obliczenia zmiennoprzecinkowe nie są dokładneCzya-b jest „mały” zależy od skalia ibCzya-b jest „mały” zależy od typua ib (np. float, double, long double)Punkt zmiennopozycyjny zazwyczaj ma + -wysokość, NaN i zdenormalizowane reprezentacje, z których każda może zakłócać naiwną formułę

Ta odpowiedź -- znany jako. „podejście Google” - wydaje się popularne. Obsługuje wszystkie trudne przypadki. I bardzo precyzyjnie skaluje porównanie, sprawdzając, czy dwie wartości mieszczą się w ustalonej liczbieULP siebie nawzajem. Na przykład bardzo duża liczba porównuje „prawie równe” do nieskończoności.

Jednak:

Moim zdaniem jest bardzo brudny.Nie jest szczególnie przenośny, opierając się w dużym stopniu na wewnętrznych reprezentacjach, używając unii do odczytu bitów z pływaka itp.Obsługuje tylko jedną precyzję IEEE 754 o podwójnej precyzji (w szczególności nie ma podwójnego podwójnego x86)

Chcę czegoś podobnego, ale używając standardowego C ++ i obsługując długie deble. Przez „standard” rozumiem C ++ 03, jeśli to możliwe, i C ++ 11, jeśli to konieczne.

Oto moja próba.

#include <cmath>
#include <limits>
#include <algorithm>

namespace {
// Local version of frexp() that handles infinities specially.
template<typename T>
T my_frexp(const T num, int *exp)
{
    typedef std::numeric_limits<T> limits;

    // Treat +-infinity as +-(2^max_exponent).
    if (std::abs(num) > limits::max())
    {
        *exp = limits::max_exponent + 1;
        return std::copysign(0.5, num);
    }
    else return std::frexp(num, exp);
}
}

template<typename T>
bool almostEqual(const T a, const T b, const unsigned ulps=4)
{
    // Handle NaN.
    if (std::isnan(a) || std::isnan(b))
        return false;

    typedef std::numeric_limits<T> limits;

    // Handle very small and exactly equal values.
    if (std::abs(a-b) <= ulps * limits::denorm_min())
        return true;

    // frexp() does the wrong thing for zero.  But if we get this far
    // and either number is zero, then the other is too big, so just
    // handle that now.
    if (a == 0 || b == 0)
        return false;

    // Break the numbers into significand and exponent, sorting them by
    // exponent.
    int min_exp, max_exp;
    T min_frac = my_frexp(a, &min_exp);
    T max_frac = my_frexp(b, &max_exp);
    if (min_exp > max_exp)
    {
        std::swap(min_frac, max_frac);
        std::swap(min_exp, max_exp);
    }

    // Convert the smaller to the scale of the larger by adjusting its
    // significand.
    const T scaled_min_frac = std::ldexp(min_frac, min_exp-max_exp);

    // Since the significands are now in the same scale, and the larger
    // is in the range [0.5, 1), 1 ulp is just epsilon/2.
    return std::abs(max_frac-scaled_min_frac) <= ulps * limits::epsilon() / 2;
}

Twierdzę, że ten kod (a) obsługuje wszystkie istotne przypadki, (b) robi to samo, co implementacja Google dla IEEE-754 z pojedynczą i podwójną precyzją, i (c) jest doskonale standardowy C ++.

Jedno lub więcej z tych twierdzeń jest prawie na pewno błędne. Przyjmę każdą odpowiedź, która to pokazuje, najlepiej z poprawką. Dobra odpowiedź powinna obejmować co najmniej jeden z następujących elementów:

Konkretne dane wejściowe różniące się o więcej niżulps Jednostki w ostatnim miejscu, ale dla których ta funkcja zwraca wartość true (im większa różnica, tym lepiej)Konkretne dane wejściowe różniące się o maksymalnieulps Jednostki w ostatnim miejscu, ale dla których ta funkcja zwraca false (im mniejsza różnica, tym lepiej)Wszelkie sprawy, które przegapiłemDowolny sposób, w jaki ten kod opiera się na niezdefiniowanym zachowaniu lub przerwach w zależności od zachowania określonego przez implementację. (Jeśli to możliwe, proszę podać odpowiednią specyfikację)Rozwiązuje problemy, które identyfikujeszDowolny sposób na uproszczenie kodu bez przerywania go

Zamierzam postawić na to pytanie niebanalną nagrodę.

questionAnswers(3)

yourAnswerToTheQuestion