How to check if an integer is a power of 3?

前端 未结 23 1092
没有蜡笔的小新
没有蜡笔的小新 2020-12-02 08:15

I saw this question, and pop up this idea.

相关标签:
23条回答
  • 2020-12-02 08:33

    Here's a general algorithm for finding out if a number is a power of another number:

    bool IsPowerOf(int n,int b)
    {
        if (n > 1)
        {
            while (n % b == 0)
            {
                n /= b;
            }
        }
        return n == 1;
    }
    
    0 讨论(0)
  • 2020-12-02 08:34

    if (log n) / (log 3) is integral then n is a power of 3.

    0 讨论(0)
  • 2020-12-02 08:34

    Set based solution...

    DECLARE @LastExponent smallint, @SearchCase decimal(38,0)
    
    SELECT
        @LastExponent = 79, -- 38 for bigint
        @SearchCase = 729
    
    ;WITH CTE AS
    (
        SELECT
            POWER(CAST(3 AS decimal(38,0)), ROW_NUMBER() OVER (ORDER BY c1.object_id)) AS Result,
            ROW_NUMBER() OVER (ORDER BY c1.object_id) AS Exponent
        FROM
            sys.columns c1, sys.columns c2
    )
    SELECT
        Result, Exponent
    FROM
        CTE
    WHERE
        Exponent <= @LastExponent
        AND
        Result = @SearchCase
    

    With SET STATISTICS TIME ON it record the lowest possible, 1 millisecond.

    0 讨论(0)
  • 2020-12-02 08:36

    Between powers of two there is at most one power of three. So the following is a fast test:

    1. Find the binary logarithm of n by finding the position of the leading 1 bit in the number. This is very fast, as modern processors have a special instruction for that. (Otherwise you can do it by bit twiddling, see Bit Twiddling Hacks).

    2. Look up the potential power of three in a table indexed by this position and compare to n (if there is no power of three you can store any number with a different binary logarithm).

    3. If they are equal return yes, otherwise no.

    The runtime depends mostly on the time needed for accessing the table entry. If we are using machine integers the table is small, and probably in cache (we are using it many millions of times, otherwise this level of optimization wouldn't make sense).

    0 讨论(0)
  • 2020-12-02 08:37

    There exists a constant time (pretty fast) method for integers of limited size (e.g. 32-bit integers).

    Note that for an integer N that is a power of 3 the following is true:

    1. For any M <= N that is a power of 3, M divides N.
    2. For any M <= N that is not a power 3, M does not divide N.

    The biggest power of 3 that fits into 32 bits is 3486784401 (3^20). This gives the following code:

    bool isPower3(std::uint32_t value) {
        return value != 0 && 3486784401u % value == 0;
    }
    

    Similarly for signed 32 bits it is 1162261467 (3^19):

    bool isPower3(std::int32_t value) {
        return value > 0 && 1162261467 % value == 0;
    }
    

    In general the magic number is:

    3^floor(log_3 MAX) == pow(3, floor(log(MAX) / log(3)))

    Careful with floating point rounding errors, use a math calculator like Wolfram Alpha to calculate the constant. For example for 2^63-1 (signed int64) both C++ and Java give 4052555153018976256, but the correct value is 4052555153018976267.

    0 讨论(0)
  • 2020-12-02 08:38

    I'm surprised at this. Everyone seems to have missed the fastest algorithm of all.

    The following algorithm is faster on average - and dramatically faster in some cases - than a simple while(n%3==0) n/=3; loop:

    bool IsPowerOfThree(uint n)
    {
      // Optimizing lines to handle the most common cases extremely quickly
      if(n%3 != 0) return n==1;
      if(n%9 != 0) return n==3;
    
      // General algorithm - works for any uint
      uint r;
      n = Math.DivRem(n, 59049, out r); if(n!=0 && r!=0) return false;
      n = Math.DivRem(n+r, 243, out r); if(n!=0 && r!=0) return false;
      n = Math.DivRem(n+r,  27, out r); if(n!=0 && r!=0) return false;
      n += r;
      return n==1 || n==3 || n==9;
    }
    

    The numeric constants in the code are 3^10, 3^5, and 3^3.

    Performance calculations

    In modern CPUs, DivRem is a often single instruction that takes a one cycle. On others it expands to a div followed by a mul and an add, which would takes more like three cycles altogether. Each step of the general algorithm looks long but it actually consists only of: DivRem, cmp, cmove, cmp, cand, cjmp, add. There is a lot of parallelism available, so on a typical two-way superscalar processor each step will likely execute in about 4 clock cycles, giving a guaranteed worst-case execution time of about 25 clock cycles.

    If input values are evenly distributed over the range of UInt32, here are the probabilities associated with this algorithm:

    • Return in or before the first optimizing line: 66% of the time
    • Return in or before the second optimizing line: 89% of the time
    • Return in or before the first general algorithm step: 99.998% of the time
    • Return in or before the second general algorithm step: 99.99998% of the time
    • Return in or before the third general algorithm step: 99.999997% of the time

    This algorithm outperforms the simple while(n%3==0) n/=3 loop, which has the following probabilities:

    • Return in the first iteration: 66% of the time
    • Return in the first two iterations: 89% of the time
    • Return in the first three iterations: 97% of the time
    • Return in the first four iterations: 98.8% of the time
    • Return in the first five iterations: 99.6% of the time ... and so on to ...
    • Return in the first twelve iterations: 99.9998% of the time ... and beyond ...

    What is perhaps even more important, this algorithm handles midsize and large powers of three (and multiples thereof) much more efficiently: In the worst case the simple algorithm will consume over 100 CPU cycles because it will loop 20 times (41 times for 64 bits). The algorithm I present here will never take more than about 25 cycles.

    Extending to 64 bits

    Extending the above algorithm to 64 bits is trivial - just add one more step. Here is a 64 bit version of the above algorithm optimized for processors without efficient 64 bit division:

    bool IsPowerOfThree(ulong nL)
    {
      // General algorithm only
      ulong rL;
      nL = Math.DivRem(nL, 3486784401, out rL); if(nL!=0 && rL!=0) return false;
      nL = Math.DivRem(nL+rL,   59049, out rL); if(nL!=0 && rL!=0) return false;
      uint n = (uint)nL + (uint)rL;
      n = Math.DivRem(n,   243, out r); if(n!=0 && r!=0) return false;
      n = Math.DivRem(n+r,  27, out r); if(n!=0 && r!=0) return false;
      n += r;
      return n==1 || n==3 || n==9;
    
    }
    

    The new constant is 3^20. The optimization lines are omitted from the top of the method because under our assumption that 64 bit division is slow, they would actually slow things down.

    Why this technique works

    Say I want to know if "100000000000000000" is a power of 10. I might follow these steps:

    1. I divide by 10^10 and get a quotient of 10000000 and a remainder of 0. These add to 10000000.
    2. I divide by 10^5 and get a quotient of 100 and a remainder of 0. These add to 100.
    3. I divide by 10^3 and get a quotient of 0 and a remainderof 100. These add to 100.
    4. I divide by 10^2 and get a quotient of 1 and a remainder of 0. These add to 1.

    Because I started with a power of 10, every time I divided by a power of 10 I ended up with either a zero quotient or a zero remainder. Had I started out with anything except a power of 10 I would have sooner or later ended up with a nonzero quotient or remainder.

    In this example I selected exponents of 10, 5, and 3 to match the code provided previously, and added 2 just for the heck of it. Other exponents would also work: There is a simple algorithm for selecting the ideal exponents given your maximum input value and the maximum power of 10 allowed in the output, but this margin does not have enough room to contain it.

    NOTE: You may have been thinking in base ten throughout this explanation, but the entire explanation above can be read and understood identically if you're thinking in in base three, except the exponents would have been expressed differently (instead of "10", "5", "3" and "2" I would have to say "101", "12", "10" and "2").

    0 讨论(0)
提交回复
热议问题