2009-06-30 7 views
11

¿Es posible calcular pow (10, x) en tiempo de compilación?¿Puedo calcular pow (10, x) en tiempo de compilación en c?

Tengo un procesador sin soporte de punto flotante y división de entero lento. Estoy tratando de realizar tantos cálculos como sea posible en tiempo de compilación. Puedo acelerar drásticamente una función en particular si paso tanto x como C/pow(10,x) como argumentos (xy C son siempre enteros constantes, pero son constantes diferentes para cada llamada). Me pregunto si puedo hacer que estas llamadas a funciones sean menos propensas a errores al introducir una macro que hace el 1/pow(10,x) automáticamente, en lugar de forzar al programador a calcularlo.

¿Hay un truco previo al procesador? ¿Puedo forzar al compilador a optimizar la llamada a la biblioteca?

+0

Creo que he visto pruebas de que el preprocesador C está completo (creo que fue una máquina de cintas implementada en un concurso de C ofuscado en el preprocesador). Así que hay una manera. Sin embargo, no sé qué es ese camino. –

+0

Las definiciones de preprocesador # no pueden ser recursivas, ya que son solo reemplazos de texto. Así que, al igual que Greg, aquí hay un lugar donde NO debes perder el tiempo buscando. :) –

+0

@Greg D: Sin embargo, comenzar con una máquina de Turing e implementar un exponente de 10 funciones me parece ambicioso. –

Respuesta

8

Puede usar la notación científica para valores de coma flotante que es parte del lenguaje C.Se ve así:

e = 1.602E-19 // == 1.602 * pow(10, -19) 

El número antes del E (tal vez la capital de E o pequeño 1.602e-19) es la parte de fracción en tanto que la secuencia (firmado) dígito después del E es la parte del exponente. Por defecto, el número es del tipo double, pero puede adjuntar un sufijo de coma flotante (f, F, l o L) si necesita un float o un long double.

No recomendaría embalarlo semántica en una macro:

  1. no va a funcionar para las variables, los valores de punto flotante
  2. , etc. La notación científica es más fácil de leer.
+5

Aunque no lo recomiendas, esto es exactamente lo que necesito: '#define P10 (X) (1eX)', combinado con '#define fixedpt (valor, dígitos) ((valor) * (1 << 15)/P10 (dígitos)) 'me da el resultado que quería, sin depender de la configuración de optimización. – AShelly

+0

¿Qué compilador trabajó con '#define P10 (X) (1eX)'? En el compilador de Arduino, necesitaba '#define P10 (X) (1e ## X)'. – BigBobby

20

Hay muy pocos valores posibles antes de desbordar int (o incluso long). Por cuestiones de claridad, ¡conviértelo en una mesa!

editar: Si está utilizando flotadores (parece que lo es), entonces no, no será posible llamar a la función pow() en tiempo de compilación sin escribir código que se ejecute en el proceso make y muestre los valores a un archivo (como un archivo de encabezado) que luego se compila.

+1

¡Inspirado! Para poderes de una base conocida. Eso no son decimales. :) –

+0

C yx están relacionados de tal manera que no se desbordará: dado un valor v, x se elige de modo que 0.1 <= v/pow (10, x) <1, y C se establece en 32768 * v. – AShelly

+0

lo que quiso decir es 'total = (total << 1) + (total << 3)' Y la mayor parte del compilador puede hacer eso automáticamente cuando usa 'total * = 10' – leiz

1

Lamentablemente, no puede usar el preprocesador para precalcular las llamadas a la biblioteca. Si x es integral, podría escribir su propia función, pero si se trata de un tipo de punto flotante, no veo ninguna buena manera de hacerlo.

19

GCC lo hará a un nivel de optimización suficientemente alto (-O1 lo hace por mí). Por ejemplo:

#include <math.h> 

int test() { 
     double x = pow(10, 4); 
     return (int)x; 
} 

Compila en -O1 -m32 a:

 .file "test.c" 
     .text 
.globl test 
     .type test, @function 
test: 
     pushl %ebp 
     movl %esp, %ebp 
     movl $10000, %eax 
     popl %ebp 
     ret 
     .size test, .-test 
     .ident "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3" 
     .section  .note.GNU-stack,"",@progbits 

Esto funciona sin el elenco también - por supuesto, usted consigue una instrucción de carga de punto flotante allí, como el Linux ABI pasa valores de retorno de punto flotante en registros FPU.

+3

Agradable. Me pregunto cómo verifican si pow es una función pura o no en un tiempo de compilación razonable. ¿Tal vez tienen una lista de funciones conocidas que son así? – akappa

+0

es probable que algunas de las funciones matemáticas sean compiladas; además, GCC tiene un atributo '' puro '' no estándar para las funciones – Christoph

+0

'puro' no es suficiente para la optimización de la unidad de compilación cruzada de este tipo; y GCC realizará plegado constante después de la alineación si están en la misma unidad de compilación. pure es principalmente una sugerencia para el compilador de que no necesita invalidar los datos en sus registros. – bdonlan

10

Puede hacerlo con Boost.Preprocessor:

