2009-06-30 7 views
11

Est-il possible de calculer pow (10, x) au moment de la compilation?Puis-je calculer pow (10, x) à la compilation dans c?

J'ai un processeur sans support de virgule flottante et une division entière lente. J'essaie d'effectuer autant de calculs que possible au moment de la compilation. Je peux accélérer considérablement une fonction particulière si je passe les deux x et C/pow(10,x) comme arguments (x et C sont toujours des entiers constants, mais ce sont des constantes différentes pour chaque appel). Je me demande si je peux rendre ces appels de fonction moins enclins à l'erreur en introduisant une macro qui fait le 1/pow(10,x) automatiquement, au lieu de forcer le programmeur à le calculer?

Existe-t-il une astuce de pré-processeur? Puis-je forcer le compilateur à optimiser l'appel de la bibliothèque?

+0

Je crois que j'ai vu la preuve que le préprocesseur C était complet (je pense qu'il s'agissait d'un magnétophone implémenté dans un concours C dissimulé dans le préprocesseur.) Donc, il y a un moyen. Je ne sais pas ce que c'est, cependant. –

+0

Le préprocesseur #defines ne peut pas être récursif, car il ne s'agit que de remplacements de texte. Donc, comme Greg, voici un endroit pour ne pas passer votre temps à regarder. :) –

+0

@Greg D: Cependant, en commençant par une machine de Turing et en mettant en œuvre un exposant de 10 fonctions me semble ambitieux. –

Répondre

8

Vous pouvez utiliser la notation scientifique pour les valeurs à virgule flottante qui fait partie du langage C.Il ressemble à ça:

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

Le nombre avant la E (le E peut-être capital ou petite 1.602e-19) est la partie de fraction alors que la (signé) séquence de chiffres après la E est la partie de l'exposant. Par défaut, le numéro est du type double, mais vous pouvez joindre un suffixe à virgule flottante (f, F, l ou L) si vous avez besoin d'un float ou d'un long double.

Je ne recommanderais pas d'emballer cette sémantique dans une macro:

  1. Il ne fonctionnera pas pour les variables, les valeurs à virgule flottante, etc.
  2. La notation scientifique est plus lisible.
+5

Bien que vous ne le recommandiez pas, c'est exactement ce dont j'ai besoin: '#define P10 (X) (1eX)', combiné avec '#define fixedpt (valeur, chiffres) ((value) * (1 << 15)/P10 (chiffres)) 'me donne le résultat que je voulais, sans confiance dans les paramètres d'optimisation. – AShelly

+0

Quel compilateur a travaillé avec '#define P10 (X) (1eX)'? Dans le compilateur Arduino, j'avais besoin de '#define P10 (X) (1e ## X)'. – BigBobby

20

Il y a très peu de valeurs possibles avant que vous débordiez int (ou même long). Pour des raisons de clarté, faites-en une table! Edit: Si vous utilisez des floats (vous ressemblez), alors il ne sera pas possible d'appeler la fonction pow() au moment de la compilation sans écrire du code qui s'exécute dans le processus make et qui sort les valeurs à un fichier (tel qu'un fichier d'en-tête) qui est ensuite compilé.

+1

Inspiré! Pour les pouvoirs d'une base connue. Ce ne sont pas des nombres décimaux. :) –

+0

C et x sont liés de telle sorte que nous ne débordons pas: étant donné une valeur v, x est choisi de sorte que 0,1 <= v/pow (10, x) <1, et C est réglé sur 32768 * v. – AShelly

+0

ce que vous vouliez dire est 'total = (total << 1) + (total << 3)' Et la plupart du compilateur peut le faire automatiquement lorsque vous utilisez 'total * = 10' – leiz

1

Malheureusement, vous ne pouvez pas utiliser le préprocesseur pour précalculer les appels de bibliothèque. Si x est intégral, vous pouvez écrire votre propre fonction, mais si c'est un type à virgule flottante, je ne vois pas de bon moyen de le faire.

19

GCC le fera à un niveau d'optimisation suffisamment élevé (-O1 le fait pour moi). Par exemple:

#include <math.h> 

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

Compile à -O1 -m32 à:

 .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 

Cela fonctionne sans le casting aussi bien - bien sûr, vous obtenez une instruction de charge à virgule flottante là-dedans, comme Linux ABI transmet les valeurs de retour à virgule flottante dans les registres FPU.

