2012-02-24 9 views
16

Sto cercando di implementare la riduzione del campo come primo passo per l'implementazione della funzione seno.Precisione scarsa della riduzione per la virgola mobile a precisione singola

sto seguendo il metodo descritto nel documento "ARGUMENT REDUCTION FOR HUGE ARGUMENTS" by K.C. NG

sto ottenendo errore grande come ,002,339146 millions quando si utilizza la gamma di ingresso di x da 0 a 20000. Il mio errore, ovviamente, non dovrebbe essere così grande, e io Non sono sicuro di come posso ridurlo. Ho notato che l'ampiezza dell'errore è associata all'ampiezza theta in ingresso a coseno/seno.

Sono stato in grado di ottenere il codice nearpi.c che la carta menziona, ma non sono sicuro di come utilizzare il codice per la virgola mobile a precisione singola. Se qualcuno è interessato, il file nearpi.c si possono trovare a questo link: nearpi.c

Ecco il mio codice MATLAB:

x = 0:0.1:20000; 

% Perform range reduction 
% Store constant 2/pi 
twooverpi = single(2/pi); 

% Compute y 
y = (x.*twooverpi); 

% Compute k (round to nearest integer 
k = round(y); 

% Solve for f 
f = single(y-k); 

% Solve for r 
r = single(f*single(pi/2)); 

% Find last two bits of k 
n = bitand(fi(k,1,32,0),fi(3,1,32,0)); 
n = single(n); 

% Preallocate for speed 
z(length(x)) = 0; 
for i = 1:length(x) 

    switch(n(i)) 
     case 0 
      z(i)=sin(r(i)); 
     case 1 
      z(i) = single(cos(r(i))); 
     case 2 
      z(i) = -sin(r(i)); 
     case 3 
      z(i) = single(-cos(r(i))); 
     otherwise 
    end 

end 

maxerror = max(abs(single(z - single(sin(single(x)))))) 
minerror = min(abs(single(z - single(sin(single(x)))))) 

Ho modificato il nearpi.c programma in modo che si compila. Tuttavia non sono sicuro di come interpretare l'output. Anche il file si aspetta un input, che ho dovuto inserire a mano, inoltre non sono sicuro del significato dell'input.

Ecco la nearpi.c di lavoro:

/* 
============================================================================ 
Name  : nearpi.c 
Author  : 
Version  : 
Copyright : Your copyright notice 
Description : Hello World in C, Ansi-style 
============================================================================ 
*/ 

#include <stdio.h> 
#include <stdlib.h> 
#include <math.h> 


/* 
* Global macro definitions. 
*/ 

# define hex(double) *(1 + ((long *) &double)), *((long *) &double) 
# define sgn(a)   (a >= 0 ? 1 : -1) 
# define MAX_k   2500 
# define D    56 
# define MAX_EXP  127 
# define THRESHOLD  2.22e-16 

/* 
* Global Variables 
*/ 

int  CFlength,    /* length of CF including terminator */ 
     binade; 
double e, 
     f;      /* [e,f] range of D-bit unsigned int of f; 
            form 1X...X */ 

// Function Prototypes 
int dbleCF (double i[], double j[]); 
void input (double i[]); 
void nearPiOver2 (double i[]); 


/* 
* This is the start of the main program. 
*/ 

int main (void) 
{ 
    int  k;     /* subscript variable */ 
    double i[MAX_k], 
      j[MAX_k];   /* i and j are continued fractions 
            (coeffs) */ 


    // fp = fopen("/src/cfpi.txt", "r"); 


/* 
* Compute global variables e and f, where 
* 
*  e = 2^(D-1), i.e. the D bit number 10...0 
* and 
*  f = 2^D - 1, i.e. the D bit number 11...1 . 
*/ 

    e = 1; 
    for (k = 2; k <= D; k = k + 1) 
     e = 2 * e; 
    f = 2 * e - 1; 

/* 
    * Compute the continued fraction for (2/e)/(pi/2) , i.e. 
    * q's starting value for the first binade, given the continued 
    * fraction for pi as input; set the global variable CFlength 
    * to the length of the resulting continued fraction (including 
    * its negative valued terminator). One should use as many 
    * partial coefficients of pi as necessary to resolve numbers 
    * of the width of the underflow plus the overflow threshold. 
    * A rule of thumb is 0.97 partial coefficients are generated 
    * for every decimal digit of pi . 
    * 
    * Note: for radix B machines, subroutine input should compute 
    * the continued fraction for (B/e)/(pi/2) where e = B^(D - 1). 
    */ 

    input (i); 

/* 
* Begin main loop over all binades: 
* For each binade, find the nearest multiples of pi/2 in that binade. 
* 
* [ Note: for hexadecimal machines (B = 16), the rest of the main 
* program simplifies(!) to 
* 
*      B_ade = 1; 
*      while (B_ade < MAX_EXP) 
*      { 
*       dbleCF (i, j); 
*       dbleCF (j, i); 
*       dbleCF (i, j); 
*       CFlength = dbleCF (j, i); 
*       B_ade = B_ade + 1; 
*      } 
*     } 
* 
* because the alternation of source & destination are no longer necessary. ] 
*/ 

    binade = 1; 
    while (binade < MAX_EXP) 
    { 

/* 
* For the current (odd) binade, find the nearest multiples of pi/2. 
*/ 

     nearPiOver2 (i); 

/* 
* Double the continued fraction to get to the next (even) binade. 
* To save copying arrays, i and j will alternate as the source 
* and destination for the continued fractions. 
*/ 

     CFlength = dbleCF (i, j); 
     binade = binade + 1; 

/* 
* Check for main loop termination again because of the 
* alternation. 
*/ 

     if (binade >= MAX_EXP) 
      break; 

/* 
* For the current (even) binade, find the nearest multiples of pi/2. 
*/ 

     nearPiOver2 (j); 

/* 
* Double the continued fraction to get to the next (odd) binade. 
*/ 

     CFlength = dbleCF (j, i); 
     binade = binade + 1; 
    } 

    return 0; 
}        /* end of Main Program */ 

/* 
* Subroutine DbleCF doubles a continued fraction whose partial 
* coefficients are i[] into a continued fraction j[], where both 
* arrays are of a type sufficient to do D-bit integer arithmetic. 
* 
* In my case (D = 56) , I am forced to treat integers as double 
* precision reals because my machine does not have integers of 
* sufficient width to handle D-bit integer arithmetic. 
* 
* Adapted from a Basic program written by W. Kahan. 
* 
* Algorithm based on Hurwitz's method of doubling continued 
* fractions (see Knuth Vol. 3, p.360). 
* 
* A negative value terminates the last partial quotient. 
* 
* Note: for the non-C programmers, the statement break 
* exits a loop and the statement continue skips to the next 
* case in the same loop. 
* 
* The call modf (l/2, &l0) assigns the integer portion of 
* half of L to L0. 
*/ 

int dbleCF (double i[], double j[]) 
{ 
     double k, 
        l, 
        l0, 
        j0; 
     int n, 
        m; 
    n = 1; 
    m = 0; 
    j0 = i[0] + i[0]; 
    l = i[n]; 
    while (1) 
    { 
     if (l < 0) 
     { 
      j[m] = j0; 
      break; 
     }; 
     modf (l/2, &l0); 
     l = l - l0 - l0; 
     k = i[n + 1]; 
     if (l0 > 0) 
     { 
      j[m] = j0; 
      j[m + 1] = l0; 
      j0 = 0; 
      m = m + 2; 
     }; 
     if (l == 0) { 
/* 
* Even case. 
*/ 
      if (k < 0) 
      { 
       m = m - 1; 
       break; 
      } 
      else 
      { 
       j0 = j0 + k + k; 
       n = n + 2; 
       l = i[n]; 
       continue; 
      }; 
     } 
/* 
* Odd case. 
*/ 
     if (k < 0) 
     { 
      j[m] = j0 + 2; 
      break; 
     }; 
     if (k == 0) 
     { 
      n = n + 2; 
      l = l + i[n]; 
      continue; 
     }; 
     j[m] = j0 + 1; 
     m = m + 1; 
     j0 = 1; 
     l = k - 1; 
     n = n + 1; 
     continue; 
    }; 
    m = m + 1; 
    j[m] = -99999; 
    return (m); 
} 

/* 
* Subroutine input computes the continued fraction for 
* (2/e)/(pi/2) , where e = 2^(D-1) , given pi 's 
* continued fraction as input. That is, double the continued 
* fraction of pi D-3 times and place a zero at the front. 
* 
* One should use as many partial coefficients of pi as 
* necessary to resolve numbers of the width of the underflow 
* plus the overflow threshold. A rule of thumb is 0.97 
* partial coefficients are generated for every decimal digit 
* of pi . The last coefficient of pi is terminated by a 
* negative number. 
* 
* I'll be happy to supply anyone with the partial coefficients 
* of pi . My ARPA address is [email protected] . 
* 
* I computed the partial coefficients of pi using a method of 
* Bill Gosper's. I need only compute with integers, albeit 
* large ones. After writing the program in bc and Vaxima , 
* Prof. Fateman suggested FranzLisp . To my surprise, FranzLisp 
* ran the fastest! the reason? FranzLisp's Bignum package is 
* hand coded in assembler. Also, FranzLisp can be compiled. 
* 
* 
* Note: for radix B machines, subroutine input should compute 
* the continued fraction for (B/e)/(pi/2) where e = B^(D - 1). 
* In the case of hexadecimal (B = 16), this is done by repeated 
* doubling the appropriate number of times. 
*/ 

void input (double i[]) 
{ 
    int  k; 
    double j[MAX_k]; 

/* 
* Read in the partial coefficients of pi from a precalculated file 
* until a negative value is encountered. 
*/ 

    k = -1; 
    do 
    { 
     k = k + 1; 
     scanf ("%lE", &i[k]); 
     printf("hello\n"); 
     printf("%d", k); 
    } while (i[k] >= 0); 

/* 
* Double the continued fraction for pi D-3 times using 
* i and j alternately as source and destination. On my 
* machine D = 56 so D-3 is odd; hence the following code: 
* 
* Double twice (D-3)/2 times, 
*/ 
    for (k = 1; k <= (D - 3)/2; k = k + 1) 
    { 
     dbleCF (i, j); 
     dbleCF (j, i); 
    }; 
/* 
* then double once more. 
*/ 
    dbleCF (i, j); 

/* 
* Now append a zero on the front (reciprocate the continued 
* fraction) and the return the coefficients in i . 
*/ 

    i[0] = 0; 
    k = -1; 
    do 
    { 
     k = k + 1; 
     i[k + 1] = j[k]; 
    } while (j[k] >= 0); 

/* 
* Return the length of the continued fraction, including its 
* terminator and initial zero, in the global variable CFlength. 
*/ 

    CFlength = k; 
} 

/* 
* Given a continued fraction's coefficients in an array i , 
* subroutine nearPiOver2 finds all machine representable 
* values near a integer multiple of pi/2 in the current binade. 
*/ 

void nearPiOver2 (double i[]) 
{ 
    int  k,     /* subscript for recurrences (see 
            handout) */ 
      K;     /* like k , but used during cancel. elim. 
            */ 
    double p[MAX_k],   /* product of the q's   (see 
            handout) */ 
      q[MAX_k],   /* successive tail evals of CF (see 
            handout) */ 
      j[MAX_k],   /* like convergent numerators (see 
            handout) */ 
      tmp,    /* temporary used during cancellation 
            elim. */ 
      mk0,    /* m[k - 1]      (see 
            handout) */ 
      mk,     /* m[k] is one of the few ints (see 
            handout) */ 
      mkAbs,    /* absolute value of m sub k 
           */ 
      mK0,    /* like mk0 , but used during cancel. 
            elim. */ 
      mK,     /* like mk , but used during cancel. 
            elim. */ 
      z,     /* the object of our quest (the argument) 
           */ 
      m0,     /* the mantissa of z as a D-bit integer 
           */ 
      x,     /* the reduced argument   (see 
            handout) */ 
      ldexp(),   /* sys routine to multiply by a power of 
            two */ 
      fabs(),   /* sys routine to compute FP absolute 
            value */ 
      floor(),   /* sys routine to compute greatest int <= 
            value */ 
      ceil();   /* sys routine to compute least int >= 
            value */ 

/* 
    * Compute the q's by evaluating the continued fraction from 
    * bottom up. 
    * 
    * Start evaluation with a big number in the terminator position. 
    */ 

    q[CFlength] = 1.0 + 30; 

    for (k = CFlength - 1; k >= 0; k = k - 1) 
     q[k] = i[k] + 1/q[k + 1]; 

/* 
* Let THRESHOLD be the biggest | x | that we are interesed in 
* seeing. 
* 
* Compute the p's and j's by the recurrences from the top down. 
* 
* Stop when 
* 
*  1       1 
*  ----- >= THRESHOLD > ------ . 
*  2 |j |      2 |j | 
*   k       k+1 
*/ 

    p[0] = 1; 
    j[0] = 0; 
    j[1] = 1; 
    k = 0; 
    do 
    { 
     p[k + 1] = -q[k + 1] * p[k]; 
     if (k > 0) 
      j[1 + k] = j[k - 1] - i[k] * j[k]; 
     k = k + 1; 
    } while (1/(2 * fabs (j[k])) >= THRESHOLD); 

/* 
* Then mk runs through the integers between 
* 
*     k  +     k  + 
*    (-1) e/p - 1/2  & (-1) f/p - 1/2 . 
*       k       k 
*/ 

    for (mkAbs = floor (e/fabs (p[k])); 
      mkAbs <= ceil (f/fabs (p[k])); mkAbs = mkAbs + 1) 
    { 

     mk = mkAbs * sgn (p[k]); 

/* 
* For each mk , mk0 runs through integers between 
* 
*     + 
*    m q - p THRESHOLD . 
*    k k  k 
*/ 

     for (mk0 = floor (mk * q[k] - fabs (p[k]) * THRESHOLD); 
       mk0 <= ceil (mk * q[k] + fabs (p[k]) * THRESHOLD); 
       mk0 = mk0 + 1) 
     { 

/* 
* For each pair { mk , mk0 } , check that 
* 
*        k 
*    m  = (-1) (j m - j m ) 
*    0     k-1 k k k-1 
*/ 
      m0 = (k & 1 ? -1 : 1) * (j[k - 1] * mk - j[k] * mk0); 

/* 
* lies between e and f . 
*/ 
      if (e <= fabs (m0) && fabs (m0) <= f) 
      { 

/* 
* If so, then we have found an 
* 
*        k 
*    x  = ((-1) m/p - m)/j 
*         0 k k  k 
* 
*      = (m q - m )/p . 
*       k k k-1  k 
* 
* But this later formula can suffer cancellation. Therefore, 
* run the recurrence for the mk 's to get mK with minimal 
* | mK | + | mK0 | in the hope mK is 0 . 
*/ 
       K = k; 
       mK = mk; 
       mK0 = mk0; 
       while (fabs (mK) > 0) 
       { 
        p[K + 1] = -q[K + 1] * p[K]; 
        tmp = mK0 - i[K] * mK; 
        if (fabs (tmp) > fabs (mK0)) 
         break; 
        mK0 = mK; 
        mK = tmp; 
        K = K + 1; 
       }; 

/* 
* Then 
*    x  = (m q - m )/p 
*       K K K-1  K 
* 
* as accurately as one could hope. 
*/ 
       x = (mK * q[K] - mK0)/p[K]; 

/* 
* To return z and m0 as positive numbers, 
* x must take the sign of m0 . 
*/ 
       x = x * sgn (m0); 
       m0 = fabs (m0); 

/*d 
* Set z = m0 * 2^(binade+1-D) . 
*/ 
       z = ldexp (m0, binade + 1 - D); 

/* 
* Print z (hex), z (dec), m0 (dec), binade+1-D, x (hex), x (dec). 
*/ 

       printf ("%08lx %08lx Z=%22.16E M=%17.17G L+1-%d=%3d %08lx %08lx x=%23.16E\n", hex (z), z, m0, D, binade + 1 - D, hex (x), x); 

      } 
     } 
    } 
} 
+0

La riduzione del range di precisione IIRC per le funzioni trigonometriche richiede molta precisione sia per il pi che per il resto; tipicamente diverse centinaia di bit sono usati nelle librerie matematiche. – janneb

+3

+1 - documento interessante (appena avuto tempo di sfogliare, dovrà leggerlo più attentamente in seguito) –

+0

@starbox: ho letto se prima, sì. Non ho familiarità con l'aritmetica ad alta precisione con MATLAB, quindi mi asterrò dal suggerirti come farlo. – janneb

risposta

9

Teoria

Prima di tutto notare la differenza con l'aritmetica a precisione singola fa.

  1. [Equazione 8] Il valore minimo di f può essere maggiore. Poiché i numeri a precisione doppia sono un super-set dei numeri a precisione singola, il più vicino single a un multiplo di 2/pi può essere solo più lontano quindi ~ 2.98e-19, pertanto il numero di zeri iniziali nella rappresentazione aritmetica fissa di f deve essere a la maggior parte dei 61 zeri iniziali (ma probabilmente sarà inferiore). Denotare questa quantità fdigits.

  2. [Equazione Prima 9] Di conseguenza, invece di 121 bit, y deve essere accurata per fdigits + 24 (non-zero bit significativi in ​​singola precisione) + 7 (bit di guardia aggiuntivo) = fdigits + 31, e allo più 92.

  3. [Equazione 9] "Perciò, insieme con la larghezza di esponente x s', 2/pi deve contenere 127 (esponente massimo single) + 31 + fdigits, o 158 + fdigits e al massimo 219 bit.

  4. [Sottosec zione 2.5] Le dimensioni A è determinata dal numero di zeri nel x prima del punto binario (e non è influenzato dal passaggio alla single), mentre le dimensioni del C è determinata dall'equazione Prima 9.

    • Per large x (x> = 2^24), x ha il seguente aspetto: [24 bit, M zeri]. Moltiplicandolo per A, la cui dimensione è il primo M bit di 2/pi, si otterrà un numero intero (gli zeri di x sposteranno semplicemente tutto negli interi).
    • La scelta di C per l'avvio da M+d bit di 2/pi darà come risultato il prodotto x*C di dimensioni al massimo d-24. In doppia precisione, l'opzione d viene scelta per essere 174 (e invece di 24, abbiamo 53) in modo che il prodotto sia di dimensioni al massimo 121. In single, è sufficiente scegliere d tale che d-24 <= 92 o più precisamente, d-24 <= fdigits+31 . Cioè, d può essere scelto come fdigits +55 o al massimo 116.
    • Di conseguenza, il valore di B deve essere al massimo 116 bit.

Siamo quindi partiti con due problemi:

  1. Computing fdigits. Ciò comporta la lettura del ref 6 dal documento collegato e la sua comprensione. Potrebbe non essere così facile. :) Per quanto posso vedere, questo è l'unico posto in cui viene utilizzato nearpi.c.
  2. Computing B, i bit rilevanti di 2/pi. Poiché M è limitato di seguito da 127, è possibile calcolare i primi 127 + 116 bit di 2/pi offline e memorizzarli in un array. Vedi Wikipedia.
  3. Computing y=x*B. Ciò comporta il moltiplicarsi di x con un numero di 116 bit. Questo è dove viene usata la Sezione 3. La dimensione dei blocchi viene scelta per essere 24 perché 2 * 24 + 2 (moltiplicando due numeri di 24 bit e aggiungendo 3 di tali numeri) è inferiore alla precisione di double, 53 (e perché 24 divide 96). Possiamo utilizzare blocchi di dimensione 11 bit per l'aritmetica single per motivi simili.

