11
votes

Approximatif e ^ x

J'aimerais approfondir la fonction E x .

est-il possible de le faire en utilisant une approche basée sur plusieurs splines? I.e entre x 1 et x 2 , puis

y 1 = a 1 x + b 1 , entre x 2 et x 3 ,

alors

y 2 = A 2 x + b 2

etc

Ceci est destiné au matériel de FPGA dédié et non à une CPU à usage général. En tant que tel, j'ai besoin de créer moi-même la fonction. La précision est beaucoup moins préoccupante. De plus, je ne peux pas vraiment me permettre plus d'un circuit de multiplication et / ou plusieurs équipes / advecteurs multiples. De plus, je veux quelque chose de beaucoup plus petit qu'une fonction cordique, la taille est critique.


14 commentaires

Quelle gamme de valeurs x envisagez-vous de se rapprocher de cela?


Réponse par défaut: Série Power


Vous avez exp () fonction dans la norme C ++. Pourquoi évitez-vous d'utiliser? Habituellement, il a une bonne vitesse.


Les approximations récursives ne conviennent pas à mon application. La plage maximale potentielle est comprise entre 0 et 4095, mais elle peut être mise à l'échelle à une valeur inférieure. Mon hunch est que j'ai besoin d'environ 4 à 6 bits de précision


Mon application n'est pas réellement C ou C ++, son matériel dédié, alors je roule moi-même. La fonction de puissance est agréable, mais je préférerais quelque chose avec moins d'opérations.


@ user786653: Certainement pas une série de puissance. C'est une définition de mathématiques théoriques, pas une définition de mathématiques numérique. La même page a plus de formules pratiques, par exemple Continuation des fractions


C'est plus ou moins un. Dans certains cas, beaucoup plus ou moins :) Désolé, une vieille blague de mathématiques.


Juste pour clarifier en fonction de l'instruction 0-4095 : c'est entier? Parce que l'algorithme d'entier x est trivial; Il suffit de stocker e ^ 1..e ^ 2048 et multipliez selon les bits de x. 11 multiplications pires cas.


Merci Msalter - Oui, la plage est entière, mais la solution contient environ 10 multiplications trop nombreuses


Voir Math .stackexchange.com / Questions / 55830 / ...


@trican: re "Mais la solution contient environ 10 multiplications trop nombreuses": Tout d'abord, cela ressemble beaucoup à l'optimisation prématurée. Deuxièmement, votre utilisation proposée des splines sera encore plus chère. Troisièmement, 0 à 4095? EXP (4095) est un très grand nombre. Enfin, voir NetLib.org/fdlibm/e_exp.c .


Merci pour la réponse David, j'aimerais que c'était une optimisation prématurée - mais il n'ya pas de mise en œuvre de fonctions exponentielles dans des descriptions matérielles telles que Verilog ou VHDL pour FPGAS / ASICS. De plus, la petite taille et la puissance inférieure sont absolument critiques dans mon cas et je disposerai de négocier une précision pour cela.


Nous avons vraiment besoin de la plage et de la précision de l'entrée et la précision de la sortie. Q12.0 pour l'entrée donne Q400 + pour la sortie. Ce sont des signaux extrêmement larges pour traiter avec une FPGA.


@ ADAM12, dans mes scénarios - x sera négatif, ce qui signifie que la sortie est liée entre 0 et 1 - afin que je puisse confortablement à gérer cela.


10 Réponses :


1
votes

Wolfram présente quelques bonnes façons de l'approcher en termes de série, etc.:


1 commentaires

"Représentations alternatives: E ^ x = z ^ x pour e = z": D



1
votes

