Pytanie Jak wykryć przepełnienie i niedomiar podwójnej precyzji?


Mam następujące zmienne:

double dblVar1;
double dblVar2;

Mogą mieć duże wartości, ale mniej niż double maks.

Mam różne operacje arytmetyczne na powyższych zmiennych, takich jak dodawanie, mnożenie i moc:

double dblVar3 = dblVar1 * dblVar2; 
double dblVar4 = dblVar1 + dblVar2;
double dblVar5 = pow(dblVar1, 2);

W tym wszystkim muszę sprawdzić przepełnienie i niedopełnienie. Jak mogę to osiągnąć w C ++?


19
2018-03-27 09:06


pochodzenie


możliwy duplikat stackoverflow.com/questions/2399269/... - Syjin
@JimBalter double / int nie ma znaczenia w tym przypadku ... - Syjin
@Syjin: to na pewno robi różnicę! - Oliver Charlesworth
@OliCharlesworth Proszę wyjaśnić, dlaczego. Myślałem, że w tym przypadku nie ma to znaczenia. Ale może jestem całkowicie w błędzie. - Syjin
@Syjin: ponieważ liczby całkowite (zazwyczaj) wyświetlają zachowanie zawijania w przepełnieniu, zmiennoprzecinkowe nasycenia w nieskończoności. A liczby całkowite nie mają niedomiarów. - Oliver Charlesworth


Odpowiedzi:


Wiele zależy od kontekstu. Aby być całkowicie przenośnym, musisz czek przed operacja, np. (dla dodania):

if ( (a < 0.0) == (b < 0.0)
    && std::abs( b ) > std::numeric_limits<double>::max() - std::abs( a ) ) {
    //  Addition would overflow...
}

Podobną logikę można zastosować w przypadku czterech podstawowych operatorów.

Jeśli wszystkie komputery, na które celujesz, obsługują IEEE (czyli prawdopodobnie w przypadku, gdy nie musisz brać pod uwagę komputerów typu mainframe), ty mogę po prostu wykonać operacje, a następnie użyć isfinite lub isinf na wyniki.

W przypadku niedopełnienia pierwsze pytanie dotyczy tego, czy stopniowe niedopełnienie liczy się jako underflow lub nie. Jeśli nie, po prostu sprawdź, czy wyniki są zerowe i a != -b zrobiłoby to. Jeśli chcesz wykrycie stopniowego niedomiaru (który jest prawdopodobnie obecny tylko wtedy, gdy masz IEEE), wtedy możesz użyć isnormal-To będzie return false, jeśli wyniki odpowiadają stopniowemu niedopełnieniu. (W przeciwieństwie do przepełnienia, testujesz niedomiar po operacja.)


11
2018-03-27 09:36



@ James co jeśli a> 0,0 i b> 0,0, w jaki sposób sprawdzamy tutaj przepełnienie - venkysmarty
@venkysmarty Z wyrażeniem, które napisałem. Działa we wszystkich przypadkach (do dodania). - James Kanze
isnormal(5.0 - 5.0) jest fałszywe, dzięki czemu wygląda na to, że w odejmowaniu jest niedomiar. Nie jestem pewien, czy OP chce, aby to odejmowanie wskazywało na niedopełnienie (w rzeczywistości nie jestem pewien, co oznacza PO poprzez "sprawdzenie [dla] niedopełnienia"). - Pascal Cuoq
@PascalCuoq Wydaje mi się to dziwne. (Nie jestem też pewien, co dokładnie próbuje zrobić w odniesieniu do niedopełnienia). - James Kanze
Niedomiar zmiennoprzecinkowy nie występuje z powodu odejmowania. Ale może się zdarzyć na przykład z powodu mnożenia przez liczbę bliską zeru lub dzielenia przez liczbę daleki od zera. - David K


POSIX, C99, C ++ 11 mają <fenv.h> (i <cfenv> dla C ++ 11), które mają funkcje testowania flag wyjątków IEEE754 (które nie mają nic wspólnego z wyjątkami C ++, byłoby to zbyt łatwe):

int  feclearexcept(int);
int  fegetexceptflag(fexcept_t *, int);
int  feraiseexcept(int);
int  fesetexceptflag(const fexcept_t *, int);
int  fetestexcept(int);

