2015-03-05 19 views
8

come ho detto, voglio implementare la mia funzione di doppia precisione cos() in uno shader di elaborazione con GLSL, perché c'è solo una versione integrata per float.Il risultato della propria implementazione di cos() a doppia precisione in uno shader è NaN, ma funziona bene sulla CPU. Cosa sta andando storto?

Questo è il mio codice:

double faculty[41];//values are calculated at the beginning of main() 

double myCOS(double x) 
{ 
    double sum,tempExp,sign; 
    sum = 1.0; 
    tempExp = 1.0; 
    sign = -1.0; 

    for(int i = 1; i <= 30; i++) 
    { 
     tempExp *= x; 
     if(i % 2 == 0){ 
      sum = sum + (sign * (tempExp/faculty[i])); 
      sign *= -1.0; 
     } 
    } 
return sum; 
} 

Il risultato di questo codice è, che la somma risulta essere NaN sullo shader, ma sulla CPU l'algoritmo funziona bene. Ho cercato di eseguire il debug di questo codice troppo e ho ottenuto le seguenti informazioni:

  • facoltà [i] è positivo e non pari a zero per tutte le voci
  • tempExp è positiva in ogni fase
  • nessuna delle altre variabili sono NaN in ogni fase
  • la prima somma il tempo è NaN è al passo con i = 4

e ora la mia domanda: che cosa può andare male se ogni variabile è un numero e niente è diviso da zero soprattutto quando l'algoritmo funziona sulla CPU?

+5

infinito diviso per l'infinito, ad esempio. Qual è il tuo dati di input? – Wintermute

+0

http://stackoverflow.com/a/4430934/17034 –

+0

@Wintermute Non ne sono veramente sicuro ed è difficile stimare i valori esatti in uno shader. Una parte di essa è positiva e il resto è negativo. Ma proverò a stimarli. – DanceIgel

risposta

1

Fammi indovinare:

Per prima cosa è determinato il problema è nel ciclo, e si utilizza solo le seguenti operazioni: +, *, /.

Le regole per generare NaN da queste operazioni sono:

  • Le divisioni 0/0 e ±∞/±∞
  • le moltiplicazioni 0×±∞ e ±∞×0
  • Le aggiunte ∞ + (−∞), (−∞) + ∞ e sottrazioni equivalenti

Tu reggi esclusa la possibilità per 0/0 e ±∞/±∞ affermando che faculty[] è inizializzato correttamente.

La variabile sign è sempre 1.0 o -1.0 quindi non può generare il NaN attraverso l'operazione *.

Ciò che rimane è l'operazione + se tempExp diventa ±∞.

Quindi probabilmente tempExp è troppo alta all'ingresso della vostra funzione e diventa ±∞, questo renderà sum essere ±∞ troppo. Alla successiva iterazione si attiverà l'operazione di generazione NaN tramite: ∞ + (−∞). Questo perché moltiplichi un lato dell'addizione per sign e segni gli scambi tra positivo e negativo ad ogni iterazione.

Si sta tentando di approssimare cos(x) intorno a 0.0.Pertanto, è necessario utilizzare le proprietà della funzione cos() per ridurre il valore di input ad un valore vicino a 0.0. Idealmente nell'intervallo [0, pi/4]. Ad esempio, rimuovere i multipli di 2*pi e ottenere i valori di cos() in [pi/4, pi/2] calcolando sin(x) attorno a 0.0 e così via.

0

Quello che può andare drammaticamente sbagliato è una perdita di precisione. cos(x) di solito è implementato dalla riduzione dell'intervallo seguito da un'implementazione dedicata per l'intervallo [0, pi/2]. La riduzione dell'intervallo utilizza cos(x+2*pi) = cos(x). Ma questa riduzione di gamma non è perfetta. Per i principianti, pi non può essere esattamente rappresentato in matematica finita.

Ora cosa succede se si prova qualcosa di assurdo come cos(1<<30)? È abbastanza probabile che l'algoritmo di riduzione dell'intervallo introduca un errore in x che è maggiore di 2*pi, nel qual caso il risultato è privo di significato. Restituire NaN in questi casi è ragionevole.