2012-02-23 3 views
5

Мне нужна очень быстрая реализация функции log2 (float x) в C++.Быстрая реализация log2 (float x) C++

Я нашел очень интересную реализацию (и очень быстро!)

#include <intrin.h> 

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

Но эта функция хороша только для целочисленных значений на входе.

Вопрос: Есть ли способ, чтобы преобразовать эту функцию в двойной входной переменной типа?

UPD:

Я нашел эту реализацию:

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; 
} 

, который гораздо быстрее, чем в предыдущем примере, но выход тип без знака.

Возможно ли вернуть эту функцию double?

Заранее благодарен!

+0

Это очень странное требование, поскольку Логарифм с основанием 2 редко используется для чего-нибудь, кроме вычисления числа битов для чего-то и вы работаете с целыми числами при подсчете бит. Так зачем вам это нужно? –

+2

@JanHudec: В верхней части моей головы два общих использования логарифма будут вычислять энтропию сигнала и делать арифметику на очень больших числах, которые иначе переполнялись бы. –

+0

@MikeSeymour: для сигнала редко бывает плавающей точкой, а не целой. Для арифметики на больших числах вам не понадобится база 2 и, вероятно, использовать естественный логарифм, поскольку математика обычно выражается этим. –

ответ

-1

Эта функция не является C++, она специфична для MSVC++. Кроме того, я очень сомневаюсь, что существуют такие внутренние свойства. И если бы они это сделали, стандартная функция просто была бы настроена на ее использование. Поэтому просто позвоните в стандартную библиотеку.

-1

Нет, но если вам нужна только целая часть результата и не настаивайте на переносимости, есть еще более быстрый. Потому что все, что вам нужно, это извлечь экспоненциальную часть поплавка!

4

Если вам нужна только целая часть логарифма, вы можете извлечь ее непосредственно из числа с плавающей запятой.

Портабельно:

#include <cmath> 

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

Возможно быстрее, но, опираясь на неопределенный и непредсказуемое поведение:

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

Вы имеете в виду, что он полагается на реализацию IEEE float или double? Конечно, разработчики библиотеки, возможно, тоже подумали об этом? – CashCow

+0

@CashCow: Действительно, он также полагается на 'reinterpret_cast', работающий как надеется - это неопределенное поведение. 'frexp', несомненно, воспользуется представлением IEEE; но если библиотека не предоставит ему встроенный, это также приведет к стоимости вызова функции и извлечению значимости. Злая версия этого не делает. –

+2

+1 для 'frexp'. –

2

Вы можете взглянуть на this implementation, но:

  • не может работа на некоторых платформах
  • may n ВЗ бить станд :: войти
4

Fast log() function (5 × быстрее приблизительно)

Может быть интерес для Вас. Здесь работает код; Однако это не бесконечно точно. Так как код разбивается на веб-страницу (> были удалены), я выложу его здесь:

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

Кажется, что это не работает. Скомпилированный с gcc-4.4.7, fast_log2 (1024.f) возвращает -347469. – netvope

+0

https://github.com/romeric/fastapprox имеет гораздо более быструю функцию с одинаковой точностью или почти как быструю функцию с гораздо более высокой точностью. – Job

+0

В частности, в этом заголовке: https://github.com/romeric/ fastapprox/блоб/ведущий/fastapprox/SRC/fastlog.h – Job

4

MSVC + GCC совместимые версии, которые дают 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

Чуть более точная формула (максимальная погрешность ± 0,00493976), оптимизированная с использованием алгоритма [Remez] (http://en.wikipedia.org/wiki/Approximation_theory#Remez.27_algorithm): '((-0.34484843f) * u. val + 2.02466578f) * u.val - 0.67487759f' – netvope

+0

@netvope серьезно поблагодарить вас за этот комментарий, заставил меня открыть глаза и изучить теорию приближения! – nimig18

0

Это улучшает первый ответ, который не полагается на реализацию IEEE, хотя я полагаю, что он работает только на машинах IEEE, где frexp() - это в принципе бесполезная функция.

Вместо того, чтобы отбрасывать фракцию, возвращаемую frexp, можно использовать ее для линейной интерполяции. Значение доли составляет от 0,5 до 1,0, если оно положительное, поэтому мы растягиваем между 0.0 и 1.0 и добавляем его к экспоненте.

На практике это выглядит так, что эта быстрая оценка хороша примерно до 5-10%, всегда возвращающая значение, немного низкое. Я уверен, что это можно улучшить, изменив масштабный коэффициент 2*.

#include <cmath> 

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

Вы можете проверить, что это разумно быстро приближение с этим:

#include <cmath> 

int main() 
{ 
    for(double x=0.001;x<1000;x+=0.1) 
    { 
     std::cout << x << " " << std::log2(x) << " " << log2_fast(x) << "\n"; 
    } 
} 
Смежные вопросы