ou vous pouvez simplement faire pow (m_e, x) dans C. (certaines plates-formes n'ont pas m_e définie; sur ceux-ci, vous devrez peut-être spécifier manuellement La valeur de e , qui est environ 2.71828182845904523536028747135266249775724709369995 .)

(comme David pointe dans les commentaires, exp (x) serait plus efficace que pow (m_e, x) . Encore une fois, le cerveau n'est pas encore activé. )

Avez-vous un cas d'utilisation où le calcul de e x est un goulot d'étranglement éprouvé? Sinon, vous devez d'abord coder pour la lisibilité; Essayez uniquement ces types d'optimisations si l'approche évidente est trop lente.


4 commentaires

pow (m_e, x) ? Sérieusement? POW (A, B) est généralement implémenté comme exp (b * journal (a)) . Utilisation de POW est une bosse de vitesse, pas une vitesse de vitesse.


C'était un peu mon point - écrivez le code correctement en premier, alors examine la performance de celui-ci. Nulle part dans la question initiale n'est indiqué que cela s'appelle un million de fois une seconde ou quelque chose comme ça, donc ce n'est pas immédiatement évident que la performance sera une question.


Quelle que soit la performance, exp (x) est une solution plus simple (et plus portable!) Que pow (m_e, x) . Même si pow () était plus rapide, le recours plutôt que exp () serait une optimisation prématurée.


Très vrai, et j'ai mis à jour ma réponse pour refléter la correction de David. Pouvez-vous dire que je n'ai pas encore eu assez de café? :)



14
votes

si x est un entier, vous pouvez simplement multiplier E à nouveau encore et encore.

si x n'est pas un entier, vous pouvez calculer le sol E plancher (x) à l'aide de la méthode ci-dessus, puis multipliez par un petit terme de correction. Ce terme de correction peut être facilement calculé à l'aide d'un certain nombre de méthodes d'approximation. Un de ces sens est ceci:

E f 1 + f / 2 (1 + f / 4 (1 + f / 4))) , où f est la partie fractionnée de x

Ceci provient de l'expansion de la série de puissance (optimisée) de E x , qui est très précise pour les petites valeurs de x . Si vous avez besoin de plus de précision, passez simplement plus de termes à la série.

Ce Math.StaCKExchange question contient quelques réponses intelligentes supplémentaires.

Edit: Notez qu'il existe un moyen plus rapide de calculer E n appelé Exponciation par squaling .


6 commentaires

La meilleure solution à la solution entières n'est pas cette solution O (n). Un algorithme de division et de conquérir (PRE) calcule e ^ 1, e ^ 2, E ^ 4, E ^ 2, etc. Vous prenez ensuite les facteurs correspondant aux bits dans x . C'est O (logn). C'est à dire. Pour x = 255, cela ne prend que 8 multiplications au lieu de 254.


Merci - mais je cherche à minimiser les opérations de multiplication, je veux seulement une opération de multiplication


Mais pourquoi ? Êtes-vous en fait Voir des problèmes de performance ou cette optimisation prématurée?


@Jonathan - ce n'est pas pour une CPU, c'est pour le matériel dédié. J'ai mis à jour ma question ci-dessus pour clarifier cela. Désolé pour la confusion


@Jonathan parce que d'avoir une fonction exponentielle O (n) conduira évidemment à une mauvaise performance. L'optimisation prématurée n'est pas mauvaise sur un niveau de systèmes.


C'était exactement ce que je devais faire une version entière mathématique de E ^ x



1
votes

