2012-02-23 15 views
5

Necesito una implementación muy rápida de la función log2 (float x) en C++.Implementación rápida de log2 (float x) C++

encontré una aplicación muy interesante (y muy rápido!)

#include <intrin.h> 

inline unsigned long log2(int x) 
{ 
    unsigned long y; 
    _BitScanReverse(&y, x); 
    return y; 
} 

Pero esta función es bueno sólo para valores enteros de entrada.

Pregunta: ¿Hay alguna manera de convertir esta función para doble variable de entrada tipo?

UPD:

me encontré con esta implementación:

typedef unsigned long uint32; 
typedef long int32; 
static inline int32 ilog2(float x) 
{ 
    uint32 ix = (uint32&)x; 
    uint32 exp = (ix >> 23) & 0xFF; 
    int32 log2 = int32(exp) - 127; 

    return log2; 
} 

que es mucho más rápido que el ejemplo anterior, pero la salida es tipo sin signo.

¿Es posible hacer que esta función devuelva un tipo doble?

¡Gracias de antemano!

+0

Este es un requisito muy extraño, porque Logaritmo con base 2 rara vez se utiliza para nada más que el cálculo de número de bits por algo y trabajas con enteros cuando cuentas los bits. Entonces, ¿para qué lo necesitas? –

+2

@JanHudec: Fuera de mi cabeza, dos usos comunes de un logaritmo serían calcular la entropía de una señal y hacer aritmética en números muy grandes que de otro modo se desbordarían. –

+0

@MikeSeymour: para la señal, es raro que sea un punto flotante en lugar de un número entero. Para la aritmética en números grandes, no necesitaría la base 2 y probablemente usaría un logaritmo natural ya que las matemáticas usualmente se expresan con eso. –

Respuesta

-1

Esta función no es C++, es específica de MSVC++. Además, dudo mucho que existan tales intrínsecos. Y si lo hicieran, la función estándar simplemente se configuraría para usarla. Así que solo llame a la biblioteca proporcionada por el estándar.

-1

No, pero si solo necesita la parte interal del resultado y no insiste en la portabilidad, la hay aún más rápida. ¡Porque todo lo que necesitas es extraer la parte exponente del flotador!

4

Si solo necesita la parte entera del logaritmo, puede extraerla directamente del número de coma flotante.

portable:

#include <cmath> 

int log2_fast(double d) { 
    int result; 
    std::frexp(d, &result); 
    return result-1; 
} 

Posiblemente más rápido, pero confiando en el comportamiento indeterminado e indefinido:

int log2_evil(double d) { 
    return ((reinterpret_cast<unsigned long long&>(d) >> 52) & 0x7ff) - 1023; 
} 
+1

¿Quiere decir que se basa en una implementación IEEE de flotación o doble? Por supuesto, los implementadores de la biblioteca también pueden haber pensado en eso. – CashCow

+0

@CashCow: De hecho, y también se basa en 'reinterpret_cast' trabajando como se esperaba, es un comportamiento indefinido. 'frexp' ciertamente tomaría ventaja de la representación IEEE; pero a menos que la biblioteca lo proporcione en línea, también incurriría en el costo de una llamada de función y extraería el significado. La versión malvada no hace esas cosas. –

+2

+1 para 'frexp'. –

2

Puede echar un vistazo a this implementation, pero:

  • no puede trabajar en algunas plataformas
  • podría n ot ritmo std :: ingrese
4

Fast log() function (5 × más rápido, aproximadamente)

Tal vez te interese. El código funciona aquí; Sin embargo, no es infinitamente preciso. Como el código se rompe en la página web (el> se han eliminado) voy a publicar aquí:

inline float fast_log2 (float val) 
{ 
    int * const exp_ptr = reinterpret_cast <int *> (&val); 
    int   x = *exp_ptr; 
    const int  log_2 = ((x >> 23) & 255) - 128; 
    x &= ~(255 << 23); 
    x += 127 << 23; 
    *exp_ptr = x; 

    val = ((-1.0f/3) * val + 2) * val - 2.0f/3; // (1) 

    return (val + log_2); 
} 

inline float fast_log (const float &val) 
{ 
    return (fast_log2 (val) * 0.69314718f); 
} 
+0

Parece que esto no funciona. Compilado con gcc-4.4.7, fast_log2 (1024.f) devuelve -347469. – netvope

+0

https://github.com/romeric/fastapprox tiene una función mucho más rápida con la misma precisión, o una función casi tan rápida con mucha mayor precisión – Job

+0

Específicamente en este encabezado: https://github.com/romeric/ fastapprox/blob/master/fastapprox/src/fastlog.h – Job

4

MSVC + GCC versión compatible que dan XX.XXXXXXX + -0,0054545

float mFast_Log2(float val) { 
    union { float val; int32_t x; } u = { val }; 
    register float log_2 = (float)(((u.x >> 23) & 255) - 128);    
    u.x &= ~(255 << 23); 
    u.x += 127 << 23; 
    log_2 += ((-0.3358287811f) * u.val + 2.0f) * u.val -0.65871759316667f; 
    return (log_2); 
} 
+1

Fórmula ligeramente más precisa (error máximo ± 0.00493976), optimizada con [algoritmo de Remez] (http://en.wikipedia.org/wiki/Approximation_theory#Remez.27_algorithm): '((-0.34484843f) * u. val + 2.02466578f) * u.val - 0.67487759f' – netvope

+0

@netvope en serio gracias por este comentario, ¡me hizo abrir los ojos y aprender la teoría de la aproximación! – nimig18

0

Esta es una mejora respecto a la primera respuesta que no se basa en la aplicación de IEEE, aunque imagino que es sólo rápido en máquinas IEEE donde frexp() es básicamente una función de costo.

En lugar de descartar la fracción que devuelve frexp, se puede usar para interpolar linealmente. El valor de la fracción está entre 0.5 y 1.0 si es positivo, por lo que estiramos entre 0.0 y 1.0 y lo agregamos al exponente.

En la práctica, parece que esta evaluación rápida es buena a aproximadamente 5-10%, siempre devolviendo un valor que es un poco bajo. Estoy seguro de que podría mejorarse ajustando el factor de escala 2*.

#include <cmath> 

double log2_fast(double d) { 
    int exponent; 
    double fraction = std::frexp(d, &exponent); 
    return (result-1) + 2* (fraction - 0.5); 
} 

Puede comprobar que se trata de aproximación rápida razonable con esto:

#include <cmath> 

int main() 
{ 
    for(double x=0.001;x<1000;x+=0.1) 
    { 
     std::cout << x << " " << std::log2(x) << " " << log2_fast(x) << "\n"; 
    } 
} 
Cuestiones relacionadas