+3

Nice. Je me demande comment ils vérifient si pow est une pure fonction ou non dans un temps de compilation raisonnable. Peut-être qu'ils ont une liste de fonctions connues qui sont ainsi? – akappa

+0

certaines fonctions mathématiques sont probablement intégrées au compilateur; aussi, GCC a un attribut 'pure' non-standard pour les fonctions – Christoph

+0

'pure' n'est pas suffisant pour l'optimisation d'unité de compilation croisée de ce genre; et GCC effectuera un pliage constant après l'insertion si elles sont dans la même unité de compilation. pure est la plupart du temps juste un indice pour le compilateur qu'il n'a pas besoin d'invalider les données dans ses registres. – bdonlan

10

Vous pouvez le faire avec Boost.Preprocessor:

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

code:

#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

malheureusement, je n'utilise que c. – AShelly

+1

Toutefois, l'OP a demandé une solution C, et Boost est une bibliothèque C++ – DaveR

+5

Mais vous pouvez l'utiliser avec C (la bibliothèque Boost.Preprocessor), il est préprocesseur seulement, je l'ai vérifié;) Configurez uniquement votre répertoire include et incluez-le! –

3

versions récentes de GCC (environ 4,3) a ajouté la possibilité d'utiliser GMP et MPFR faire quelques optimisations à la compilation en évaluant des fonctions plus complexes qui sont constantes. Cette approche laisse votre code simple et portable, et fait confiance au compilateur pour faire le gros du travail.

Bien sûr, il y a des limites à ce qu'il peut faire. Here's a link to the description in the changelog, qui inclut une liste de fonctions qui sont supportées par ceci. 'pow' en est un.

0

La relecture de bdonlan est optimale, mais gardez à l'esprit que vous pouvez effectuer presque toutes les optimisations que vous avez choisies dans la boîte de compilation à condition d'analyser et d'analyser le code dans votre préprocesseur personnalisé. C'est une tâche triviale dans la plupart des versions d'unix de remplacer les règles implicites qui appellent le compilateur à appeler une étape personnalisée de votre propre avant qu'il ne frappe le compilateur.

4

En fait, vous avez M4 qui est un moyen de pré-processeur plus puissant que le GCC. Une différence principale entre ces deux est que GCC n'est pas récursif alors que M4 est. Cela rend possible des choses comme faire de l'arithmétique à la compilation (et bien plus encore!). L'exemple de code ci-dessous est ce que vous aimeriez faire, n'est-ce pas? Je l'ai fait volumineux dans une source d'un fichier; mais je mets généralement les définitions de macros de M4 dans des fichiers séparés et ajuste mes règles Makefile. De cette façon, votre code est conservé des définitions M4 intrusives laides dans le code source C que j'ai fait ici.

$ 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 ligne de commande pour compiler cet exemple de code utilise la capacité de GCC et M4 à lire à partir de l'entrée standard.

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

Espérons que cette aide!

5

En fait, en exploitant le préprocesseur C, vous pouvez l'obtenir pour calculer C pow(10, x) pour tout réel C et intégrale x. Observez que, comme @quinmars noté, C vous permet d'utiliser la syntaxe scientifique pour exprimer des constantes numériques:

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

à utiliser pour les constantes. Avec cela à l'esprit, et un peu d'intelligence, nous pouvons construire une macro préprocesseur qui prend C et x et les combiner en un jeton d'exponentiation:

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

Cela peut maintenant être utilisé comme une valeur numérique constante:

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

Il est only 23 different powers of 10 qui peut exactement représentable en double précision de sorte que vous pouvez simplement utiliser une table de recherche

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 vous n'avez pas besoin de la valeur exacte mais devez utiliser des puissances supérieures à 10, vous pouvez simplement écrire vos propres versions de puissances de 10 utilisant la table de recherche ci-dessus pour obtenir rapidement le résultat sans multiplier par 10 et encore.

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 des exposants négatifs ne sont pas nécessaires, la branche finale peut être retirée.

Vous pouvez également réduire davantage la taille de la table de recherche si la mémoire est une contrainte. Par exemple, en ne stockant que des puissances égales à 10 et multiplier par 10 lorsque l'exposant est impair, la taille de la table n'est plus que la moitié.

Questions connexes