2013-02-15 13 views
15

Ho una situazione in cui alcuni risultati numerici (che coinvolgono l'aritmetica in virgola mobile con double e float) non sono corretti per le dimensioni di input grandi, ma non per quelle piccole.Diagnosi di overflow in virgola mobile in programmi C++

In generale, vorrei sapere quali strumenti sono disponibili per diagnosticare condizioni quali overflow numerici e perdita di precisione problematica.

In altre parole: esiste uno strumento che si lamenta di overflow ecc. Allo stesso modo in cui valgrind si lamenta degli errori di memoria?

+1

@LihO Se potesse fare questo allora potrei anche rispondere alla mia domanda me stesso. Sarò felice di descrivere ciò che ha funzionato per me una volta che ho raccolto alcuni suggerimenti. In questo momento, non so da dove cominciare a cercare, o se un tale strumento può persino esistere. – clstaudt

+0

Aritmetica in intero o in virgola mobile? Non è probabile che si raggiunga una condizione di overflow in virgola mobile a precisione doppia, poiché ciò si verifica quando si superano circa 308 cifre decimali (quando il numero è troppo grande per rappresentare l'esponente). Molto probabilmente hai a che fare con una perdita di precisione. – amdn

+1

Per rispondere alla tua domanda abbiamo bisogno di sapere che cosa si intende per "risultati numerici diventano non corretta per i grandi formati di input" – amdn

risposta

8

Se si abilitano le eccezioni in virgola mobile, la FPU può generare un'eccezione in caso di overflow. Come esattamente questo funziona dipende dal sistema operativo. Per esempio:

  • In Windows, è possibile utilizzare _control87 smascherare _EM_OVERFLOW in modo che si otterrà un'eccezione C++ in caso di overflow.
  • Su Linux, è possibile utilizzare feenableexcept per abilitare le eccezioni su FE_OVERFLOW in modo da ottenere un valore SIGFPE in overflow. Ad esempio, per abilitare tutte le eccezioni, chiama feenableexcept(FE_ALL_EXCEPT) nel tuo main. Per abilitare l'overflow e dividere per zero, chiamare .

Nota che, in tutti i casi, il codice di terze parti può disabilitare le eccezioni che hai attivato; questo è probabilmente raro in pratica.

Questo è probabilmente non è così bello come Valgrind, dal momento che è più di una goccia-a-debugger-e-manuale-ispezionare di quello che è un get-un-bel-sintesi-at-the-end, ma lavori.

+0

Linux è rilevante per me. Vorrei abilitare tutte queste eccezioni: 'divide per zero',' overflow', 'underflow',' inexact', 'invalid'. Non ho familiarità con questo tipo di funzioni C. Mi piace chiamare 'feenableexcept (?)' Nel mio 'main'? Qual è l'argomento corretto? – clstaudt

+0

@cls - Ho aggiunto alcuni esempi. Grazie. –

+0

I '#include ' e quindi 'std :: feenableexcept (FE_ALL_EXCEPT);', ma ottengo 'errore: 'feenableexcept' non è un membro di 'std''. Si noti che non viene visualizzato un errore per 'std :: feclearexcept (FE_ALL_EXCEPT);'. Cosa c'è che non va qui? – clstaudt

3

Per diagnosticare l'overflow, è possibile utilizzare le eccezioni in virgola mobile. Vedi ad esempio cppreference. Si noti che potrebbe essere necessario utilizzare le funzioni specifiche dell'implementazione per configurare il comportamento degli errori in virgola mobile.

Si noti che anche se vengono spesso denominate "eccezioni", gli errori in virgola mobile non causano eccezioni C++.

Il codice cppreference mostra quale dovrebbe essere il comportamento predefinito per le implementazioni basate su IEEE 754: è possibile controllare i flag di eccezione in virgola mobile ogni volta che lo si ritiene opportuno. Dovresti cancellare i flag prima di inserire il tuo calcolo. Potresti voler aspettare fino a quando il tuo calcolo non è visibile, se ha impostato dei flag o potresti voler controllare ogni singola operazione sospettata di essere suscettibile di errore.

Ci possono essere estensioni specifiche dell'implementazione per far sì che tali "eccezioni" attivino qualcosa che non si può ignorare. Su Windows/MSVC++ potrebbe essere una 'eccezione strutturata' (non reale in C++), su Linux che potrebbe essere SIGFPE (quindi è necessario un gestore di segnale per gestire gli errori). Avrai bisogno di funzioni di libreria specifiche dell'implementazione o anche di compilatori/linker per abilitare tale comportamento.

Suppongo ancora che l'overflow non sia il problema. Se alcuni dei tuoi input diventano grandi mentre altri valori rimangono piccoli, è probabile che perderti con precisione quando li combini. Un modo per controllare è usare l'aritmetica dell'intervallo. Ci sono varie librerie per questo, tra cui boost interval.

Disclaimer: Non ho esperienza con questa libreria (né altre librerie aritmetiche a intervalli), ma forse questo può farti iniziare.

+0

Qual è il codice che devo aggiungere per abilitare tutte queste eccezioni in virgola mobile nel mio programma? Non riesci a trovare l'esempio di codice su cppreference molto utile. – clstaudt

+0

Vedere la risposta di @Josh Kelley. Dipende dalla piattaforma, dal compilatore, dalla biblioteca. Diteci quale piattaforma/implementazione utilizzate - o meglio ancora RTFM della vostra implementazione. Su Linux, di solito hai una pagina man. – JoergB

1

Forse avete bisogno di eseguire il debug di un'implementazione di un algoritmo in cui si può avere commesso un errore di codifica e vogliono tracciare i calcoli in virgola mobile in corso. Forse hai bisogno di un gancio per ispezionare tutti i valori su cui si sta operando, cercando i valori che sembrano fuori dalla portata che ti aspetti. In C++ puoi definire la tua classe floating point e utilizzare l'overloading dell'operatore per scrivere i tuoi calcoli in modo naturale, pur mantenendo la possibilità di ispezionare tutti i calcoli.

Ad esempio, ecco un programma che definisce una classe FP e stampa tutte le aggiunte e le moltiplicazioni.

#include <iostream> 
struct FP { 
    double value; 
    FP(double value) : value(value) {} 
}; 
std::ostream & operator<< (std::ostream &o, const FP &x) { o << x.value; return o; } 
FP operator+(const FP & lhs, const FP & rhs) { 
    FP sum(lhs.value + rhs.value); 
    std::cout << "lhs=" << lhs.value << " rhs=" << rhs.value << " sum=" << sum << std::endl; 
    return sum; 
} 
FP operator*(const FP & lhs, const FP & rhs) { 
    FP product(lhs.value * rhs.value); 
    std::cout << "lhs=" << lhs.value << " rhs=" << rhs.value << " product=" << product << std::endl; 
    return product; 
} 

int main() { 
    FP x = 2.0; 
    FP y = 3.0; 
    std::cout << "answer=" << x + 2 * y << std::endl; 
    return 0; 
} 

che stampa

lhs=2 rhs=3 product=6 
lhs=2 rhs=6 sum=8 
answer=8 

Aggiornamento: Ho migliorato il programma (x86) per mostrare le flag di stato a virgola mobile dopo ogni operazione in virgola mobile (solo implementato addizione e moltiplicazione, altri potrebbe essere facilmente aggiunto).

#include <iostream> 

struct MXCSR { 
    unsigned value; 
    enum Flags { 
     IE = 0, // Invalid Operation Flag 
     DE = 1, // Denormal Flag 
     ZE = 2, // Divide By Zero Flag 
     OE = 3, // Overflow Flag 
     UE = 4, // Underflow Flag 
     PE = 5, // Precision Flag 
    }; 
}; 
std::ostream & operator<< (std::ostream &o, const MXCSR &x) { 
    if (x.value & (1<<MXCSR::IE)) o << " Invalid"; 
    if (x.value & (1<<MXCSR::DE)) o << " Denormal"; 
    if (x.value & (1<<MXCSR::ZE)) o << " Divide-by-Zero"; 
    if (x.value & (1<<MXCSR::OE)) o << " Overflow"; 
    if (x.value & (1<<MXCSR::UE)) o << " Underflow"; 
    if (x.value & (1<<MXCSR::PE)) o << " Precision"; 
    return o; 
} 

struct FP { 
    double value; 
    FP(double value) : value(value) {} 
}; 
std::ostream & operator<< (std::ostream &o, const FP &x) { o << x.value; return o; } 
FP operator+(const FP & lhs, const FP & rhs) { 
    FP sum(lhs.value); 
    MXCSR mxcsr, new_mxcsr; 
    asm ("movsd %0, %%xmm0 \n\t" 
      "addsd %3, %%xmm0 \n\t" 
      "movsd %%xmm0, %0 \n\t" 
      "stmxcsr %1 \n\t" 
      "stmxcsr %2 \n\t" 
      "andl $0xffffffc0,%2 \n\t" 
      "ldmxcsr %2 \n\t" 
      : "=m" (sum.value), "=m" (mxcsr.value), "=m" (new_mxcsr.value) 
      : "m" (rhs.value) 
      : "xmm0", "cc"); 

    std::cout << "lhs=" << lhs.value 
       << " rhs=" << rhs.value 
       << " sum=" << sum 
       << mxcsr 
       << std::endl; 
    return sum; 
} 
FP operator*(const FP & lhs, const FP & rhs) { 
    FP product(lhs.value); 
    MXCSR mxcsr, new_mxcsr; 
    asm ("movsd %0, %%xmm0 \n\t" 
      "mulsd %3, %%xmm0 \n\t" 
      "movsd %%xmm0, %0 \n\t" 
      "stmxcsr %1 \n\t" 
      "stmxcsr %2 \n\t" 
      "andl $0xffffffc0,%2 \n\t" 
      "ldmxcsr %2 \n\t" 
      : "=m" (product.value), "=m" (mxcsr.value), "=m" (new_mxcsr.value) 
      : "m" (rhs.value) 
      : "xmm0", "cc"); 

    std::cout << "lhs=" << lhs.value 
       << " rhs=" << rhs.value 
       << " product=" << product 
       << mxcsr 
       << std::endl; 
    return product; 
} 

int main() { 
    FP x = 2.0; 
    FP y = 3.9; 
    std::cout << "answer=" << x + 2.1 * y << std::endl; 
    std::cout << "answer=" << x + 2 * x << std::endl; 
    FP z = 1; 
    for(int i=0; i<310; ++i) { 
     std::cout << "i=" << i << " z=" << z << std::endl; 
     z = 10 * z; 
    } 

    return 0; 
} 

L'ultimo ciclo moltiplica un numero per 10 volte sufficiente a mostrare troppo pieno accadere. Noterai anche errori di precisione. Termina con il valore che è infinito una volta che trabocca.

Ecco la coda dell'uscita

lhs=10 rhs=1e+305 product=1e+306 Precision 
i=306 z=1e+306 
lhs=10 rhs=1e+306 product=1e+307 
i=307 z=1e+307 
lhs=10 rhs=1e+307 product=1e+308 Precision 
i=308 z=1e+308 
lhs=10 rhs=1e+308 product=inf Overflow Precision 
i=309 z=inf 
lhs=10 rhs=inf product=inf 
1

Oltre ai suggerimenti eccellenti già postato, qui è un altro approccio. Scrivi una funzione che esamina le strutture dei dati in virgola mobile, eseguendo controlli di intervallo e di coerenza. Inserisci le chiamate nel tuo ciclo principale. Per esaminare altre variabili, è possibile impostare un punto di interruzione nel correttore dopo aver rilevato un problema.

questo è più di set-up il lavoro di eccezioni che permettono, ma può raccogliere problemi più sottili come le incongruenze e numeri che sono più grandi del previsto, senza essere andati infinita, che porta alla rivelazione assieme al problema originale.

Problemi correlati