Flaga jest bitfield z określonymi bitami:

FE_DIVBYZERO
FE_INEXACT
FE_INVALID
FE_OVERFLOW
FE_UNDERFLOW

Możesz więc wyczyścić je przed operacjami, a następnie je przetestować. Będziesz musiał sprawdzić dokumentację pod kątem działania funkcji bibliotecznych na nich.


9
2018-03-27 09:43



Makra określające pola bitów są tylko warunkowo obsługiwane; nie wszystkie systemy będą je miały. (I nie mogę znaleźć w standardzie, gdzie określa znaczenie FF_UNDERFLOW. Czy w IEEE zawiera ona stopniowy niedopełnienie czy nie?) - James Kanze
@JamesKanze IEEE-754 obsługuje niezormowane wartości (czyli stopniowe niedopełnienie). - Alexey Frunze
@JamesKanze, jest zdefiniowany w IEEE 754. Z pamięci pozwala implementacji wybrać między kilkoma semantykami, ale ISTR, że dla wszystkich z nich FE_UNDERFLOW jest generowany podczas generowania denormałów, jeśli wynik nie jest dokładny (jeśli się nie mylę, lenience polega na wykryciu sytuacji przed lub po zaokrągleniu). - AProgrammer
@AProgrammer Tak. I FF_UNDERFLOW definiuje się po prostu odzwierciedlając to, co robi hardward ("wyjątek" w sensie zastosowanym w standardzie IEEE), więc powinieneś go otrzymać, gdy pojawi się stopniowe niedopełnianie. (Czy to, co jest pożądane, czy nie, to kolejne pytanie.) - James Kanze
@AProgrammer - tylko drobna gadka: najnowsza wersja IEEE 754 zastąpiła "denormal" słowem "subnormal". - Pete Becker


Z przyzwoitym kompilatorem (który obsługuje najnowszy standard C ++), możesz użyć te Funkcje:

#include <cfenv>
#include <iostream>

int main() {
    std::feclearexcept(FE_OVERFLOW);
    std::feclearexcept(FE_UNDERFLOW);

    double overflowing_var = 1000;
    double underflowing_var = 0.01;

    std::cout << "Overflow flag before: " << (bool)std::fetestexcept(FE_OVERFLOW) << std::endl;
    std::cout << "Underflow flag before: " << (bool)std::fetestexcept(FE_UNDERFLOW) << std::endl;

    for(int i = 0; i < 20; ++i) {
        overflowing_var *= overflowing_var;
        underflowing_var *= underflowing_var;
    }

    std::cout << "Overflow flag after: " << (bool)std::fetestexcept(FE_OVERFLOW) << std::endl;
    std::cout << "Underflow flag after: " << (bool)std::fetestexcept(FE_UNDERFLOW) << std::endl;
}

/** Output:
  Overflow flag before: 0
  Underflow flag before: 0
  Overflow flag after: 1
  Underflow flag after: 1
 */

5
2018-03-27 09:52



@ ulidtko powyżej kodu nie skompilować na VS2008, ponieważ otrzymuję nie można otworzyć pliku cfenv błąd. Jak mogę przezwyciężyć tę sytuację. Dzięki - venkysmarty
@venkysmarty możesz rozwiązać ten problem, aktualizując swój kompilator. - ulidtko


ISO C99 definiuje funkcje do zapytania i manipulowania słowem statusowym zmiennoprzecinkowym. Możesz użyć tych funkcji, aby sprawdzić niewyrównane wyjątki, gdy jest to wygodne, zamiast martwić się nimi w trakcie obliczeń.

To zapewnia

FE_INEXACT
FE_DIVBYZERO
FE_UNDERFLOW
FE_OVERFLOW
FE_INVALID

Na przykład

   {
       double f;
       int raised;
       feclearexcept (FE_ALL_EXCEPT);
       f = compute ();
       raised = fetestexcept (FE_OVERFLOW | FE_INVALID);
       if (raised & FE_OVERFLOW) { /* ... */ }
       if (raised & FE_INVALID) { /* ... */ }
       /* ... */
     }

http://www.gnu.org/software/libc/manual/html_node/Status-bit-operations.html


4
2018-03-27 09:46