web-dev-qa-db-fra.com

Comment puis-je écrire une fonction d'alimentation moi-même?

Je me demandais toujours comment créer une fonction permettant de calculer la puissance (par exemple, 23) moi même. Dans la plupart des langues, ils sont inclus dans la bibliothèque standard, principalement sous la forme pow(double x, double y), mais comment puis-je l'écrire moi-même?

Je pensais à for loops, mais je pense que mon cerveau s’est mis en boucle (quand je voulais faire un pouvoir avec un exposant non entier, comme 54,5 ou des négatifs 2-21) et je suis devenu fou;)

Alors, comment puis-je écrire une fonction qui calcule la puissance d'un nombre réel? Merci


Oh, peut-être est-il important de noter: je ne peux pas utiliser de fonctions qui utilisent des pouvoirs (par exemple, exp), ce qui rendrait cela inutilisable.

49
user142019

Les pouvoirs négatifs ne sont pas un problème, ils sont simplement l'inverse (1/x) du pouvoir positif.

Les pouvoirs en virgule flottante sont un peu plus compliqués; comme vous le savez, un pouvoir fractionnaire équivaut à une racine (par exemple, x^(1/2) == sqrt(x)) et vous savez également que multiplier des pouvoirs avec la même base revient à ajouter leurs exposants.

Avec tout ce qui précède, vous pouvez:

  • Décomposer l’exposant en une partie entière et une partie rationnelle .
  • Calculez la puissance entière avec une boucle (vous pouvez l’optimiser en décomposant les facteurs et en réutilisant les calculs partiels).
  • Calculez la racine avec n'importe quel algorithme de votre choix (toute approximation itérative telle que la bissection ou la méthode de Newton pourrait fonctionner).
  • Multipliez le résultat.
  • Si l'exposant était négatif, appliquez l'inverse.

Exemple:

2^(-3.5) = (2^3 * 2^(1/2)))^-1 = 1 / (2*2*2 * sqrt(2))
48
fortran

UNEB = Journal-1(Journal (A) * B)

Edit: oui, cette définition fournit vraiment quelque chose d'utile. Par exemple, sur un x86, il se traduit presque directement par FYL2X (Y * Log2(X)) et F2XM1 (2x-1):

fyl2x
fld st(0)
frndint
fsubr st(1),st
fxch st(1)
fchs
f2xmi
fld1
faddp st(1),st
fscale
fstp st(1) 

Le code finit par être un peu plus long que prévu, principalement parce que F2XM1 ne fonctionne qu'avec des nombres compris dans la plage -1.0..1.0. La pièce fld st(0)/frndint/fsubr st(1),st soustrait la partie entière, il ne reste donc que la fraction. Nous appliquons F2XM1 à cela, rajoutons le 1, puis utilisons FSCALE pour gérer la partie entière de l’exponentiation.

22
Jerry Coffin

En règle générale, l'implémentation de la fonction pow(double, double) dans les bibliothèques de mathématiques repose sur l'identité

pow(x,y) = pow(a, y * log_a(x))

En utilisant cette identité, vous devez seulement savoir comment élever un nombre unique a à un exposant arbitraire et comment utiliser un logarithme de base a. Vous avez effectivement transformé une fonction multi-variable complexe en deux fonctions d'une seule variable et en une multiplication assez facile à mettre en œuvre. Les valeurs les plus couramment choisies de a sont e ou 2 - e car e^x et log_e(1+x) possèdent des propriétés mathématiques très agréables, et 2 car elles possèdent des propriétés agréables pour une implémentation dans l'arithmétique à virgule flottante.

