7

I matematyka, x <= y jest odpowiednikiem !(x > y). Dotyczy to arytmetyki zmiennoprzecinkowej, w większości przypadków, ale nie zawsze. Gdy x lub y jest NaN, x <= y jest nie równoważne !(x > y), ponieważ porównywanie NaN do niczego zawsze zwraca false. Ale nadal, x <= y <=> !(x > y) jest prawdziwe przez większość czasu.Najlepszy sposób na uniknięcie powielania kodu definiującego operatory porównania `<, <=, >,> =, ==,! =`, Ale biorąc pod uwagę NaNs?

Załóżmy teraz, że piszę klasę zawierającą wartości zmiennoprzecinkowe i chcę zdefiniować operatorów porównania dla tej klasy. W celu określenia, przypuśćmy, że piszę wysokiej precyzji liczba zmiennoprzecinkowa, która używa wewnętrznie jednej lub więcej wartości double do przechowywania wysokiej precyzji. Matematycznie definicja x < y dla tej klasy już definiuje wszystkie inne operatory (jeśli jestem w zgodzie ze zwykłą semantyką operatorów porównania). Ale NaN s przełamać tę matematyczną delikatność. Więc może jestem zmuszony napisać wielu z tych operatorów osobno, żeby wziąć pod uwagę NaN. Ale czy istnieje lepszy sposób? Moje pytanie brzmi: Jak mogę uniknąć powielania kodu w jak największym stopniu i nadal szanować zachowanie NaN?

Powiązane: http://www.boost.org/doc/libs/1_59_0/libs/utility/operators.htm. W jaki sposób funkcja boost/operatorzy rozwiązuje ten problem?

Uwaga: To pytanie zostało oznaczone jako c++, ponieważ to właśnie rozumiem. Napisz przykłady w tym języku.

+0

Powiązane: http://stackoverflow.com/a/29269216/855050. Ta odpowiedź stanowi zadowalające rozwiązanie dla typów integralnych. Ale nie działa dla zmiennoprzecinkowej z powodu NaN. – becko

Odpowiedz

0

Po zdefiniowaniu operatora < musisz zająć się sprawą NaN. Dla celów zamawiania i porównawczych, jeśli traktować NaN jako mniej niż non-NaN, można zrobić coś takiego:

bool operator<(double l, double r) { 
    if (isnan(l)) { 
     if (isnan(r)) return false; // NaN == NaN 
     return true;  // NaN < rational 
    } 
    return l < r;  // if r is NaN will return false, which is how we've defined it 
} 

Pozostali operatorzy są zdefiniowane w kategoriach operatora < i nie wymagają ręcznego kod napisany.

+0

Ale 'NaN becko

+0

Czy chcesz, aby x> Nan było fałszywe lub prawdziwe? Aby zachować symetrię z <, powinno być prawdą. – 1201ProgramAlarm

+0

@ 1201ProgramAlarm NaN 'op' jest zawsze fałszywy. –

1

Osobiście użyłbym podobnej techniki jak w this answer, która definiuje funkcję porównania opartą na operator<(), co daje ścisłą słabą kolejność. W przypadku typów z wartością zerową, która ma porównywać zawsze daje false, operacje byłyby zdefiniowane w kategoriach operator<(), zapewniając ścisłą słabą kolejność dla wszystkich wartości innych niż zero i testu is_null(). funkcja, która jest parametryzowana przez funkcję porównania i która jest odpowiedzialna za właściwe podawanie funkcji porównania z parametrami. Na przykład:

namespace compare_relational { 
    struct tag {}; 

    template <typename T> 
    bool operator== (T const& lhs, T const& rhs) { 
     return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs == rhs; }); 
    } 
    template <typename T> 
    bool operator!= (T const& lhs, T const& rhs) { 
     return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs != rhs; }); 
    } 

    template <typename T> 
    bool operator< (T const& lhs, T const& rhs) { 
     return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs < rhs; }); 
    } 
    template <typename T> 
    bool operator> (T const& lhs, T const& rhs) { 
     return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs > rhs; }); 
    } 
    template <typename T> 
    bool operator<= (T const& lhs, T const& rhs) { 
     return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs <= rhs; }); 
    } 
    template <typename T> 
    bool operator>= (T const& lhs, T const& rhs) { 
     return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs >= rhs; }); 
    } 
} 

class foo 
    : private compare_relational::tag { 
    double value; 
public: 
    foo(double value): value(value) {} 

    template <typename Compare> 
    friend bool compare(foo const& f0, foo const& f1, Compare&& predicate) { 
     return predicate(f0.value, f1.value); 
    } 
}; 

Mogę sobie wyobrazić posiadanie wielu z tych przestrzeni nazw generujących operacje, aby obsługiwać odpowiedni wybór dla typowych sytuacji. Inną opcją może być inna kolejność niż liczba zmiennoprzecinkowa i np. Wartość pusta jest najmniejsza lub największa.Ponieważ niektórzy używają boksowania NaN, rozsądnym może okazać się zamówienie różnych wartości NaN i uporządkowanie wartości NaN w odpowiednich miejscach. Na przykład użycie podstawowej reprezentacji bitów zapewnia całkowitą kolejność wartości zmiennoprzecinkowych, które mogą być odpowiednie do używania obiektów jako klucza w zamówionym kontenerze, chociaż kolejność może być inna niż kolejność utworzona przez operator<().

Powiązane problemy