Bien sûr, il est "possible". Il y a plusieurs problèmes.

  1. Quelle est votre exigence pour la précision?

  2. Êtes-vous prêt à utiliser des splines d'ordre supérieur?

  3. Combien de mémoire êtes-vous prêt à dépenser à ce sujet? La fonction linéaire sur de petits intervalles suffisamment est approximative de la fonction exponentielle à tout degré de précision nécessaire, mais elle peut nécessiter un très petit intervalle.

    EDIT:

    Compte tenu des informations supplémentaires fournies, j'ai exécuté un test rapide. La réduction de la plage peut toujours être utilisée sur la fonction exponentielle. Ainsi, si je souhaite calculer exp (x) pour tout X, je peux réécrire le problème dans la forme ... xxx

    où xi est la partie entière de x et XF est la partie fractionnée. La partie entière est simple. Calculez XI sous forme binaire, puis des carings répétés et des multiplications vous permettent de calculer Exp (XI) dans relativement peu d'opérations. (D'autres astuces, utilisant des pouvoirs de 2 et d'autres intervalles peuvent vous donner encore plus de vitesse pour la vitesse affamée.)

    Tout ce qui reste est maintenant de calculer exp (XF). Pouvons-nous utiliser une spline avec des segments linéaires pour calculer Exp (XF), sur l'intervalle [0,1] avec seulement 4 segments linéaires, à une précision de 0,005?

    Cette dernière question est résolue par une fonction Que j'ai écrit il y a quelques années, cela se rapprochera d'une fonction avec une spline d'une commande donnée, à l'intérieur d'une tolérance fixe sur l'erreur maximale. Ce code a nécessité 8 segments sur l'intervalle [0,1] pour atteindre la tolérance requise avec une fonction de spline linéaire par morceaux. Si j'ai choisi de réduire l'intervalle plus loin à [0,0,5], je pouvais maintenant atteindre la tolérance prescrite.

    Donc, la réponse est simple. Si vous êtes prêt à effectuer les réductions de la plage pour réduire x à l'intervalle [0.0.5], faites les calculs appropriés, alors oui, vous pouvez obtenir la précision demandée avec une spline linéaire dans 4 segments.

    En fin de compte, vous ferez toujours mieux d'utiliser une fonction exponentielle codée durement. Toutes les opérations mentionnées ci-dessus seront sûrement plus lentes que ce que votre compilateur fournira, si exp (x) est disponible.


1 commentaires

Merci beaucoup pour la réponse détaillée. À la nouvelle réflexion, je peux tolérer des marges d'erreur beaucoup plus élevées, probablement autant que 0,05, et peut-être même 0,1. J'ai utilisé des splines avec une réduction de la plage avant d'autres fonctions, mais dans ce cas, je pense que la réponse de Lucas ci-dessus est encore plus appropriée pour la réalisation de précision inférieure. Le point clé est également qu'il n'ya aucune implémentation directe dans le matériel "compilateur" pour une fonction exponentielle. c'est-à-dire que je ne travaille pas sur une CPU



3
votes

Tout d'abord, qu'est-ce qui motive cette approximation? En d'autres termes, qu'est-ce qui ne va pas avec le simple exp (x) ?

qui dit, une implémentation typique de exp (x) est de

  • Trouver un integer K et le numéro de point flottant r tel que x = k * journal (2) + r et r est compris entre -0,5 * journal (2) et 0,5 * journal (2).
  • avec cette réduction, exp (x) est 2 k * exp (r) . .
  • Calculer 2 k est un snap.
  • Les implémentations standard de exp (x) utilisent un algorithme de type REMES pour proposer un polynôme minimax qui se rapproche exp (R) . .
  • Vous pouvez faire la même chose, mais utilisez un polynôme d'ordre réduit.

    Voici le kicker: Peu importe ce que vous faites, les chances sont très élevées que votre fonction sera beaucoup, beaucoup plus lente que simplement appeler exp () . La plupart des fonctionnalités de exp () sont implémentées dans le coprocesseur mathématique de votre ordinateur. Ré-implémentation de cette fonctionnalité dans des logiciels, même avec une précision réduite, une ordonnance de grandeur est plus lente que d'utiliser exp () .


2 commentaires

Remez * et la plupart utilisent réellement une approximation de pain centrée sur la liaison de sorte que l'erreur sur cette plage est aussi petite que possible. L'erreur d'une entrée donnée x est égale à l'erreur limitée multipliée par 2 ^ k qui détruit généralement la plupart de ces approximations lorsque l'entrée est grande ... je crois que «La mise en œuvre effective utilise à la fois l'approximation de la pente et une méthode de recherche de racines d'amélioration itérative de fonction inverse soustrayée de l'entrée.


Pourquoi r réside entre -0.5log (2) et 0.5log (2) pas (0, 1) ?



24
votes

Que diriez-vous d'une stratégie comme celle-ci qui utilise la formule

