【问题标题】:Fast log2(float x) implementation C++快速 log2(float x) 实现 C++
【发布时间】:2012-02-23 11:07:49
【问题描述】:

我需要在 C++ 中快速实现 log2(float x) 函数。

我发现了一个非常有趣的实现(而且速度非常快!)

#include <intrin.h>

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

但是这个函数只适用于输入中的整数值。

问题:有没有办法把这个函数转换成double类型的输入变量?

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类型?

提前致谢!

【问题讨论】:

  • 这是一个非常奇怪的要求,因为以 2 为底的对数很少用于任何事情,除了计算某事物的位数并且在计算位数时使用整数。那你需要它做什么?
  • @JanHudec:在我的脑海中,对数的两个常见用途是计算信号的熵,以及对非常大的数字进行算术运算,否则会溢出。
  • @MikeSeymour:对于信号,很少使用浮点数而不是整数。对于大数的算术,您不需要以 2 为底,并且可能使用自然对数,因为数学通常用它来表示。
  • @JanHudec:好吧,如果它比整数更方便,我会使用浮点来表示信号的属性;我可能会选择 2 作为任意基数,因为这可能比自然对数计算得更快;但这只是我。没有必要争论它,或者 OP 是否应该对快速对数感兴趣。
  • @MikeSeymour:问这个问题总是有意义的。如果我们知道问题或至少知道问题域,那么完全使用其他东西可能会更有效。

标签: c++ performance


【解决方案1】:

如果你只需要对数的整数部分,那么你可以直接从浮点数中提取出来。

便携:

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

【讨论】:

  • 您的意思是它依赖于浮点或双精度的 IEEE 实现?当然库的实现者可能也想到了那个?
  • @CashCow:确实,它还依赖于reinterpret_cast 按预期工作——这是未定义的行为。 frexp 当然会利用 IEEE 表示;但除非库内联提供它,否则也会产生函数调用和提取有效数字的成本。邪恶的版本不会做这些事情。
  • 谢谢你们!我在高性能科学计算领域工作,因此需要优化所有基本的数学函数。坦率地说,我有 4 个嵌套循环,瓶颈是一些复杂的数学表达式,涉及 log()、exp()、atan()、sqrt() 等。我需要自然对数函数 log(),但由于 log2() 更“流行”,我可以通过乘以 log(2)(它是常数)将 log2() 转换为 log()。所以,我的问题是:如何实现最快的 log()/log2() 函数,它以 double 类型作为参数并返回 double 类型。
  • 例如:log(15.37) = 2.7292
  • 您需要编译器特定的命令来关闭严格的别名优化,否则邪恶的强制转换代码几乎肯定会失败。
【解决方案2】:

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

【讨论】:

  • 公式稍微准确一些(最大误差±0.00493976),使用Remez' algorithm优化:((-0.34484843f) * u.val + 2.02466578f) * u.val - 0.67487759f
  • @netvope 非常感谢你的评论,让我睁大了眼睛,学习了近似理论!
【解决方案3】:

编辑:查看下方 cmets 中 Job 的链接以获得更好的版本。


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

【讨论】:

【解决方案4】:

你可以看看this implementation,但是:

  • 它可能无法在某些平台上运行
  • 可能无法击败 std::log

【讨论】:

    【解决方案5】:

    C++11 将std::log2 添加到&lt;cmath&gt;

    【讨论】:

    • 从什么时候开始标准库中的任何东西都快...
    【解决方案6】:

    这是对第一个答案的改进,它不依赖于 IEEE 实现,尽管我认为它仅在 frexp() 基本上是无成本函数的 IEEE 机器上速度很快。

    可以使用它进行线性插值,而不是丢弃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";
       }
    }
    

    【讨论】:

      【解决方案7】:

      不,但是如果您只需要结果的整数部分并且不坚持可移植性,那么还有更快的。因为您只需要提取浮点数的指数部分!

      【讨论】:

        【解决方案8】:

        这个函数不是 C++,它是 MSVC++ 特定的。此外,我高度怀疑是否存在任何此类内在函数。如果他们这样做了,标准功能将被简单地配置为使用它。所以只需调用标准提供的库。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-09-23
          • 2012-01-01
          • 2012-07-07
          • 2011-03-29
          • 2017-02-18
          • 2017-05-09
          • 1970-01-01
          相关资源
          最近更新 更多