log10函数返回int的性能

时间:2014-09-17 13:59:04

标签: c++ optimization int

今天我需要一个廉价的log10函数,其中我只使用了int部分。假设结果是无效的,那么999的log10将是2.自己编写函数是否有益?如果是这样,哪种方式最好去。假设代码不会被优化。

log10的替代品我虽然;

  • 使用for循环除以或乘以10;
  • 使用字符串解析器(可能非常昂贵);
  • 使用整数log2()函数乘以常量。

事先谢谢你:)

5 个答案:

答案 0 :(得分:14)

可以在任何具有count-leading-zero或类似指令(大多数架构)的架构上(快速)恒定时间内完成操作。这是我用来计算十分之一位数的C片段,它基本上是相同的任务(假设一个类似gcc的编译器和32位int):

unsigned int baseTwoDigits(unsigned int x) {
    return x ? 32 - __builtin_clz(x) : 0;
}

static unsigned int baseTenDigits(unsigned int x) {
    static const unsigned char guess[33] = {
        0, 0, 0, 0, 1, 1, 1, 2, 2, 2,
        3, 3, 3, 3, 4, 4, 4, 5, 5, 5,
        6, 6, 6, 6, 7, 7, 7, 8, 8, 8,
        9, 9, 9
    };
    static const unsigned int tenToThe[] = {
        1, 10, 100, 1000, 10000, 100000, 
        1000000, 10000000, 100000000, 1000000000,
    };
    unsigned int digits = guess[baseTwoDigits(x)];
    return digits + (x >= tenToThe[digits]);
}

GCC和clang将此编译为x86上的~10条指令。小心翼翼,人们可以更快地装配它。

关键的洞察力是使用(非常便宜)基数为2的对数来快速估计基数为10的对数;在那一点上,我们只需要比较一个10的幂来决定我们是否需要调整猜测。这比搜索十个多个权力以找到正确的权力要高效得多。

如果输入绝对偏向于一位和两位数字,则线性扫描有时会更快;对于所有其他输入分配,这种实现往往非常方便。

答案 1 :(得分:2)

嗯,有旧的待机 - “穷人的日志功能”。 (如果要处理超过63个整数位,请将第一个“if”更改为“while”。)

n = 1;
if (v >= 1e32){n += 32; v /= 1e32;}
if (v >= 1e16){n += 16; v /= 1e16;}
if (v >=  1e8){n +=  8; v /=  1e8;}
if (v >=  1e4){n +=  4; v /=  1e4;}
if (v >=  1e2){n +=  2; v /=  1e2;}
if (v >=  1e1){n +=  1; v /=  1e1;}

所以,如果你输入123456.7,它就是这样的:

n = 1;
if (v >= 1e32) no
if (v >= 1e16) no
if (v >=  1e8) no
if (v >=  1e4) yes, so n = 5, v = 12.34567
if (v >=  1e2) no
if (v >=  1e1) yes, so n = 6, v = 1.234567
     so result is n = 6

这是一个使用乘法而不是除法的变体:

int n = 1;
double d = 1, temp;
temp = d * 1e32; if (v >= temp){n += 32; d = temp;}
temp = d * 1e16; if (v >= temp){n += 16; d = temp;}
temp = d *  1e8; if (v >= temp){n +=  8; d = temp;}
temp = d *  1e4; if (v >= temp){n +=  4; d = temp;}
temp = d *  1e2; if (v >= temp){n +=  2; d = temp;}
temp = d *  1e1; if (v >= temp){n +=  1; d = temp;}

并且执行看起来像这样

v = 123456.7
n = 1
d = 1
temp = 1e32, if (v >= 1e32) no
temp = 1e16, if (v >= 1e16) no
temp =  1e8, if (v >=  1e8) no
temp =  1e4, if (v >=  1e4) yes, so n = 5, d = 1e4;
temp =  1e6, if (v >=  1e6) no
temp =  1e5, if (v >=  1e5) yes, so n = 6, d = 1e5;

答案 2 :(得分:1)

如果您想拥有更快的日志功能,则需要近似其结果。例如。 exp函数可以使用' short'来近似。泰勒近似。您可以找到exp,log,root和power here

的示例近似值

修改 您可以找到简短的性能比较here

答案 3 :(得分:1)

一种方法是使用减去10的幂来循环。可以计算这些幂并将其存储在表中。这里是python的例子:

table = [10**i for i in range(1, 10)]
# [10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]

def fast_log10(n):
    for i, k in enumerate(table):
        if n - k < 0:
           return i

用法示例:

>>> fast_log10(1)
0
>>> fast_log10(10)
1
>>> fast_log10(100)
2
>>> fast_log10(999)
2
fast_log10(1000)
3

您也可以对此表使用二进制搜索。然后算法复杂度仅为O(lg(n)),其中n是数字位数。 这里用C:

中的二进制搜索示例
long int table[] = {10, 100, 1000, 10000, 1000000};
#define TABLE_LENGHT sizeof(table) / sizeof(long int)

int bisect_log10(long int n, int s, int e) {
    int a = (e - s) / 2 + s;
    if(s >= e)
        return s;
    if((table[a] - n) <= 0)
        return bisect_log10(n, a + 1, e);
    else
        return bisect_log10(n, s, a);
}

int fast_log10(long int n){
    return bisect_log10(n, 0, TABLE_LENGHT);
}

注意小数字这个方法会慢于上方法。 完整代码here

答案 4 :(得分:0)

没有任何规范,我只会给出一般答案:

日志功能在大多数语言中都非常有效,因为它是一个基本功能。

您只对整数感兴趣的事实可以为您提供一些杠杆作用,但这可能不足以轻松击败内置标准解决方案。

我认为比内置函数更快的一件事是表查找,所以如果你只对10000的数字感兴趣,你可以简单地创建一个你可以使用的表在需要时查找这些值中的任何一个。

显然,这种解决方案不能很好地扩展,但它可能正是您所需要的。


旁注:例如,如果要导入数据,实际上查看字符串节点长度实际上会更快(而不是先将字符串转换为数字而不是查看字符串的值)。当然,这将要求输入以正确的格式存储,否则它将无法获得任何东西。