E x = 2 x / ln (2)

  1. Precalculate 1 / ln (2)
  2. Multipliez cette constante par votre argument (1 multiplication)
  3. Utilisez des changements binaires pour augmenter 2 à la partie entière de la puissance (suppose exp + Mantissa Format)
  4. Réglez en fonction de la puissance fractionnée du reste-2 (probablement une seconde multiplication)

    Je me rends compte que ce n'est pas une solution complète, mais elle ne nécessite qu'une seule multiplication et réduit le problème restant pour rapprocher d'une puissance fractionnée de 2, ce qui devrait être plus facile à mettre en œuvre dans du matériel.

    En outre, si votre application est suffisamment spécialisée, vous pouvez essayer de ré-dériver tout le code numérique qui fonctionnera sur votre matériel pour être dans un système de numéros de base- E et de mettre en œuvre votre flottant Quincaillerie ponctuelle pour travailler dans la base e aussi. Alors aucune conversion n'est nécessaire du tout.


4 commentaires

Merci Lucas - C'est parfait pour mes besoins, encore mieux que j'aurais pu espérer. Merci beaucoup!


Heureux d'entendre. On dirait que vous avez des compromis de design intéressants.


@Ttrican Il existe un bon papier sur la mise en œuvre de cette identité et de cette réduction pour obtenir une précision raisonnable pour un point flottant de précision unique à l'aide de tables de recherche et d'arithmétique à point fixe: LORIA.FR/~DETEDYJE/Publications/detdin_fpt_2005.pdf


Lien alternatif vers le PDF: perso.citi-lab.fr/ FDEINEC / RECHERCHE / Publis / 2005-FPT.PDF



2
votes

4 commentaires

Merci @jdberton pour ajouter cela et les liens. L'approche semble assez intéressante, mais êtes-vous sûr que l'extrait de code ci-dessus est correct? Je l'ai essayé pour certaines valeurs et le résultat ne semble pas être proche de près?


Je pense que cela serait inexact pour les grandes valeurs. Vous pouvez probablement trouver un meilleur approximateur de pain avec quelques travaux pour obtenir une meilleure gamme. Cela fonctionne pour moi parce que je n'ai besoin de rien exactement.


La méthode Schrauudolphs est parfaite. Je ne pense pas que cela puisse obtenir plus vite si la précision est acceptable. Dans son article, il détermine l'erreur relative moyenne d'être d'environ 4%. Source: Nic.schrauudolph.org/pubs/schrauudolph99.pdf


Voici une implémentation plus moderne de la méthode de Schrauudolph, en utilisant un float à point unique au lieu de double (qui est un gaspillage, car seuls les 32 bits supérieurs du double sont écrits). MachineDlearnings.com/2011/06/...




2
votes

Ce n'est pas l'interpolation de spline lisse que vous avez demandée, mais son efficacité de calcul:

float expf_fast(float x) {
   union { float f; int i; } y;
   y.i = (int)(x * 0xB5645F + 0x3F7893F5);
   return (y.f);
}


0 commentaires

2
votes

Pour le matériel, j'ai une solution géniale pour vous si vous en avez besoin pour être précis au bit. (Sinon faire une approximation comme ci-dessus). L'identité est exp (x) = Cosh (x) + sinh (x), le sinus hyperbolique et le cosinus. La capture est que le sinus et le cosinus hyperboliques peuvent être calculés à l'aide de la technique coricienne, et le meilleur de tous, ils sont l'une des fonctions cordicales rapides, ce qui signifie qu'ils semblent presque comme se multiplier au lieu de diviser presque comme une division!

Qui signifie pour sur la zone d'un multiplicateur de tableau, vous pouvez calculer l'exposant à une précision arbitraire en seulement 2 cycles!

Rechercher la méthode cordique - c'est incroyable pour la mise en œuvre du matériel.

Une autre approche matérielle utilise une petite table associée à une formule d'autres personnes mentionnées: exp (x + y) = exp (x) * exp (Y). Vous pouvez casser le numéro dans de petits champs - dire 4 ou 8 bits à la fois - et recherchez simplement l'exposant pour ce bitfield. Probablement seulement efficace pour des calculs étroits, mais c'est une autre approche.


0 commentaires