61 votes

Optimisations pour les pow() const non entier exposant?

J'ai des points chauds dans mon code où je suis en train de faire pow() prendre place autour de 10 à 20% de mon temps d'exécution.

Mon entrée à l' pow(x,y) est très spécifique, alors je me demandais si il existe un moyen de rouleau de deux pow() des approximations (un pour chaque exposant) avec des performances supérieures:

  • J'ai deux exposants constants: 2.4 et 1/2.4.
  • Lorsque l'exposant est de 2,4, x sera dans la gamme (0.090473935, 1.0].
  • Lorsque l'exposant est de 1/2.4, x sera dans la gamme (0.0031308, 1.0].
  • Je suis l'aide de SSE/AVX float vecteurs. Si la plate-forme spécificités peuvent être mises à profit, à droite sur la!

Un maximum de taux d'erreur d'environ 0,01% est idéale, si je m'intéresse à plein précision ( float) des algorithmes.

Je suis déjà en utilisant un fast - pow() rapprochement, mais il ne veut pas tenir compte de ces contraintes. Est-il possible de faire mieux?

34voto

David Hammen Points 17912

Une autre réponse, car c'est très différent de ma réponse précédente, et c'est ultra-rapide. L'erreur Relative est 3e-8. Voulez plus de précision? Ajouter un couple de plus Chebychev termes. Il est préférable de garder l'ordre, aussi étrange que cela fait une petite discontinuité entre les 2^n-epsilon et 2^n+epsilon.

#include <stdlib.h>
#include <math.h>

// Returns x^(5/12) for x in [1,2), to within 3e-8 (relative error).
// Want more precision? Add more Chebychev polynomial coefs.
double pow512norm (
   double x)
{
   static const int N = 8;

   // Chebychev polynomial terms.
   // Non-zero terms calculated via
   //   integrate (2/pi)*ChebyshevT[n,u]/sqrt(1-u^2)*((u+3)/2)^(5/12)
   //   from -1 to 1
   // Zeroth term is similar except it uses 1/pi rather than 2/pi.
   static const double Cn[N] = { 
       1.1758200232996901923,
       0.16665763094889061230,
      -0.0083154894939042125035,
       0.00075187976780420279038,
      // Wolfram alpha doesn't want to compute the remaining terms
      // to more precision (it times out).
      -0.0000832402,
       0.0000102292,
      -1.3401e-6,
       1.83334e-7};

   double Tn[N];

   double u = 2.0*x - 3.0;

   Tn[0] = 1.0;
   Tn[1] = u;
   for (int ii = 2; ii < N; ++ii) {
      Tn[ii] = 2*u*Tn[ii-1] - Tn[ii-2];
   }   

   double y = 0.0;
   for (int ii = N-1; ii >= 0; --ii) {
      y += Cn[ii]*Tn[ii];
   }   

   return y;
}


// Returns x^(5/12) to within 3e-8 (relative error).
double pow512 (
   double x)
{
   static const double pow2_512[12] = {
      1.0,
      pow(2.0, 5.0/12.0),
      pow(4.0, 5.0/12.0),
      pow(8.0, 5.0/12.0),
      pow(16.0, 5.0/12.0),
      pow(32.0, 5.0/12.0),
      pow(64.0, 5.0/12.0),
      pow(128.0, 5.0/12.0),
      pow(256.0, 5.0/12.0),
      pow(512.0, 5.0/12.0),
      pow(1024.0, 5.0/12.0),
      pow(2048.0, 5.0/12.0)
   };

   double s;
   int iexp;

   s = frexp (x, &iexp);
   s *= 2.0;
   iexp -= 1;

   div_t qr = div (iexp, 12);
   if (qr.rem < 0) {
      qr.quot -= 1;
      qr.rem += 12;
   }

   return ldexp (pow512norm(s)*pow2_512[qr.rem], 5*qr.quot);
}

Addendum: Ce qui se passe ici?
Par demande, celui-ci explique comment le code ci-dessus fonctionne.

Vue d'ensemble
Le code ci-dessus définit deux fonctions, double pow512norm (double x) et double pow512 (double x). Ce dernier est le point d'entrée pour la suite; c'est la fonction que le code d'utilisateur doit appeler pour calculer la valeur de x^(5/12). La fonction pow512norm(x) utilise des polynômes de Tchebychev approximation de x^(5/12), mais que pour x dans l'intervalle [1,2]. (Utiliser pow512norm(x) pour des valeurs de x en dehors de cette plage et le résultat sera la poubelle.)