Le problème avec cette méthode est que (si vous voulez obtenir une précision complète), vous devez calculer le terme log_a(x) (et son produit avec y) avec une précision supérieure à la représentation à virgule flottante de x et y. Par exemple, si x et y sont doubles et que vous souhaitez obtenir un résultat de haute précision, vous devez trouver un moyen de stocker les résultats intermédiaires (et de faire de l'arithmétique) dans un format de précision plus élevée. Le format Intel x87 est un choix courant, de même que les entiers 64 bits (bien que si vous voulez vraiment une implémentation de qualité supérieure, vous devrez faire quelques calculs d’entiers sur 96 bits, ce qui est un peu pénible dans certains cas. langues). Il est beaucoup plus facile de gérer cela si vous implémentez powf(float,float) car vous pouvez simplement utiliser double pour les calculs intermédiaires. Je recommanderais de commencer avec cela si vous voulez utiliser cette approche.


L'algorithme que j'ai décrit n'est pas le seul moyen possible de calculer pow. C’est simplement ce qui convient le mieux pour obtenir un résultat à grande vitesse qui réponde à une limite de précision fixée a priori. Il convient moins dans d'autres contextes et est certainement beaucoup plus difficile à implémenter que l'algorithme de répétition de carrés [racine] suggéré par certains.

Si vous voulez essayer l'algorithme carré répété [racine], commencez par écrire une fonction d'alimentation entière non signée utilisant uniquement la quadrature répétée. Une fois que vous maîtriserez parfaitement l’algorithme pour ce cas réduit, il vous semblera assez simple de l’étendre pour gérer les exposants fractionnaires.

20
Stephen Canon

Il existe deux cas distincts à traiter: les exposants entiers et les exposants fractionnaires.

Pour les exposants entiers, vous pouvez utiliser l’exponentiation par quadrature.

def pow(base, exponent):
    if exponent == 0:
        return 1
    Elif exponent < 0:
        return 1 / pow(base, -exponent)
    Elif exponent % 2 == 0:
        half_pow = pow(base, exponent // 2)
        return half_pow * half_pow
    else:
        return base * pow(base, exponent - 1)

Le deuxième "Elif" est ce qui distingue cela de la fonction naïve de poudres. Il permet à la fonction de faire des appels récursifs O (log n) au lieu de O (n).

Pour les exposants fractionnaires, vous pouvez utiliser l'identité a ^ b = C ^ (b * log_C (a)). Il est commode de prendre C = 2, donc a ^ b = 2 ^ (b * log2 (a)). Cela réduit le problème à l'écriture de fonctions pour 2 ^ x et log2 (x).

La raison pour laquelle il est pratique de prendre C = 2 est que les nombres à virgule flottante sont stockés dans une virgule flottante en base 2. log2 (a * 2 ^ b) = log2 (a) + b. Cela facilite l'écriture de votre fonction log2: vous n'avez pas besoin qu'elle soit précise pour chaque nombre positif, uniquement sur l'intervalle [1, 2). De même, pour calculer 2 ^ x, vous pouvez multiplier 2 ^ (partie entière de x) * 2 ^ (partie fractionnaire de x). La première partie est facile à stocker dans un nombre à virgule flottante, pour la deuxième partie, il vous suffit d’une fonction 2 ^ x sur l’intervalle [0, 1).

La difficulté consiste à trouver une bonne approximation de 2 ^ x et log2 (x). Une approche simple consiste à utiliser Taylor series .

9
dan04

Par définition:

a ^ b = exp (b ln (a))

exp(x) = 1 + x + x^2/2 + x^3/3! + x^4/4! + x^5/5! + ...

n! = 1 * 2 * ... * n.

En pratique, vous pouvez stocker un tablea des 10 premières valeurs de 1/n!, puis approximer

exp(x) = 1 + x + x^2/2 + x^3/3! + ... + x^10/10!

parce que 10! est un nombre énorme, donc 1/10! est très petit (2.7557319224⋅10 ^ -7).

7
Andreas Rejbrand

Pour les puissances entières positives, regardez exponentiation en exposant et la chaîne d'addition .

4
Richie Cotton

Fonctions Wolfram donne une grande variété de formules de calcul des puissances. Certains d'entre eux seraient très simples à mettre en œuvre.

4

En utilisant trois fonctions auto-implémentées iPow(x, n), Ln(x) et Exp(x), je suis capable de calculer fPow(x, a), x et un être doubles . Aucune des fonctions ci-dessous n'utilise des fonctions de bibliothèque, mais une simple itération.

Quelques explications sur les fonctions implémentées:

(1) iPow(x, n): x est double, n est int. C'est une simple itération, car n est un entier.

(2) Ln(x): Cette fonction utilise l'itération de Taylor Series. La série utilisée dans l'itération est Σ (from int i = 0 to n) {(1 / (2 * i + 1)) * ((x - 1) / (x + 1)) ^ (2 * n + 1)}. Le symbole ^ indique la fonction de puissance Pow(x, n) implémentée dans la 1ère fonction, qui utilise une simple itération.

(3) Exp(x): Cette fonction utilise à nouveau l'itération de Taylor Series. La série utilisée dans l'itération est Σ (from int i = 0 to n) {x^i / i!}. Ici, ^ désigne la fonction d’alimentation, mais c’est pas calculé en appelant la 1ère fonction Pow(x, n); au lieu de cela, il est implémenté dans la 3ème fonction, en même temps que la factorielle, en utilisant d *= x / i. J'ai senti que je devais utiliser cette astuce , car dans cette fonction, l'itération nécessite quelques étapes supplémentaires par rapport aux autres fonctions et le facteur (i!) factoriel déborde la plupart du temps. Afin de s'assurer que l'itération ne déborde pas, la fonction d'alimentation de cette partie est itérée en même temps que la factorielle. De cette façon, j'ai surmonté le débordement.

(4) fPow(x, a): x et a sont tous deux doubles . Cette fonction ne fait rien mais appelle simplement les trois autres fonctions implémentées ci-dessus. L'idée principale de cette fonction dépend du calcul suivant: fPow(x, a) = Exp(a * Ln(x)). Et maintenant, j'ai déjà toutes les fonctions iPow, Ln et Exp avec itération.

n.b. J'ai utilisé un constant MAX_DELTA_DOUBLE afin de décider à quelle étape arrêter l'itération. Je l'ai réglé sur 1.0E-15, ce qui semble raisonnable pour les doubles. Ainsi, l'itération s'arrête si (delta < MAX_DELTA_DOUBLE). Si vous avez besoin de plus de précision, vous pouvez utiliser long double et réduire la valeur constante de MAX_DELTA_DOUBLE à 1.0E-18 par exemple (1.0E-18 serait le minimum).

Voici le code, qui fonctionne pour moi.

#define MAX_DELTA_DOUBLE 1.0E-15
#define EULERS_NUMBER 2.718281828459045

double MathAbs_Double (double x) {
    return ((x >= 0) ? x : -x);
}

int MathAbs_Int (int x) {
    return ((x >= 0) ? x : -x);
}

double MathPow_Double_Int(double x, int n) {
    double ret;
    if ((x == 1.0) || (n == 1)) {
        ret = x;
    } else if (n < 0) {
        ret = 1.0 / MathPow_Double_Int(x, -n);
    } else {
        ret = 1.0;
        while (n--) {
            ret *= x;
        }
    }
    return (ret);
}

double MathLn_Double(double x) {
    double ret = 0.0, d;
    if (x > 0) {
        int n = 0;
        do {
            int a = 2 * n + 1;
            d = (1.0 / a) * MathPow_Double_Int((x - 1) / (x + 1), a);
            ret += d;
            n++;
        } while (MathAbs_Double(d) > MAX_DELTA_DOUBLE);
    } else {
        printf("\nerror: x < 0 in ln(x)\n");
        exit(-1);
    }
    return (ret * 2);
}

double MathExp_Double(double x) {
    double ret;
    if (x == 1.0) {
        ret = EULERS_NUMBER;
    } else if (x < 0) {
        ret = 1.0 / MathExp_Double(-x);
    } else {
        int n = 2;
        double d;
        ret = 1.0 + x;
        do {
            d = x;
            for (int i = 2; i <= n; i++) {
                d *= x / i;
            }
            ret += d;
            n++;
        } while (d > MAX_DELTA_DOUBLE);
    }
    return (ret);
}

double MathPow_Double_Double(double x, double a) {
    double ret;
    if ((x == 1.0) || (a == 1.0)) {
        ret = x;
    } else if (a < 0) {
        ret = 1.0 / MathPow_Double_Double(x, -a);
    } else {
        ret = MathExp_Double(a * MathLn_Double(x));
    }
    return (ret);
}
2
ssd

Vous pouvez trouver la fonctionpowcomme ceci:

static double pows (double p_nombre, double p_puissance)
{
    double nombre   = p_nombre;
    double i=0;
    for(i=0; i < (p_puissance-1);i++){
          nombre = nombre * p_nombre;
       }
    return (nombre);
}

Vous pouvez trouver la fonctionfloorcomme ceci:

static double floors(double p_nomber)
{
    double x =  p_nomber;
    long partent = (long) x; 

    if (x<0)
    {
        return (partent-1);
    }
    else
    {
        return (partent);
    }
}

Meilleures salutations

1
che.moor

C'est un exercice intéressant. Voici quelques suggestions que vous devriez essayer dans cet ordre:

  1. Utilisez une boucle.
  2. Utilisez la récursion (pas mieux, mais intéressant quand même)
  3. Optimisez considérablement votre récursion en utilisant les techniques de division et de conquête
  4. Utilisez des logarithmes
1
Wouter Lievens

Un meilleur algorithme pour calculer efficacement les puissances de nombre entier positif consiste à relier à plusieurs reprises la base tout en gardant une trace des multiplicandes restants supplémentaires. Voici un exemple de solution en Python qui devrait être relativement facile à comprendre et à traduire dans votre langue préférée:

def power(base, exponent):
  remaining_multiplicand = 1
  result = base

  while exponent > 1:
    remainder = exponent % 2
    if remainder > 0:
      remaining_multiplicand = remaining_multiplicand * result
    exponent = (exponent - remainder) / 2
    result = result * result

  return result * remaining_multiplicand

Pour le faire gérer les exposants négatifs, tout ce que vous avez à faire est de calculer la version positive et de diviser 1 par le résultat, ce qui devrait constituer une simple modification du code ci-dessus. Les exposants fractionnaires sont considérablement plus difficiles, car il s’agit essentiellement de calculer une racine nième de la base, où n = 1/abs(exponent % 1) et de multiplier le résultat par le résultat du calcul de la puissance de la partie entière:

power(base, exponent - (exponent % 1))

Vous pouvez calculer les racines avec le niveau de précision souhaité à l'aide de la méthode de Newton. Consultez l'article de wikipedia sur l'algorithme .

1
Kent

J'utilise des arithmétiques longues à point fixe et ma puissance est basée sur log2/exp2. Les numéros comprennent:

  • int sig = { -1; +1 } signum
  • DWORD a[A+B] nombre
  • A est le nombre de DWORDs pour la partie entière du nombre
  • B est le nombre de DWORDs pour la partie décimale

Ma solution simplifiée est la suivante:

//---------------------------------------------------------------------------
longnum exp2 (const longnum &x)
{
    int i,j;
    longnum c,d;
    c.one();
    if (x.iszero()) return c;
    i=x.bits()-1;
    for(d=2,j=_longnum_bits_b;j<=i;j++,d*=d)
    if (x.bitget(j))
    c*=d;
    for(i=0,j=_longnum_bits_b-1;i<_longnum_bits_b;j--,i++)
    if (x.bitget(j))
    c*=_longnum_log2[i];
    if (x.sig<0) {d.one(); c=d/c;}
    return c;
}
//---------------------------------------------------------------------------
longnum log2 (const longnum &x)
{
    int i,j;
    longnum c,d,dd,e,xx;
    c.zero(); d.one(); e.zero(); xx=x;
    if (xx.iszero()) return c; //**** error: log2(0) = infinity
    if (xx.sig<0) return c; //**** error: log2(negative x) ... no result possible
    if (d.geq(x,d)==0) {xx=d/xx; xx.sig=-1;}
    i=xx.bits()-1;
    e.bitset(i); i-=_longnum_bits_b;
    for (;i>0;i--,e>>=1) // integer part
    {
        dd=d*e;
        j=dd.geq(dd,xx);
        if (j==1) continue; // dd> xx
        c+=i; d=dd;
        if (j==2) break; // dd==xx
    }
    for (i=0;i<_longnum_bits_b;i++) // fractional part
    {
        dd=d*_longnum_log2[i];
        j=dd.geq(dd,xx);
        if (j==1) continue; // dd> xx
        c.bitset(_longnum_bits_b-i-1); d=dd;
        if (j==2) break; // dd==xx
    }
    c.sig=xx.sig;
    c.iszero();
    return c;
}
//---------------------------------------------------------------------------
longnum pow (const longnum &x,const longnum &y)
{
    //x^y = exp2(y*log2(x))
    int ssig=+1; longnum c; c=x;
    if (y.iszero()) {c.one(); return c;} // ?^0=1
    if (c.iszero()) return c; // 0^?=0
    if (c.sig<0)
    {
        c.overflow(); c.sig=+1;
        if (y.isreal()) {c.zero(); return c;} //**** error: negative x ^ noninteger y
        if (y.bitget(_longnum_bits_b)) ssig=-1;
    }
    c=exp2(log2(c)*y); c.sig=ssig; c.iszero();
    return c;
}
//---------------------------------------------------------------------------

où:

_longnum_bits_a = A*32
_longnum_bits_b = B*32
_longnum_log2[i] = 2 ^ (1/(2^i))  ... precomputed sqrt table 
_longnum_log2[0]=sqrt(2)  
_longnum_log2[1]=sqrt[tab[0]) 
_longnum_log2[i]=sqrt(tab[i-1])
longnum::zero() sets *this=0
longnum::one() sets *this=+1
bool longnum::iszero() returns (*this==0)
bool longnum::isnonzero() returns (*this!=0)
bool longnum::isreal() returns (true if fractional part !=0)
bool longnum::isinteger() returns (true if fractional part ==0)
int longnum::bits() return num of used bits in number counted from LSB
longnum::bitget()/bitset()/bitres()/bitxor() are bit access
longnum.overflow() rounds number if there was a overflow X.FFFFFFFFFF...FFFFFFFFF??h  -> (X+1).0000000000000...000000000h
int longnum::geq(x,y)  is comparition |x|,|y| returns 0,1,2 for (<,>,==)

Tout ce dont vous avez besoin de comprendre ce code, c’est que les nombres sous forme binaire consistent en une somme de puissances de 2, lorsque vous devez calculer 2 ^ num, il peut être réécrit de la manière suivante:

  • 2^(b(-n)*2^(-n) + ... + b(+m)*2^(+m))

n sont des bits fractionnaires et m sont des bits entiers. multiplication/division par 2 sous forme binaire est un simple décalage de bits, donc si vous mettez tout cela ensemble, vous obtenez un code pour exp2 similaire à my. log2 est basé sur la recherche binaru ... modifiant les bits de résultat de MSB en LSB jusqu'à ce qu'ils correspondent à la valeur recherchée (algorithme très similaire à celui utilisé pour le calcul rapide de sqrt). espérons que cela aide à clarifier les choses ...

0
Spektre

Beaucoup d'approches sont données dans d'autres réponses. Voici quelque chose qui, à mon avis, pourrait être utile en cas de pouvoirs intégraux.

Dans le cas de la puissance entière x de nx , l'approche simple prendrait des multiplications x-1. Afin d'optimiser cela, nous pouvons utiliser la programmation dynamique et réutiliser un résultat de multiplication précédent pour éviter toutes les multiplications x. Par exemple, dans 59, on peut, par exemple, faire lots de 3, c’est-à-dire calculer 53 une fois, obtenez 125, puis le cube 125 en utilisant la même logique, en ne prenant que 4 multiplcations dans le processus, au lieu de 8 multiplications avec la méthode simple. 

La question est de savoir quelle est la taille idéale du lot b pour que le nombre de multiplications soit minimal. Alors écrivons l'équation pour cela. Si f (x, b) est la fonction représentant le nombre de multiplications impliqué dans le calcul de nx en utilisant la méthode ci-dessus, puis 

 > f(x,b) = (x/b - 1) + (b-1)

Explication: Un produit de lot de nombres p prendra des multiplications p-1. Si nous divisons x multiplications en lots b, il y aurait des multiplications (x/b) -1 requises à l'intérieur de chaque lot et des multiplications b-1 requises pour tous les lots b.

Nous pouvons maintenant calculer la première dérivée de cette fonction par rapport à b et l'égaler à 0 pour obtenir le b pour le plus petit nombre de multiplications.

 f'(x,b) = -x/b<sup>2</sup> + 1 = 0

 enter image description here

Remettez maintenant cette valeur de b dans la fonction f (x, b) pour obtenir le plus petit nombre de multiplications:

 enter image description here

Pour tout x positif, cette valeur est inférieure aux multiplications par la voie simple.

0
zafar142003