任何比 pow() 更快的方法都可以在 C++ 中计算 10 的整数幂

Any way faster than pow() to compute an integer power of 10 in C++?

本文关键字:C++ 计算 整数 方法 pow 任何比 都可以      更新时间:2023-10-16

我知道 2 的幂可以使用运算符实现<<。10的幂呢?喜欢 10^5?在C++中,还有比 pow(10,5( 更快的方法吗?这是一个非常简单的手动计算。但是对于计算机来说似乎并不容易,因为数字的二进制表示......让我们假设我只对整数幂 10^n 感兴趣,其中 n 是整数。

像这样:

int quick_pow10(int n)
{
    static int pow10[10] = {
        1, 10, 100, 1000, 10000, 
        100000, 1000000, 10000000, 100000000, 1000000000
    };
    return pow10[n]; 
}

显然,可以为long long做同样的事情.

这应该比任何竞争方法快几倍。但是,如果您有很多碱基,它是非常有限的(尽管值的数量随着基数的增大而急剧下降(,因此如果没有大量的组合,它仍然是可行的。

作为比较:

#include <iostream>
#include <cstdlib>
#include <cmath>
static int quick_pow10(int n)
{
    static int pow10[10] = {
        1, 10, 100, 1000, 10000, 
        100000, 1000000, 10000000, 100000000, 1000000000
    };
    return pow10[n]; 
}
static int integer_pow(int x, int n)
{
    int r = 1;
    while (n--)
       r *= x;
    return r; 
}
static int opt_int_pow(int n)
{
    int r = 1;
    const int x = 10;
    while (n)
    {
        if (n & 1) 
        {
           r *= x;
           n--;
        }
        else
        {
            r *= x * x;
            n -= 2;
        }
    }
    return r; 
}

int main(int argc, char **argv)
{
    long long sum = 0;
    int n = strtol(argv[1], 0, 0);
    const long outer_loops = 1000000000;
    if (argv[2][0] == 'a')
    {
        for(long i = 0; i < outer_loops / n; i++)
        {
            for(int j = 1; j < n+1; j++)
            {
                sum += quick_pow10(n);
            }
        }
    }
    if (argv[2][0] == 'b')
    {
        for(long i = 0; i < outer_loops / n; i++)
        {
            for(int j = 1; j < n+1; j++)
            {
                sum += integer_pow(10,n);
            }
        }
    }
    if (argv[2][0] == 'c')
    {
        for(long i = 0; i < outer_loops / n; i++)
        {
            for(int j = 1; j < n+1; j++)
            {
                sum += opt_int_pow(n);
            }
        }
    }
    std::cout << "sum=" << sum << std::endl;
    return 0;
}
使用

g++ 4.6.3 编译,使用 -Wall -O2 -std=c++0x ,给出以下结果:

$ g++ -Wall -O2 -std=c++0x pow.cpp
$ time ./a.out 8 a
sum=100000000000000000
real    0m0.124s
user    0m0.119s
sys 0m0.004s
$ time ./a.out 8 b
sum=100000000000000000
real    0m7.502s
user    0m7.482s
sys 0m0.003s
$ time ./a.out 8 c
sum=100000000000000000
real    0m6.098s
user    0m6.077s
sys 0m0.002s

(我也确实可以选择使用 pow,但是当我第一次尝试它时需要 1m22.56s,所以当我决定优化循环变体时我删除了它(

当然有办法比使用 std::pow() 更快地计算 10 的积分幂!第一个实现是pow(x, n)可以在O(log n(时间内实现。下一个认识是pow(x, 10)(x << 3) * (x << 1)一样。当然,编译器知道后者,即当您将整数乘以整数常数 10 时,编译器将做任何最快的乘以 10 的事情。基于这两个规则,即使x是大整数类型,也很容易创建快速计算。

如果您对这样的游戏感兴趣:

  1. 通用的 O(log n( 版本的功率在编程元素中讨论。
  2. 许多有趣的整数"技巧"在Hacker's Delight中讨论。

使用模板元编程的任何基础的解决方案:

template<int E, int N>
struct pow {
    enum { value = E * pow<E, N - 1>::value };
};
template <int E>
struct pow<E, 0> {
    enum { value = 1 };
};

然后它可以用来生成一个可以在运行时使用的查找表:

template<int E>
long long quick_pow(unsigned int n) {
    static long long lookupTable[] = {
        pow<E, 0>::value, pow<E, 1>::value, pow<E, 2>::value,
        pow<E, 3>::value, pow<E, 4>::value, pow<E, 5>::value,
        pow<E, 6>::value, pow<E, 7>::value, pow<E, 8>::value,
        pow<E, 9>::value
    };
    return lookupTable[n];
}

这必须与正确的编译器标志一起使用,以便检测可能的溢出。

使用示例:

for(unsigned int n = 0; n < 10; ++n) {
    std::cout << quick_pow<10>(n) << std::endl;
}

整数幂函数(不涉及浮点转换和计算(很可能比pow()快:

int integer_pow(int x, int n)
{
    int r = 1;
    while (n--)
        r *= x;
    return r; 
}

编辑:基准测试 - 朴素整数幂方法似乎比浮点运算高出大约两倍:

h2co3-macbook:~ h2co3$ cat quirk.c
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <string.h>
#include <math.h>
int integer_pow(int x, int n)
{
    int r = 1;
    while (n--)
    r *= x;
    return r; 
}
int main(int argc, char *argv[])
{
    int x = 0;
    for (int i = 0; i < 100000000; i++) {
        x += powerfunc(i, 5);
    }
    printf("x = %dn", x);
    return 0;
}
h2co3-macbook:~ h2co3$ clang -Wall -o quirk quirk.c -Dpowerfunc=integer_pow
h2co3-macbook:~ h2co3$ time ./quirk
x = -1945812992
real    0m1.169s
user    0m1.164s
sys 0m0.003s
h2co3-macbook:~ h2co3$ clang -Wall -o quirk quirk.c -Dpowerfunc=pow
h2co3-macbook:~ h2co3$ time ./quirk
x = -2147483648
real    0m2.898s
user    0m2.891s
sys 0m0.004s
h2co3-macbook:~ h2co3$ 

没有乘法,也没有表格版本:

//Nx10^n
int Npow10(int N, int n){
  N <<= n;
  while(n--) N += N << 2;
  return N;
}

这是对它的刺:

// specialize if you have a bignum integer like type you want to work with:
template<typename T> struct is_integer_like:std::is_integral<T> {};
template<typename T> struct make_unsigned_like:std::make_unsigned<T> {};
template<typename T, typename U>
T powT( T base, U exponent ) {
  static_assert( is_integer_like<U>::value, "exponent must be integer-like" );
  static_assert( std::is_same< U, typename make_unsigned_like<U>::type >::value, "exponent must be unsigned" );
  T retval = 1;
  T& multiplicand = base;
  if (exponent) {
    while (true) {
      // branch prediction will be awful here, you may have to micro-optimize:
      retval *= (exponent&1)?multiplicand:1;
      // or /2, whatever -- `>>1` is probably faster, esp for bignums:
      exponent = exponent>>1;
      if (!exponent)
        break;
      multiplicand *= multiplicand;
    }
  }
  return retval;
}

上面发生的事情是几件事。

首先,所以BigNum支持很便宜,它template化了。 开箱即用,它支持任何支持 *= own_type 的基类型,并且可以隐式转换为 int int 或可以隐式转换为它(如果两者都为真,则会出现问题(,并且您需要专门化一些template来指示所涉及的指数类型既无符号又类似整数。

在这种情况下,类整数和无符号意味着它支持&1返回bool>>1返回可以从中构造的东西,并最终(在重复>>1之后(达到在bool上下文中评估它返回false的点。 我使用 traits 类来表达限制,因为像 -1 这样的值的天真使用会编译并(在某些平台上(永远循环,而(在其他平台上(则不会。

假设乘法为 O(1(,此算法的执行时间为 O(lg(指数((,其中 lg(指数( 是在bool ean 上下文中评估为false之前<<1 exponent所需的次数。 对于传统的整数类型,这将是 exponent s 值的二进制日志:因此不超过 32。

我还删除了循环中的所有分支(或者,让现有编译器清楚地知道不需要分支,更准确地说(,只使用控制分支(这是均匀的,直到它为假一次(。 对于高基数和低指数来说,可能甚至消除该分支可能是值得的......

现在,使用 constexpr ,您可以这样做:

constexpr int pow10(int n) {
    int result = 1;
    for (int i = 1; i<=n; ++i)
        result *= 10;
    return result;
}
int main () {
    int i = pow10(5);
}

i将在编译时计算。为 x86-64 gcc 9.2 生成的 ASM:

main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 100000
        mov     eax, 0
        pop     rbp
        ret
您可以使用查找表,这将是

迄今为止最快的

您也可以考虑使用这个:-

template <typename T>
T expt(T p, unsigned q)
{
    T r(1);
    while (q != 0) {
        if (q % 2 == 1) {    // q is odd
            r *= p;
            q--;
        }
        p *= p;
        q /= 2;
    }
    return r;
}

这个函数计算 x ^ y 的速度比 pow 快得多。如果是整数值。

int pot(int x, int y){
int solution = 1;
while(y){
    if(y&1)
        solution*= x;
    x *= x;
    y >>= 1;
}
return solution;

}

基于 constexpr 函数的通用表生成器。浮点部分需要 c++20 和 gcc,但非浮点部分适用于 c++17。如果将"auto"类型参数更改为"long",则可以使用c ++ 14。未正确测试。

#include <cstdio>
#include <cassert>
#include <cmath>
// Precomputes x^N
// Inspired by https://stackoverflow.com/a/34465458
template<auto x, unsigned char N, typename AccumulatorType>
struct PowTable {
    constexpr PowTable() : mTable() {
        AccumulatorType p{ 1 };
        for (unsigned char i = 0; i < N; ++i) {
            p *= x;
            mTable[i] = p;
        }
    }
    AccumulatorType operator[](unsigned char n) const {
        assert(n < N);
        return mTable[n];
    }
    AccumulatorType mTable[N];
};
long pow10(unsigned char n) {
    static constexpr PowTable<10l, 10, long> powTable;
    return powTable[n-1];
}
double powe(unsigned char n) {
    static constexpr PowTable<2.71828182845904523536, 10, double> powTable;
    return powTable[n-1];
}

int main() {
    printf("10^3=%ldn", pow10(3));
    printf("e^2=%f", powe(2));
    assert(pow10(3) == 1000);
    assert(powe(2) - 7.389056 < 0.001);
}

基于 Mats Petersson 方法,但编译时生成缓存。

#include <iostream>
#include <limits>
#include <array>
// digits
template <typename T>
constexpr T digits(T number) {    
  return number == 0 ? 0 
                     : 1 + digits<T>(number / 10);
}
// pow
// https://stackoverflow.com/questions/24656212/why-does-gcc-complain-error-type-intt-of-template-argument-0-depends-on-a
// unfortunatly we can't write `template <typename T, T N>` because of partial specialization `PowerOfTen<T, 1>`
template <typename T, uintmax_t N>
struct PowerOfTen {
  enum { value = 10 * PowerOfTen<T, N - 1>::value };
};
template <typename T>
struct PowerOfTen<T, 1> {
  enum { value = 1 };
};
// sequence
template<typename T, T...>
struct pow10_sequence { };
template<typename T, T From, T N, T... Is>
struct make_pow10_sequence_from 
: make_pow10_sequence_from<T, From, N - 1, N - 1, Is...> { 
  //  
};
template<typename T, T From, T... Is>
struct make_pow10_sequence_from<T, From, From, Is...> 
: pow10_sequence<T, Is...> { 
  //
};
// base10list
template <typename T, T N, T... Is>
constexpr std::array<T, N> base10list(pow10_sequence<T, Is...>) {
  return {{ PowerOfTen<T, Is>::value... }};
}
template <typename T, T N>
constexpr std::array<T, N> base10list() {    
  return base10list<T, N>(make_pow10_sequence_from<T, 1, N+1>());
}
template <typename T>
constexpr std::array<T, digits(std::numeric_limits<T>::max())> base10list() {    
  return base10list<T, digits(std::numeric_limits<T>::max())>();    
};
// main pow function
template <typename T>
static T template_quick_pow10(T n) {
  static auto values = base10list<T>();
  return values[n]; 
}
// client code
int main(int argc, char **argv) {
  long long sum = 0;
  int n = strtol(argv[1], 0, 0);
  const long outer_loops = 1000000000;
  if (argv[2][0] == 't') {
    for(long i = 0; i < outer_loops / n; i++) {
      for(int j = 1; j < n+1; j++) {
        sum += template_quick_pow10(n);
      }
    }
  }
  std::cout << "sum=" << sum << std::endl;
  return 0;
}

代码不包含quick_pow10、integer_pow opt_int_pow以提高可读性,而是在代码中使用它们完成的测试。

使用 gcc 版本 4.6.3

(Ubuntu/Linaro 4.6.3-1ubuntu5( 编译,使用 -Wall -O2 -std=c++0x,给出以下结果:

$ g++ -Wall -O2 -std=c++0x main.cpp
$ time ./a.out  8 a
sum=100000000000000000
real  0m0.438s
user  0m0.432s
sys 0m0.008s
$ time ./a.out  8 b
sum=100000000000000000
real  0m8.783s
user  0m8.777s
sys 0m0.004s
$ time ./a.out  8 c
sum=100000000000000000
real  0m6.708s
user  0m6.700s
sys 0m0.004s
$ time ./a.out  8 t
sum=100000000000000000
real  0m0.439s
user  0m0.436s
sys 0m0.000s
如果你想

计算,例如,10^5,那么你可以:

int main() {
   cout << (int)1e5 << endl; // will print 100000
   cout << (int)1e3 << endl; // will print 1000
   return 0;
} 

result *= 10也可以写成result = (result << 3) + (result << 1)

constexpr int pow10(int n) {
  int result = 1;
  for (int i = 0; i < n; i++) {
    result = (result << 3) + (result << 1);
  }
  return result;
}