La fonction pow512(x) divise le entrantes x sur une paire (double s, int n) tels que x = s * 2^n et tels que 1≤s<2. Une autre partitionnement de l' n en (int q, unsigned int r) tels que n = 12*q + r et r de moins de 12 me permet de diviser le problème de la définition de x^(5/12) en deux parties:

  1. x^(5/12)=(s^(5/12))*((2^n)^(5/12)) via (u*v)^a=(u^a)*(v^) pour le positif u,v et un réel.
  2. s^(5/12) est calculé par l' pow512norm(s).
  3. (2^n)^(5/12)=(2^(12*q+r))^(5/12) par substitution.
  4. 2^(12*q+r)=(2^(12*q))*(2^r) par u^(a+b)=(u^a)*(u^b) positif u, réels a,b.
  5. (2^(12*q+r))^(5/12)=(2^(5*q))*((2^r)^(5/12)) via certains de plus de manipulations.
  6. (2^r)^(5/12) est calculé par la table de recherche pow2_512.
  7. Calculer pow512norm(s)*pow2_512[qr.rem] et nous y sommes presque. Ici, qr.rem est le r de la valeur calculée à l'étape 3 ci-dessus. Tout ce qui est nécessaire, c'est de multiplier ce chiffre par 2^(5*q) pour produire le résultat désiré.
  8. C'est exactement ce que la bibliothèque de mathématiques de la fonction ldexp .

Approximation De Fonction
Le but ici est de trouver une facilement calculable approximation de f(x)=x^(5/12) qui est "suffisamment bonne" pour le problème à la main. Notre rapprochement devrait être proche de f(x) dans un certain sens. Question rhétorique: Qu'est "proche"? Deux interprétations concurrentes sont de minimiser l'erreur quadratique moyenne rapport à minimiser l'erreur absolue maximum.

Je vais utiliser un marché boursier analogie pour décrire la différence entre ces. Supposons que vous souhaitez enregistrer pour votre éventuelle retraite. Si vous êtes dans la vingtaine, la meilleure chose à faire est d'investir dans des actions ou des fonds boursiers. C'est parce que sur un assez long laps de temps, le marché boursier en moyenne battements de tout autre régime de l'investissement. Cependant, nous avons tous vu des moments où mettre de l'argent dans les stocks est une très mauvaise chose à faire. Si vous êtes dans la cinquantaine ou de la soixantaine (ou la quarantaine si vous souhaitez à la retraite des jeunes), vous devez investir un peu plus prudente. Ces baisses peuvent faire sur votre portefeuille de retraite.

Retour à l'approximation de fonction: en tant Que consommateur d'un rapprochement, vous êtes généralement inquiet au sujet de la pire cas d'erreur plutôt que de la performance "en moyenne". Utiliser une approximation conçu pour donner la meilleure performance "moyenne" (par exemple, la méthode des moindres carrés) et la loi de Murphy dicte que votre programme va passer beaucoup de temps à l'aide de l'approximation de exactement où la performance est de loin pire que la moyenne. Ce que vous voulez, c'est un minimax approximation, quelque chose qui minimise le maximum de l'erreur absolue sur certains domaine. Une bonne bibliothèque de mathématiques va prendre un minimax approche plutôt qu'une approche des moindres carrés, car cela permet aux auteurs de la bibliothèque de mathématiques donner une certaine garantie de performance de leur bibliothèque.

Bibliothèques de mathématiques utilisent généralement un polynôme ou rationnelle polynôme d'approximation de la fonction f(x) sur certaines de domaine a≤x≤b. Supposons que la fonction f(x) est analytique sur ce domaine et que vous souhaitez pour approximer la fonction par certains polynôme p(x) de degré N. Pour un degré donné N, il existe magique, unique polynôme p(x) tel que p(x)-f(x) a N+2 extrema sur [a,b] et telle que les valeurs absolues de ces N+2 extrema sont tous égaux l'un à l'autre. Trouver cette magie du polynôme p(x) est le saint graal de la fonction approximators.