http://www.boost.org/doc/libs/1_39_0/libs/preprocessor/doc/index.html

Código:

versiones
#include <boost/preprocessor/repeat.hpp> 

#define _TIMES_10(z, n, data) * 10 
#define POW_10(n) (1 BOOST_PP_REPEAT(n, _TIMES_10, _)) 

int test[4] = {POW_10(0), POW_10(1), POW_10(2), POW_10(3)}; 
+1

desafortunadamente solo estoy usando c. – AShelly

+1

Sin embargo, el OP pidió una solución C, y Boost es una biblioteca C++ – DaveR

+5

Pero puede usarlo con C (la biblioteca Boost.Preprocessor), es solo preprocesador, lo comprobé;) Configure solo su directorio de inclusión y incluirlo! –

3

recientes de GCC (alrededor de 4,3) añaden la capacidad de utilizar GMP y MPFR para hacer algunas optimizaciones en tiempo de compilación mediante la evaluación de funciones más complejas que son constantes. Ese enfoque deja su código simple y portátil, y confía en que el compilador haga el trabajo pesado.

Por supuesto, existen límites a lo que puede hacer. Here's a link to the description in the changelog, que incluye una lista de funciones que son compatibles con esto. 'pow' es uno de ellos.

0

La repetición de bdonlan es perfecta, pero tenga en cuenta que puede realizar casi cualquier optimización que elija en el cuadro de compilación siempre que esté dispuesto a analizar y analizar el código en su propio preprocesador personalizado. Es una tarea trivial en la mayoría de las versiones de Unix anular las reglas implícitas que llaman al compilador a llamar a un paso personalizado propio antes de que llegue al compilador.

4

En realidad, usted tiene M4 que es un preprocesador mucho más potente que los GCC. Una diferencia principal entre esos dos es que GCC no es recursiva, mientras que M4 sí lo es. Hace posible cosas como hacer aritmética en tiempo de compilación (¡y mucho más!). La muestra del código a continuación es lo que le gustaría hacer, ¿no es así? Lo hice voluminoso en una fuente de un solo archivo; pero generalmente pongo las definiciones de macro de M4 en archivos separados y ajusto mis reglas de Makefile. De esta forma, su código se mantiene alejado de las desagradables definiciones intrusivas de M4 en el código fuente de C que he hecho aquí.

$ cat foo.c 
define(M4_POW_AUX, `ifelse($2, 1, $1, `eval($1 * M4_POW_AUX($1, decr($2)))')')dnl 
define(M4_POW, `ifelse($2, 0, 1, `M4_POW_AUX($1, $2)')')dnl 

#include <stdio.h> 

int      main(void) 
{ 
    printf("2^0 = %d\n", M4_POW(2, 0)); 
    printf("2^1 = %d\n", M4_POW(2, 1)); 
    printf("2^4 = %d\n", M4_POW(2, 4)); 

    return 0; 
} 

La línea de comandos para compilar este ejemplo de código usa la capacidad de GCC y M4 para leer desde la entrada estándar.

$ cat foo.c | m4 - | gcc -x c -o m4_pow - 
$ ./m4_pow 
2^0 = 1 
2^1 = 2 
2^4 = 16 

Hope this help!

5

En realidad, explotando el preprocesador C, puede hacer que calcule C pow(10, x) para cualquier real e integral x. Obsérvese que, como se señaló @quinmars, C le permite utilizar la sintaxis científica para expresar constantes numéricas:

#define myexp 1.602E-19 // == 1.602 * pow(10, -19) 

que se utilizarán para las constantes. Con esto en mente, y un poco de ingenio, podemos construir una macro preprocesador que se lleva a C y x y combinarlos en un token de exponenciación:

#define EXP2(a, b) a ## b 
#define EXP(a, b) EXP2(a ## e,b) 
#define CONSTPOW(C,x) EXP(C, x) 

Esto ahora se puede utilizar como un valor numérico constante:

const int myint = CONSTPOW(3, 4); // == 30000 
const double myfloat = CONSTPOW(M_PI, -2); // == 0.03141592653 
1

Hay only 23 different powers of 10 que puede exactamente representable en doble precisión por lo que puede simplemente usar una tabla de búsqueda

double POW10[] = {1., 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 
1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; 

Si no necesita el valor exacto pero necesita usar potencias mayores de 10, simplemente puede escribir sus propias versiones de potencias de 10 que utilizan la tabla de búsqueda anterior para obtener rápidamente el resultado sin necesidad de multiplicar por 10 nuevamente y otra vez.

double pow10(int x) 
{ 
    if (x > 22) 
     return POW10[22]*pow10(x-22); 
    else if (x >= 0) 
     return POW10[x]; 
    else 
     return 1/pow10(-x); 
} 

Si no se necesitan exponentes negativos, se puede eliminar la rama final.

También puede reducir el tamaño de la tabla de búsqueda aún más si la memoria es una restricción. Por ejemplo, al almacenar solo potencias iguales de 10 y multiplicar por 10 cuando el exponente es impar, el tamaño de la tabla ahora es solo la mitad.

Cuestiones relacionadas