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

后端 未结 12 708
逝去的感伤
逝去的感伤 2020-12-05 13:25

I know power of 2 can be implemented using << operator. What about power of 10? Like 10^5? Is there any way faster than pow(10,5) in C++? It is a pretty straight-forw

相关标签:
12条回答
  • 2020-12-05 13:52

    Now, with constexpr, you can do like so:

    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 will be calculated at compile time. ASM generated for x86-64 gcc 9.2:

    main:
            push    rbp
            mov     rbp, rsp
            mov     DWORD PTR [rbp-4], 100000
            mov     eax, 0
            pop     rbp
            ret
    
    0 讨论(0)
  • 2020-12-05 13:57

    You can use the lookup table which will be by far the fastest

    You can also consider using this:-

    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;
    }
    
    0 讨论(0)
  • 2020-12-05 13:59

    Here is a stab at it:

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

    What is going on above is a few things.

    First, so BigNum support is cheap, it is templateized. Out of the box, it supports any base type that supports *= own_type and either can be implicitly converted to int, or int can be implicitly converted to it (if both is true, problems will occur), and you need to specialize some templates to indicate that the exponent type involved is both unsigned and integer-like.

    In this case, integer-like and unsigned means that it supports &1 returning bool and >>1 returning something it can be constructed from and eventually (after repeated >>1s) reaches a point where evaluating it in a bool context returns false. I used traits classes to express the restriction, because naive use by a value like -1 would compile and (on some platforms) loop forever, while (on others) would not.

    Execution time for this algorithm, assuming multiplication is O(1), is O(lg(exponent)), where lg(exponent) is the number of times it takes to <<1 the exponent before it evaluates as false in a boolean context. For traditional integer types, this would be the binary log of the exponents value: so no more than 32.

    I also eliminated all branches within the loop (or, made it obvious to existing compilers that no branch is needed, more precisely), with just the control branch (which is true uniformly until it is false once). Possibly eliminating even that branch might be worth it for high bases and low exponents...

    0 讨论(0)
  • 2020-12-05 14:01

    There are certainly ways to compute integral powers of 10 faster than using std::pow()! The first realization is that pow(x, n) can be implemented in O(log n) time. The next realization is that pow(x, 10) is the same as (x << 3) * (x << 1). Of course, the compiler knows the latter, i.e., when you are multiplying an integer by the integer constant 10, the compiler will do whatever is fastest to multiply by 10. Based on these two rules it is easy to create fast computations, even if x is a big integer type.

    In case you are interested in games like this:

    1. A generic O(log n) version of power is discussed in Elements of Programming.
    2. Lots of interesting "tricks" with integers are discussed in Hacker's Delight.
    0 讨论(0)
  • 2020-12-05 14:03

    A solution for any base using template meta-programming :

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

    Then it can be used to generate a lookup-table that can be used at runtime :

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

    This must be used with correct compiler flags in order to detect the possible overflows.

    Usage example :

    for(unsigned int n = 0; n < 10; ++n) {
        std::cout << quick_pow<10>(n) << std::endl;
    }
    
    0 讨论(0)
  • 2020-12-05 14:04

    An integer power function (which doesn't involve floating-point conversions and computations) may very well be faster than pow():

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

    Edit: benchmarked - the naive integer exponentiation method seems to outperform the floating-point one by about a factor of two:

    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 = %d\n", 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$ 
    
    0 讨论(0)
提交回复
热议问题