Je n'ai pas trouvé le saint graal pour vous. J'ai plutôt utilisé une approximation de Chebyshev. Les polynômes de Tchebychev de première espèce sont un orthogonale (mais pas orthonormé) de l'ensemble des polynômes à certaines caractéristiques très agréable quand il s'agit de la fonction d'approximation. L'approximation de Chebyshev est souvent très proche de celle de magique polynôme p(x). (En fait, l'algorithme de Remez qui n'trouver le saint graal polynôme commence généralement avec une approximation de Chebyshev.)

pow512norm(x)
Cette fonction utilise l'approximation de Chebyshev pour trouver quelques polynôme p*(x) qui est une approximation de x^(5/12). Ici, je suis à l'aide de p*(x) pour distinguer cette approximation de Chebyshev de la magie du polynôme p(x) décrit ci-dessus. L'approximation de Chebyshev p*(x) est facile à trouver, à trouver p(x) est un ours. L'approximation de Chebyshev p*(x) est sum_i Cn[i]*Tn(i,x), où le Cn[i] sont les coefficients de Tchebychev et Tn(i,x) sont les polynômes de Chebyshev évalué à x.

J'ai utilisé Wolfram alpha pour trouver les coefficients de Tchebychev Cn pour moi. Par exemple, ce calcule Cn[1]. La première boîte après la zone d'entrée a la réponse souhaitée, 0.166658 dans ce cas. Ce n'est pas autant de chiffres que je le voudrais. Cliquez sur "plus de chiffres" et le tour est joué, vous obtenez un ensemble beaucoup plus de chiffres. Wolfram alpha est gratuit; il y a une limite sur la quantité de calcul. Il cogne la limite sur les conditions d'ordre supérieur. (Si vous achetez ou avoir accès à mathematica, vous serez en mesure de calculer ceux d'ordre élevé des coefficients à un haut degré de précision.)

Les polynômes de Tchebychev Tn(x) sont calculées dans le tableau Tn. Au-delà de donner quelque chose de très proche de magique polynôme p(x), une autre raison de l'utilisation d'approximation de Chebyshev est que les valeurs de ces polynômes de Tchebychev sont faciles à calculer: Démarrer avec Tn[0]=1 et Tn[1]=x, puis de calculer de manière itérative Tn[i]=2*x*Tn[i-1] - Tn[i-2]. (J'ai utilisé " ii "comme la variable d'index plutôt que" je " dans mon code. Je n'utilise jamais " je " comme un nom de variable. Combien de mots dans la langue anglaise ont un " i " dans le mot? Combien ont deux fois de suite "moi"?)

pow512(x)
pow512 est la fonction que le code d'utilisateur devrait appeler. J'ai déjà décrit les principes de base de cette fonction ci-dessus. Un peu plus de détails: La bibliothèque de mathématiques de la fonction frexp(x) renvoie le significande s et exposant iexp pour l'entrée x. (Petite question: je voudrais s entre 1 et 2 pour une utilisation avec des pow512norm mais frexp renvoie une valeur comprise entre 0,5 et 1.) La bibliothèque de mathématiques de la fonction div renvoie le quotient et le reste de division entière dans un seul récit foop. Enfin, j'utilise la bibliothèque de mathématiques de la fonction ldexp de mettre les trois pièces ensemble afin de former la réponse finale.

24voto

Potatoswatter Points 70305

Dans la norme IEEE 754 piratage veine, voici une autre solution qui est plus rapide et moins "magique." Il réalise une marge d'erreur sous .02% (bien que j'ai fait le minimum de tests) dans une douzaine de cycles d'horloge (pour le cas de p=2.4, sur un Core 2 Duo T7400).

Les nombres à virgule flottante ont été conçue à l'origine comme une approximation des logarithmes, de sorte que vous pouvez utiliser la valeur de l'entier comme une approximation de l' log2. C'est un peu-de façon portable réalisables par l'application de la convertir-de-entier instruction d'une valeur à virgule flottante, pour obtenir une autre valeur à virgule flottante.

Pour compléter l' pow calcul, vous pouvez multiplier par un facteur constant et convertir le logarithme de retour avec le convertir à l'entier de l'instruction. Sur l'ESS, les instructions correspondantes sont cvtdq2ps et cvtps2dq.

Il n'est pas si simple, cependant. L'exposant champ de la norme IEEE 754 est signé, avec un parti pris de la valeur de 127 représentant un exposant de zéro. Ce biais doit être retiré avant de vous multipliez le logarithme, et rajouté avant de vous exponentiate. En outre, la correction du biais par soustraction ne fonctionne pas sur zéro. Heureusement, ces deux réglages peuvent être obtenus en multipliant par un facteur constant à l'avance.

x^p
= exp2( p * log2( x ) )
= exp2( p * ( log2( x ) + 127 - 127 ) - 127 + 127 )
= cvtps2dq( p * ( log2( x ) + 127 - 127 - 127 / p ) )
= cvtps2dq( p * ( log2( x ) + 127 - log2( exp2( 127 - 127 / p ) ) )
= cvtps2dq( p * ( log2( x * exp2( 127 / p - 127 ) ) + 127 ) )
= cvtps2dq( p * ( cvtdq2ps( x * exp2( 127 / p - 127 ) ) ) )

exp2( 127 / p - 127 ) est le facteur constant. Cette fonction est plutôt spécialisé: il ne fonctionnera pas avec les petits exposant fractionnaire, parce que le facteur constant croît de façon exponentielle avec l'inverse de l'exposant et de débordement. Il ne fonctionnera pas avec les exposants négatifs. Grand exposants entraînent une grande erreur, car les bits de la mantisse sont mêlés avec les bits d'exposant par la multiplication.

Mais, c'est juste 4 rapide instructions de long. Pré-multiplier, convertir entier de pouvoir, de multiplier, de les convertir en entier. Les Conversions sont très rapides sur les processeurs Intel de mise en œuvre de l'ESS. On peut aussi presser un supplément constante coefficient dans la première multiplication.

template< unsigned expnum, unsigned expden, unsigned coeffnum, unsigned coeffden >
__m128 fastpow( __m128 arg ) {
        __m128 ret = arg;
//      std::printf( "arg = %,vg\n", ret );
        // Apply a constant pre-correction factor.
        ret = _mm_mul_ps( ret, _mm_set1_ps( exp2( 127. * expden / expnum - 127. )
                * pow( 1. * coeffnum / coeffden, 1. * expden / expnum ) ) );
//      std::printf( "scaled = %,vg\n", ret );
        // Reinterpret arg as integer to obtain logarithm.
        asm ( "cvtdq2ps %1, %0" : "=x" (ret) : "x" (ret) );
//      std::printf( "log = %,vg\n", ret );
        // Multiply logarithm by power.
        ret = _mm_mul_ps( ret, _mm_set1_ps( 1. * expnum / expden ) );
//      std::printf( "powered = %,vg\n", ret );
        // Convert back to "integer" to exponentiate.
        asm ( "cvtps2dq %1, %0" : "=x" (ret) : "x" (ret) );
//      std::printf( "result = %,vg\n", ret );
        return ret;
}

Quelques essais avec l'exposant = 2.4 montrent systématiquement une surestimation d'environ 5%. (La routine est toujours garanti à surestimer.) Il vous suffit de multiplier par 0.95, mais un peu plus d'instructions nous permettra d'aller d'environ 4 décimales de précision, ce qui devrait être suffisant pour les graphiques.

La clé est de faire correspondre le surestimer avec une sous-estimation, et en faire la moyenne.

  • Calculer x^0.8: quatre instructions, erreur ~ +3%.
  • Calculer x^-0.4: un rsqrtps. (Ce qui est assez précis, mais ne sacrifier la capacité de travailler avec zéro.)
  • Calculer x^0.4: un mulps.
  • Calculer x^-0.2: un rsqrtps.
  • Calculer x^2: un mulps.
  • Calculer x^3: un mulps.
  • x^2.4 = x^2 * x^0.4: un mulps. C'est le surestimer.
  • x^2.4 = x^3 * x^-0.4 * x^-0.2: deux mulps. C'est le sous-estimer.
  • La moyenne ci-dessus: un addps, un mulps.

L'Instruction de pointage: quatorze ans, y compris deux conversions avec des temps de latence = 5 et deux réciproque de la racine carrée des estimations de débit = 4.

Pour bien en prendre la moyenne, nous voulons poids estimations par leurs erreurs attendues. La sous-estimation soulève l'erreur à une puissance de 0,6 vs 0.4, de sorte que nous nous attendons à 1,5 x comme erronée. Pondération ne pas ajouter des instructions; il peut être fait dans le pré-facteur. Appelant le coefficient a: a^0.5 = 1,5 a^-0.75, et a = 1.38316186.

La dernière erreur est d'environ .015%, ou de 2 ordres de grandeur mieux que la première fastpow résultat. Le moteur d'exécution est d'environ une douzaine de cycles pour une longue boucle avec volatile source et de la destination des variables... même si c'est le chevauchement des itérations, l'usage du monde réel seront également voir l'instruction au niveau de parallélisme. Considérant SIMD, c'est un débit de l'un scalaire résultat par 3 cycles de!

int main() {
        __m128 const x0 = _mm_set_ps( 0.01, 1, 5, 1234.567 );
        std::printf( "Input: %,vg\n", x0 );

        // Approx 5% accuracy from one call. Always an overestimate.
        __m128 x1 = fastpow< 24, 10, 1, 1 >( x0 );
        std::printf( "Direct x^2.4: %,vg\n", x1 );

        // Lower exponents provide lower initial error, but too low causes overflow.
        __m128 xf = fastpow< 8, 10, int( 1.38316186 * 1e9 ), int( 1e9 ) >( x0 );
        std::printf( "1.38 x^0.8: %,vg\n", xf );

        // Imprecise 4-cycle sqrt is still far better than fastpow, good enough.
        __m128 xfm4 = _mm_rsqrt_ps( xf );
        __m128 xf4 = _mm_mul_ps( xf, xfm4 );

        // Precisely calculate x^2 and x^3
        __m128 x2 = _mm_mul_ps( x0, x0 );
        __m128 x3 = _mm_mul_ps( x2, x0 );

        // Overestimate of x^2 * x^0.4
        x2 = _mm_mul_ps( x2, xf4 );

        // Get x^-0.2 from x^0.4. Combine with x^-0.4 into x^-0.6 and x^2.4.
        __m128 xfm2 = _mm_rsqrt_ps( xf4 );
        x3 = _mm_mul_ps( x3, xfm4 );
        x3 = _mm_mul_ps( x3, xfm2 );

        std::printf( "x^2 * x^0.4: %,vg\n", x2 );
        std::printf( "x^3 / x^0.6: %,vg\n", x3 );
        x2 = _mm_mul_ps( _mm_add_ps( x2, x3 ), _mm_set1_ps( 1/ 1.960131704207789 ) );
        // Final accuracy about 0.015%, 200x better than x^0.8 calculation.
        std::printf( "average = %,vg\n", x2 );
}

Eh bien... désolé je n'ai pas pu poster plus tôt. Et en l'étendant à x^1/2.4 est laissée en exercice ;v) .


Mise à jour avec les stats

J'ai mis en place un petit atelier de test et deux x(512) des cas correspondant à la ci-dessus.

#include <cstdio>
#include <xmmintrin.h>
#include <cmath>
#include <cfloat>
#include <algorithm>
using namespace std;

template< unsigned expnum, unsigned expden, unsigned coeffnum, unsigned coeffden >
__m128 fastpow( __m128 arg ) {
    __m128 ret = arg;
//  std::printf( "arg = %,vg\n", ret );
    // Apply a constant pre-correction factor.
    ret = _mm_mul_ps( ret, _mm_set1_ps( exp2( 127. * expden / expnum - 127. )
        * pow( 1. * coeffnum / coeffden, 1. * expden / expnum ) ) );
//  std::printf( "scaled = %,vg\n", ret );
    // Reinterpret arg as integer to obtain logarithm.
    asm ( "cvtdq2ps %1, %0" : "=x" (ret) : "x" (ret) );
//  std::printf( "log = %,vg\n", ret );
    // Multiply logarithm by power.
    ret = _mm_mul_ps( ret, _mm_set1_ps( 1. * expnum / expden ) );
//  std::printf( "powered = %,vg\n", ret );
    // Convert back to "integer" to exponentiate.
    asm ( "cvtps2dq %1, %0" : "=x" (ret) : "x" (ret) );
//  std::printf( "result = %,vg\n", ret );
    return ret;
}

__m128 pow125_4( __m128 arg ) {
    // Lower exponents provide lower initial error, but too low causes overflow.
    __m128 xf = fastpow< 4, 5, int( 1.38316186 * 1e9 ), int( 1e9 ) >( arg );

    // Imprecise 4-cycle sqrt is still far better than fastpow, good enough.
    __m128 xfm4 = _mm_rsqrt_ps( xf );
    __m128 xf4 = _mm_mul_ps( xf, xfm4 );

    // Precisely calculate x^2 and x^3
    __m128 x2 = _mm_mul_ps( arg, arg );
    __m128 x3 = _mm_mul_ps( x2, arg );

    // Overestimate of x^2 * x^0.4
    x2 = _mm_mul_ps( x2, xf4 );

    // Get x^-0.2 from x^0.4, and square it for x^-0.4. Combine into x^-0.6.
    __m128 xfm2 = _mm_rsqrt_ps( xf4 );
    x3 = _mm_mul_ps( x3, xfm4 );
    x3 = _mm_mul_ps( x3, xfm2 );

    return _mm_mul_ps( _mm_add_ps( x2, x3 ), _mm_set1_ps( 1/ 1.960131704207789 * 0.9999 ) );
}

__m128 pow512_2( __m128 arg ) {
    // 5/12 is too small, so compute the sqrt of 10/12 instead.
    __m128 x = fastpow< 5, 6, int( 0.992245 * 1e9 ), int( 1e9 ) >( arg );
    return _mm_mul_ps( _mm_rsqrt_ps( x ), x );
}

__m128 pow512_4( __m128 arg ) {
    // 5/12 is too small, so compute the 4th root of 20/12 instead.
    // 20/12 = 5/3 = 1 + 2/3 = 2 - 1/3. 2/3 is a suitable argument for fastpow.
    // weighting coefficient: a^-1/2 = 2 a; a = 2^-2/3
    __m128 xf = fastpow< 2, 3, int( 0.629960524947437 * 1e9 ), int( 1e9 ) >( arg );
    __m128 xover = _mm_mul_ps( arg, xf );

    __m128 xfm1 = _mm_rsqrt_ps( xf );
    __m128 x2 = _mm_mul_ps( arg, arg );
    __m128 xunder = _mm_mul_ps( x2, xfm1 );

    // sqrt2 * over + 2 * sqrt2 * under
    __m128 xavg = _mm_mul_ps( _mm_set1_ps( 1/( 3 * 0.629960524947437 ) * 0.999852 ),
                                _mm_add_ps( xover, xunder ) );

    xavg = _mm_mul_ps( xavg, _mm_rsqrt_ps( xavg ) );
    xavg = _mm_mul_ps( xavg, _mm_rsqrt_ps( xavg ) );
    return xavg;
}

__m128 mm_succ_ps( __m128 arg ) {
    return (__m128) _mm_add_epi32( (__m128i) arg, _mm_set1_epi32( 4 ) );
}

void test_pow( double p, __m128 (*f)( __m128 ) ) {
    __m128 arg;

    for ( arg = _mm_set1_ps( FLT_MIN / FLT_EPSILON );
            ! isfinite( _mm_cvtss_f32( f( arg ) ) );
            arg = mm_succ_ps( arg ) ) ;

    for ( ; _mm_cvtss_f32( f( arg ) ) == 0;
            arg = mm_succ_ps( arg ) ) ;

    std::printf( "Domain from %g\n", _mm_cvtss_f32( arg ) );

    int n;
    int const bucket_size = 1 << 25;
    do {
        float max_error = 0;
        double total_error = 0, cum_error = 0;
        for ( n = 0; n != bucket_size; ++ n ) {
            float result = _mm_cvtss_f32( f( arg ) );

            if ( ! isfinite( result ) ) break;

            float actual = ::powf( _mm_cvtss_f32( arg ), p );

            float error = ( result - actual ) / actual;
            cum_error += error;
            error = std::abs( error );
            max_error = std::max( max_error, error );
            total_error += error;

            arg = mm_succ_ps( arg );
        }

        std::printf( "error max = %8g\t" "avg = %8g\t" "|avg| = %8g\t" "to %8g\n",
                    max_error, cum_error / n, total_error / n, _mm_cvtss_f32( arg ) );
    } while ( n == bucket_size );
}

int main() {
    std::printf( "4 insn x^12/5:\n" );
    test_pow( 12./5, & fastpow< 12, 5, 1059, 1000 > );
    std::printf( "14 insn x^12/5:\n" );
    test_pow( 12./5, & pow125_4 );
    std::printf( "6 insn x^5/12:\n" );
    test_pow( 5./12, & pow512_2 );
    std::printf( "14 insn x^5/12:\n" );
    test_pow( 5./12, & pow512_4 );
}

Sortie:

4 insn x^12/5:
Domain from 1.36909e-23
error max =      inf    avg =      inf  |avg| =      inf    to 8.97249e-19
error max =  2267.14    avg =  139.175  |avg| =  139.193    to 5.88021e-14
error max = 0.123606    avg = -0.000102963  |avg| = 0.0371122   to 3.85365e-09
error max = 0.123607    avg = -0.000108978  |avg| = 0.0368548   to 0.000252553
error max =  0.12361    avg = 7.28909e-05   |avg| = 0.037507    to  16.5513
error max = 0.123612    avg = -0.000258619  |avg| = 0.0365618   to 1.08471e+06
error max = 0.123611    avg = 8.70966e-05   |avg| = 0.0374369   to 7.10874e+10
error max =  0.12361    avg = -0.000103047  |avg| = 0.0371122   to 4.65878e+15
error max = 0.123609    avg =      nan  |avg| =      nan    to 1.16469e+16
14 insn x^12/5:
Domain from 1.42795e-19
error max =      inf    avg =      nan  |avg| =      nan    to 9.35823e-15
error max = 0.000936462 avg = 2.0202e-05    |avg| = 0.000133764 to 6.13301e-10
error max = 0.000792752 avg = 1.45717e-05   |avg| = 0.000129936 to 4.01933e-05
error max = 0.000791785 avg = 7.0132e-06    |avg| = 0.000129923 to  2.63411
error max = 0.000787589 avg = 1.20745e-05   |avg| = 0.000129347 to   172629
error max = 0.000786553 avg = 1.62351e-05   |avg| = 0.000132397 to 1.13134e+10
error max = 0.000785586 avg = 8.25205e-06   |avg| = 0.00013037  to 6.98147e+12
6 insn x^5/12:
Domain from 9.86076e-32
error max = 0.0284339   avg = 0.000441158   |avg| = 0.00967327  to 6.46235e-27
error max = 0.0284342   avg = -5.79938e-06  |avg| = 0.00897913  to 4.23516e-22
error max = 0.0284341   avg = -0.000140706  |avg| = 0.00897084  to 2.77556e-17
error max = 0.028434    avg = 0.000440504   |avg| = 0.00967325  to 1.81899e-12
error max = 0.0284339   avg = -6.11153e-06  |avg| = 0.00897915  to 1.19209e-07
error max = 0.0284298   avg = -0.000140597  |avg| = 0.00897084  to 0.0078125
error max = 0.0284371   avg = 0.000439748   |avg| = 0.00967319  to      512
error max = 0.028437    avg = -7.74294e-06  |avg| = 0.00897924  to 3.35544e+07
error max = 0.0284369   avg = -0.000142036  |avg| = 0.00897089  to 2.19902e+12
error max = 0.0284368   avg = 0.000439183   |avg| = 0.0096732   to 1.44115e+17
error max = 0.0284367   avg = -7.41244e-06  |avg| = 0.00897923  to 9.44473e+21
error max = 0.0284366   avg = -0.000141706  |avg| = 0.00897088  to 6.1897e+26
error max = 0.485129    avg = -0.0401671    |avg| = 0.048422    to 4.05648e+31
error max = 0.994932    avg = -0.891494 |avg| = 0.891494    to 2.65846e+36
error max = 0.999329    avg =      nan  |avg| =      nan    to       -0
14 insn x^5/12:
Domain from 2.64698e-23
error max =  0.13556    avg = 0.00125936    |avg| = 0.00354677  to 1.73472e-18
error max = 0.000564988 avg = 2.51458e-06   |avg| = 0.000113709 to 1.13687e-13
error max = 0.000565065 avg = -1.49258e-06  |avg| = 0.000112553 to 7.45058e-09
error max = 0.000565143 avg = 1.5293e-06    |avg| = 0.000112864 to 0.000488281
error max = 0.000565298 avg = 2.76457e-06   |avg| = 0.000113713 to       32
error max = 0.000565453 avg = -1.61276e-06  |avg| = 0.000112561 to 2.09715e+06
error max = 0.000565531 avg = 1.42628e-06   |avg| = 0.000112866 to 1.37439e+11
error max = 0.000565686 avg = 2.71505e-06   |avg| = 0.000113715 to 9.0072e+15
error max = 0.000565763 avg = -1.56586e-06  |avg| = 0.000112415 to 1.84467e+19

Je soupçonne que la précision de la plus exacte 5/12 est limitée par l' rsqrt de l'opération.

20voto

PengOne Points 33226

Ian Stephenson a écrit ce code qu'il prétend surpasse pow(). Il décrit l'idée comme suit:

Pow est essentiellement mis en œuvre à l'aide de journal: pow(a,b)=x(logx(a)*b). nous avons donc besoin d'un rapide de connexion et rapide exposant - il n'a pas d'importance ce que x est pourquoi nous utilisons 2. Le truc, c'est qu'une virgule flottante le numéro est déjà dans un journal de style format:

a=M*2E

En prenant le logarithme des deux côtés donne:

log2(a)=log2(M)+E

ou plus simplement:

log2(a)~=E

En d'autres termes, si nous prenons le flottant point de la représentation d'un nombre, et extrait de l'Exposant, nous avons quelque chose qui est un bon point de départ comme son journal. Il s'avère que lorsque nous ce faire masser les modèles de bits, la Mantisse finit par donner une bonne approximation de l'erreur, et il fonctionne assez bien.

Ce devrait être assez bon pour de simples l'éclairage des calculs, mais si vous avez besoin d' quelque chose de mieux, vous pouvez ensuite extraire la Mantisse, et l'utiliser pour calculer un facteur de correction quadratique ce qui est assez précis.

17voto

David Hammen Points 17912

Tout d'abord, à l'aide de flotteurs n'est pas qui va acheter beaucoup sur la plupart des machines de nos jours. En fait, en double peut être plus rapide. Votre pouvoir, 1.0/2.4, 5/12 ou 1/3*(1+1/4). Même si c'est l'appel de cbrt (une fois) et sqrt (deux fois!) il est encore deux fois plus rapide que d'utiliser pow(). (Optimisation: -O3, compilateur: i686-apple-darwin10-g++-4.2.1).

#include <math.h> // cmath does not provide cbrt; C99 does.
double xpow512 (double x) {
  double cbrtx = cbrt(x);
  return cbrtx*sqrt(sqrt(cbrtx));
}

15voto

Dietrich Epp Points 72865

Cela pourrait ne pas répondre à votre question.

L' 2.4f et 1/2.4f me rend très suspect, parce que ce sont justement les pouvoirs utilisé pour convertir entre sRGB et linéaire dans l'espace colorimétrique RVB. De sorte que vous pourrait en fait être en essayant d'optimiser , en particulier. Je ne sais pas, c'est pourquoi cela ne peut pas répondre à votre question.

Si c'est le cas, essayez d'utiliser une table de recherche. Quelque chose comme:

__attribute__((aligned(64))
static const unsigned short SRGB_TO_LINEAR[256] = { ... };
__attribute__((aligned(64))
static const unsigned short LINEAR_TO_SRGB[256] = { ... };

void apply_lut(const unsigned short lut[256], unsigned char *src, ...

Si vous êtes à l'aide de 16 bits de données, modifier comme il convient. Je voudrais faire le tableau 16 bits de toute façon, alors vous pouvez juxtaposer le résultat si nécessaire lorsque l'on travaille avec des données de 8 bits. De toute évidence, cela ne fonctionne pas très bien si vos données à virgule flottante pour commencer-mais il n'a pas vraiment de sens de les stocker sRGB de données en virgule flottante, ainsi vous pouvez convertir de 16 bits / 8 bits en premier et ensuite faire la conversion linéaire en sRGB.

(La raison sRGB ne fait pas de sens qu'à virgule flottante est que HDR devrait être linéaire, et sRGB est seulement pratique pour le stockage sur disque ou de l'affichage sur l'écran, mais pas pratique pour la manipulation.)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X