2012-02-24 17 views
16

Estoy tratando de implementar la reducción de rango como el primer paso para implementar la función senoidal.Reducción de rango Precisión pobre para punto flotante de precisión simple

estoy siguiendo el método descrito en el documento "ARGUMENT REDUCTION FOR HUGE ARGUMENTS" by K.C. NG

estoy consiguiendo error tan grande como 0.002339146 cuando se utiliza el rango de entrada de x de 0 a 20000. Mi error, evidentemente, no debería ser tan grande, y yo No estoy seguro de cómo puedo reducirlo. Noté que la magnitud del error está asociada con la magnitud theta de entrada a coseno/seno.

Pude obtener el código nearpi.c que menciona el artículo, pero no estoy seguro de cómo utilizar el código para el punto flotante de precisión simple. Si alguien está interesado, el archivo nearpi.c se puede encontrar en este enlace: nearpi.c

Aquí está mi código 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)))))) 

He editado el programa nearpi.c para que compila. Sin embargo, no estoy seguro de cómo interpretar la salida. Además, el archivo espera una entrada, que tuve que ingresar a mano, también no estoy seguro del significado de la entrada.

Aquí es el nearpi.c de trabajo:

/* 
============================================================================ 
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 reducción del rango preciso de IIRC para las funciones trigonométricas requiere mucha precisión tanto para pi como para el resto; típicamente varios cientos de bits se usan en las bibliotecas matemáticas. – janneb

+3

+1 - papel interesante (acaba de pasar el tiempo de descremada, tendremos que leerlo con más cuidado más adelante) –

+0

@starbox: He leído si antes, sí. No estoy familiarizado con hacer cálculos aritméticos de alta precisión con Matlab, así que me abstendré de sugerirles cómo hacerlo. – janneb

Respuesta

9

Teoría

primer lugar vamos a notar la diferencia utilizando la aritmética de precisión simple hace.

  1. [Ecuación 8] El valor mínimo de f puede ser más grande. Como los números de precisión doble son un superconjunto de los números de precisión simple, el single más cercano a un múltiplo de 2/pi solo puede estar más lejos que ~ 2.98e-19, por lo tanto, el número de ceros iniciales en la representación aritmética fija de f debe estar en la mayoría de los ceros a la izquierda (pero probablemente serán menos). Denote esta cantidad fdigits.

  2. [Ecuación Antes 9] En consecuencia, en lugar de 121 bits, en y debe ser precisa para fdigits + 24 (distintas de cero bits significativos en precisión simple) + 7 (bits de guarda adicional) = fdigits + 31, y por lo más 92.

  3. [Ecuación 9] "por lo tanto, junto con la anchura de exponente x 's, 2/pi debe contener 127 (exponente máxima de single) + 31 + fdigits, o 158 + fdigits y en la mayoría de 219 bits.

  4. [Subsec ción 2.5] El tamaño de A se determina por el número de ceros en x antes del punto binario (y no se ve afectada por el paso a single), mientras que el tamaño de C se determina por la ecuación Antes 9.

    • Para grande x (x> = 2^24), x tiene el siguiente aspecto: [24 bits, M ceros]. Multiplicarlo por A, cuyo tamaño es el primero M bits de 2/pi, dará como resultado un número entero (los ceros de x simplemente cambiarán todo a los enteros).
    • La elección de C a estar empezando desde el M+d poco de 2/pi dará lugar a la x*C ser producto del tamaño de como máximo d-24. En doble precisión, se elige d para ser 174 (y en lugar de 24, tenemos 53) para que el producto tenga un tamaño máximo de 121. En single, es suficiente elegir d de manera que d-24 <= 92, o más precisamente, d-24 <= fdigits+31 . Es decir, d puede elegirse como fdigits +55, o como máximo 116.
    • Como resultado, B debe tener un tamaño máximo de 116 bits.

Estamos, por tanto, quedan dos problemas:

  1. Computing fdigits. Esto implica leer ref 6 del documento vinculado y entenderlo. Puede que no sea tan fácil. :) Por lo que puedo ver, ese es el único lugar donde se usa nearpi.c.
  2. Informática B, los bits relevantes de 2/pi. Como M está limitado por debajo de 127, podemos calcular los primeros 127 + 116 bits de 2/pi sin conexión y almacenarlos en una matriz. Ver Wikipedia.
  3. Informática y=x*B. Esto implica multiplicar x por un número de 116 bits. Aquí es donde se usa la Sección 3. El tamaño de los bloques se elige como 24 porque 2 * 24 + 2 (multiplicando dos números de 24 bits y sumando 3 de esos) es menor que la precisión de double, 53 (y porque 24 divide 96). Podemos usar bloques de tamaño 11 bits para la aritmética single por razones similares.

Nota: el truco con B solo se aplica a los números cuyos exponentes son positivos (x> = 2^24).

En resumen: primero, debe resolver el problema con la precisión double. Su código Matlab tampoco funciona en double precisión (intente eliminar single y computar sin(2^53), porque su twooverpi solo tiene 53 bits significativos, no 175 (y de todos modos, no puede multiplicar directamente tales números precisos en Matlab). El esquema debe ser adaptado para trabajar con single, y de nuevo, el problema clave es representar 2/pi con precisión suficiente, y apoyar la multiplicación de números de alta precisión. Por último, cuando todo funcione, puede intentar averiguar fdigits mejor para reducir el número de bits que tiene que almacenar y multiplicar.

Afortunadamente no estoy completamente fuera - comentarios y contradicciones son bienvenidos.

Ejemplo

Como ejemplo, Calculemos sin(x) donde x = single(2^24-1), que no tiene ceros después de que los bits significativos (M = 0). Esto simplifica encontrar B, ya que B consta de los primeros 116 bits de 2/pi. Desde x tiene precisión de 24 bits y B de 116 bits, el producto

y = x * B 

tendrá 92 bits de precisión, según se requiera.

La Sección 3 en el documento vinculado describe cómo realizar este producto con suficiente precisión; el mismo algoritmo se puede usar con bloques de tamaño 11 para calcular y en nuestro caso. Siendo penoso, espero que me excusen por no hacer esto explícitamente, en lugar de confiar en la caja de herramientas de matemática simbólica de Matlab. Esta caja de herramientas nos proporciona la función vpa, que nos permite especificar la precisión de un número en dígitos decimales. Así,

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

producirá una aproximación de 2/pi de al menos 116 bits de precisión. Como vpa solo acepta números enteros para su argumento de precisión, generalmente no podemos especificar la precisión binaria de un número exactamente, por lo que usamos el siguiente mejor.

El siguiente código calcula sin(x) de acuerdo con el papel, en single precisión:

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 precisión de y se obtiene usando Mathematica, que resultó ser una herramienta mucho mejor numérico que Matlab :))

En libm

La otra respuesta a esta pregunta (que se ha suprimido desde entonces) me llevan a un implem entation en libm, que aunque funciona con números de precisión doble, sigue muy concienzudamente el documento vinculado.

Ver fichero s_sin.c para la envoltura (Tabla 2 del papel vinculada aparece como una declaración switch al final del archivo), y e_rem_pio2.c para el código de reducción argumento (de particular interés es una matriz que contiene la primera 396 hex- dígitos de 2/pi, comenzando en la línea 69).

+0

Entiendo que hay problemas para resolver. Pero todavía no veo la solución basada en lo que escribiste. ¿Puedes completar los espacios en blanco que dejaste, por favor? Gracias por tu tiempo. – Veridian

+0

Se agregaron algunas aclaraciones. – user1071136

+0

mis entradas probablemente siempre estarán en el rango +/- 20000 (también se incluirán pequeñas entradas), pero mis entradas x siempre serán mucho menores que 2^24. – Veridian

Cuestiones relacionadas