Nota: il trucco con B si applica solo ai numeri i cui esponenti sono positivi (x> = 2^24).

Per riassumere: in primo luogo, è necessario risolvere il problema con la precisione double. Il codice Matlab non funziona anche nella precisione double (provare a rimuovere single e calcolare sin(2^53), perché il numero twooverpi ha solo 53 bit significativi, non 175 (e comunque non è possibile moltiplicare direttamente tali numeri precisi in Matlab). lo schema deve essere adattato per funzionare con single e, ancora una volta, il problema chiave rappresenta esattamente 2/pi e supporta la moltiplicazione di numeri estremamente precisi. Infine, quando tutto funziona, è possibile provare a calcolare un numero migliore di fdigits per ridurre il numero di bit è necessario memorizzare e moltiplicare

Speriamo che non sto completamente fuori -. commenti e le contraddizioni sono i benvenuti

01.235.164,106 mila.

Esempio

Come esempio, calcoliamo sin(x) dove x = single(2^24-1), che non ha zeri dopo i bit significativi (M = 0). Questo semplifica la ricerca di B, poiché B consiste dei primi 116 bit di 2/pi. Poiché x ha precisione di 24 bit e B di 116 bit, il prodotto

y = x * B 

avrà 92 bit di precisione, come richiesto.

Nella sezione 3 del documento collegato viene descritto come eseguire questo prodotto con una precisione sufficiente; lo stesso algoritmo può essere utilizzato con blocchi di dimensione 11 per calcolare nel nostro caso. Essendo faticoso, spero di essere scusato per non farlo esplicitamente, affidandomi invece alla toolbox matematica simbolica di Matlab. Questo toolbox ci fornisce la funzione vpa, che ci consente di specificare la precisione di un numero in cifre decimali. Quindi,

vpa('2/pi', ceil(116*log10(2))) 

produrrà un'approssimazione di 2/pi di almeno 116 bit di precisione. Poiché vpa accetta solo numeri interi per il suo argomento di precisione, di solito non è possibile specificare esattamente la precisione binaria di un numero, quindi utilizziamo il valore successivo.

Il codice seguente calcola sin(x) secondo la carta, in single precisione:

x = single(2^24-1); 
y = x * vpa('2/pi', ceil(116*log10(2))); % Precision = 103.075 
k = round(y); 
f = single(y - k); 
r = f * single(pi)/2; 
switch mod(k, 4) 
    case 0 
     s = sin(r); 
    case 1 
     s = cos(r); 
    case 2 
     s = -sin(r); 
    case 3 
     s = -cos(r); 
end 
sin(x) - s         % Expected value: exactly zero. 

(La precisione di y si ottiene utilizzando Mathematica, che si è rivelato essere uno strumento molto migliore numerico di Matlab :))

In libm

L'altra risposta a questa domanda (che è stato soppresso in quanto) mi portano a un implem entazione in libm, che sebbene lavori su numeri a precisione doppia, segue molto attentamente la carta collegata.

Visualizza file s_sin.c per l'involucro (Tabella 2 dalla carta collegata appare come una dichiarazione switch alla fine del file), e e_rem_pio2.c per il codice riduzione argomento (di particolare interesse è un array contenente la prima 396 HEX cifre di 2/pi, a partire dalla riga 69).

+0

Capisco che ci sono problemi da risolvere. Ma continuo a non vedere la soluzione in base a ciò che hai scritto. Puoi riempire gli spazi vuoti che hai lasciato per favore? Grazie per il tuo tempo. – Veridian

+0

Aggiunti alcuni chiarimenti. – user1071136

+0

probabilmente i miei ingressi saranno sempre nel range +/- 20000 (anche piccoli input saranno inclusi), ma i miei input x saranno sempre molto inferiori a 2^24. – Veridian

Problemi correlati