Find the smallest regular number that is not less than N

后端 未结 8 2119
清歌不尽
清歌不尽 2020-12-16 19:08

Regular numbers are numbers that evenly divide powers of 60. As an example, 602 = 3600 = 48 × 75, so both 48 and 75 are divisors of a power of 60.

8条回答
  •  北荒
    北荒 (楼主)
    2020-12-16 19:17

    Here's a solution in Python, based on Will Ness answer but taking some shortcuts and using pure integer math to avoid running into log space numerical accuracy errors:

    import math
    
    def next_regular(target):
        """
        Find the next regular number greater than or equal to target.
        """
        # Check if it's already a power of 2 (or a non-integer)
        try:
            if not (target & (target-1)):
                return target
        except TypeError:
            # Convert floats/decimals for further processing
            target = int(math.ceil(target))
    
        if target <= 6:
            return target
    
        match = float('inf') # Anything found will be smaller
        p5 = 1
        while p5 < target:
            p35 = p5
            while p35 < target:
                # Ceiling integer division, avoiding conversion to float
                # (quotient = ceil(target / p35))
                # From https://stackoverflow.com/a/17511341/125507
                quotient = -(-target // p35)
    
                # Quickly find next power of 2 >= quotient
                # See https://stackoverflow.com/a/19164783/125507
                try:
                    p2 = 2**((quotient - 1).bit_length())
                except AttributeError:
                    # Fallback for Python <2.7
                    p2 = 2**(len(bin(quotient - 1)) - 2)
    
                N = p2 * p35
                if N == target:
                    return N
                elif N < match:
                    match = N
                p35 *= 3
                if p35 == target:
                    return p35
            if p35 < match:
                match = p35
            p5 *= 5
            if p5 == target:
                return p5
        if p5 < match:
            match = p5
        return match
    

    In English: iterate through every combination of 5s and 3s, quickly finding the next power of 2 >= target for each pair and keeping the smallest result. (It's a waste of time to iterate through every possible multiple of 2 if only one of them can be correct). It also returns early if it ever finds that the target is already a regular number, though this is not strictly necessary.

    I've tested it pretty thoroughly, testing every integer from 0 to 51200000 and comparing to the list on OEIS http://oeis.org/A051037, as well as many large numbers that are ±1 from regular numbers, etc. It's now available in SciPy as fftpack.helper.next_fast_len, to find optimal sizes for FFTs (source code).

    I'm not sure if the log method is faster because I couldn't get it to work reliably enough to test it. I think it has a similar number of operations, though? I'm not sure, but this is reasonably fast. Takes <3 seconds (or 0.7 second with gmpy) to calculate that 2142 × 380 × 5444 is the next regular number above 22 × 3454 × 5249+1 (the 100,000,000th regular number, which has 392 digits)

提交回复
热议问题