繁体   English   中英

找到不小于N的最小常数

[英]Find the smallest regular number that is not less than N

常数是均匀划分60的幂的数字。例如,60 2 = 3600 = 48×75,因此48和75都是60的幂的除数。因此,它们也是常规数。

这是四舍五入到下一个幂的延伸。

我有一个整数值N ,它可能包含大的素因子,我想把它四舍五入到只由小素因子组成的数字(2,3和5)

例子:

  • f(18) == 18 == 2 1 * 3 2
  • f(19) == 20 == 2 2 * 5 1
  • f(257) == 270 == 2 1 * 3 3 * 5 1

找到满足此要求的最小数字的有效方法是什么?

涉及的值可能很大,所以我想避免枚举从1开始的所有常规数字或维护所有可能值的数组。

通过直接枚举三元组(i,j,k) ,可以在时间~ n^(2/3)围绕第n个成员产生任意薄的汉明序列切片,使得N = 2^i * 3^j * 5^k

该算法从log2(N) = i+j*log2(3)+k*log2(5)起作用; 枚举所有可能的k s,并且对于每个可能的j s,找到顶部i ,从而找到三元组(k,j,i)并且如果在给定的高对数顶部下面的给定“宽度”内,则将其保持在“带”中值(当width <1时,最多可以有一个这样的i )然后按它们的对数对它们进行排序。

WP表示 n ~ (log N)^3 ,即运行时间~ (log N)^2 这里我们不关心序列中找到的三元组的确切位置,因此原始代码中的所有计数计算都可以丢弃:

slice hi w = sortBy (compare `on` fst) b where       -- hi>log2(N) is a top value
  lb5=logBase 2 5 ; lb3=logBase 2 3                  -- w<1 (NB!) is log2(width)
  b  = concat                                        -- the slice
      [ [ (r,(i,j,k)) | frac < w ]                   -- store it, if inside width
        | k <- [ 0 .. floor ( hi   /lb5) ],  let p = fromIntegral k*lb5,
          j <- [ 0 .. floor ((hi-p)/lb3) ],  let q = fromIntegral j*lb3 + p,
          let (i,frac)=properFraction(hi-q) ;    r = hi - frac ]   -- r = i + q
                    -- properFraction 12.7 == (12, 0.7)

-- update: in pseudocode:
def slice(hi, w):
    lb5, lb3 = logBase(2, 5), logBase(2, 3)  -- logs base 2 of 5 and 3
    for k from 0 step 1 to floor(hi/lb5) inclusive:
        p = k*lb5
        for j from 0 step 1 to floor((hi-p)/lb3) inclusive:
           q = j*lb3 + p
           i = floor(hi-q)
           frac = hi-q-i                     -- frac < 1 , always
           r = hi - frac                     -- r == i + q
           if frac < w:
              place (r,(i,j,k)) into the output array
   sort the output array's entries by their "r" component
        in ascending order, and return thus sorted array

列举了切片中的三元组,这是一个简单的排序和搜索问题,实际上花费O(1)时间(对于任意薄的切片)来找到N之上的第一个三元组。 那么,实际上,对于恒定宽度(对数),切片中的数字量(在log(N)平面下面的(i,j,k)空间中的“上地壳”的成员)也是m ~ n^2/3 ~ (log N)^2并且排序需要m log m时间(因此搜索,甚至线性,然后需要~ m运行时间)。 但是,经过一些实证观察,对于更大的N s,宽度可以做得更小; 并且,三元组枚举的常数因子远远高于随后的排序。

即使有恒定的宽度(logarthmic)它运行速度非常快,计算汉明序列的百万个值即刻在十亿分在0.05秒。

最初的“三重乐队”的想法归功于路易斯·克劳德(Louis Klauder),正如在2008年关于DDJ博客讨论的帖子中所引用的那样。

更新:正如GordonBGood评论中所指出的那样 ,不需要整个乐队,而只需要在目标之上和之下的一两个值。 该算法很容易修改为该效果。 继续算法之前,还应该测试输入是汉明数,以避免双精度的舍入问题。 有没有比较事先知道海明数的对数是不同的(尽管四舍五入问题上升到一万亿分项的顺序在对数值使用约14显著数字,只留下1-2位的空闲时间,所以事实上,情况可能会转向那里;但对于十亿分之一,我们只需要11位有效数字)。

update2:结果是对数的双精度将其限制在低于大约20,000到40,000个十进制数字的数字(即10万亿到100万亿分之一的汉明数)。 如果对于如此大的数字确实需要这个,那么算法可以切换回使用Integer值本身而不是它们的对数,这将更慢。

好的,希望第三次在这里有魅力。 用于p的初始输入的递归分支算法,其中N是在每个线程内“构建”的数字。 这里的NB 3a-c作为单独的线程启动或以其他方式异步完成(准)。

  1. 在p之后计算下一个最大的2的幂,称之为R. N = p。

  2. N> R? 退出这个帖子。 p只由小素因子组成吗? 你完成了。 否则,请转到步骤3。

  3. 在3a-c中的任何一个之后,转到步骤4。

    a)圆形p到最接近的2的倍数。该数字可表示为m * 2。
    b)圆形p到最接近的3的倍数。该数字可表示为m * 3。
    c)圆形p到最接近的5的倍数。该数字可表示为m * 5。

  4. 转到步骤2,p = m。

我省略了关于跟踪N的记账,但我认为这很简单。

编辑:忘了6,谢谢ypercube。

编辑2:如果这达到30,(5,6,10,15,30)意识到这是不必要的,那就把它拿出来。

编辑3 :(我承诺的最后一个!)添加了30次幂检查,这有助于防止此算法占用所有RAM。

编辑4:根据finnw的观察,将30的幂变为2的幂。

这是Python中的一个解决方案,基于Will Ness的答案,但采用一些快捷方式并使用纯整数数学来避免遇到日志空间数值精度错误:

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

在英语中:迭代5s和3s的每个组合,快速找到每对的下一个2> =目标的幂并保持最小的结果。 (如果只有其中一个是正确的,那么迭代每个可能的2的倍数是浪费时间)。 如果它发现目标已经是常规数字,它也会提前返回,尽管这不是绝对必要的。

我已经对它进行了相当彻底的测试,测试了0到51200000之间的每个整数,并与OEIS http://oeis.org/A051037上的列表进行了比较,还有许多大数字,这些数字是普通数字的±1等。 现在是在SciPy中以fftpack.helper.next_fast_len ,以找到FFT的最佳大小( 源代码 )。

我不确定log方法是否更快,因为我无法让它足够可靠地工作来测试它。 我认为它有相似数量的操作? 我不确定,但速度相当快。 花费<3秒(或gmpy为0.7秒)来计算2 142 ×3 80 ×5 444是下一个常规数字2 2 ×3 454 ×5 249 +1(100,000,000个常规数字,其中有392位)

你想找到m >= Nm = 2^i * 3^j * 5^k的最小数m ,其中所有i,j,k >= 0

以对数表示方程式可以改写为:

 log m >= log N
 log m = i*log2 + j*log3 + k*log5

您可以将log2log3log5logN为(足够高,取决于N的大小)准确度。 然后这个问题看起来像一个整数线性编程问题,你可以尝试使用这个NP难问题的已知算法之一解决它。

EDITED / CORRECTED:更正了通过scipy测试的代码:

这是基于endolith的答案的答案 ,但几乎消除了长的多精度整数计算,通过使用float64对数表示来进行基本比较以找到通过标准的三元值,只有在有可能对数时才采用全精度比较值可能不够准确,仅当目标非常接近前一个或下一个常规数时才会出现:

import math

def next_regulary(target):
    """
    Find the next regular number greater than or equal to target.
    """
    if target < 2: return ( 0, 0, 0 )
    log2hi = 0
    mant = 0
    # Check if it's already a power of 2 (or a non-integer)
    try:
        mant = target & (target - 1)
        target = int(target) # take care of case where not int/float/decimal
    except TypeError:
        # Convert floats/decimals for further processing
        target = int(math.ceil(target))
        mant = target & (target - 1)

    # Quickly find next power of 2 >= target
    # See https://stackoverflow.com/a/19164783/125507
    try:
        log2hi = target.bit_length()
    except AttributeError:
        # Fallback for Python <2.7
        log2hi = len(bin(target)) - 2

    # exit if this is a power of two already...
    if not mant: return ( log2hi - 1, 0, 0 )

    # take care of trivial cases...
    if target < 9:
        if target < 4: return ( 0, 1, 0 )
        elif target < 6: return ( 0, 0, 1 )
        elif target < 7: return ( 1, 1, 0 )
        else: return ( 3, 0, 0 )

    # find log of target, which may exceed the float64 limit...
    if log2hi < 53: mant = target << (53 - log2hi)
    else: mant = target >> (log2hi - 53)
    log2target = log2hi + math.log2(float(mant) / (1 << 53))

    # log2 constants
    log2of2 = 1.0; log2of3 = math.log2(3); log2of5 = math.log2(5)

    # calculate range of log2 values close to target;
    # desired number has a logarithm of log2target <= x <= top...
    fctr = 6 * log2of3 * log2of5
    top = (log2target**3 + 2 * fctr)**(1/3) # for up to 2 numbers higher
    btm = 2 * log2target - top # or up to 2 numbers lower

    match = log2hi # Anything found will be smaller
    result = ( log2hi, 0, 0 ) # placeholder for eventual matches
    count = 0 # only used for debugging counting band
    fives = 0; fiveslmt = int(math.ceil(top / log2of5))
    while fives < fiveslmt:
        log2p = top - fives * log2of5
        threes = 0; threeslmt = int(math.ceil(log2p / log2of3))
        while threes < threeslmt:
            log2q = log2p - threes * log2of3
            twos = int(math.floor(log2q)); log2this = top - log2q + twos

            if log2this >= btm: count += 1 # only used for counting band
            if log2this >= btm and log2this < match:
                # logarithm precision may not be enough to differential between
                # the next lower regular number and the target, so do
                # a full resolution comparison to eliminate this case...
                if (2**twos * 3**threes * 5**fives) >= target:
                    match = log2this; result = ( twos, threes, fives )
            threes += 1
        fives += 1

    return result

print(next_regular(2**2 * 3**454 * 5**249 + 1)) # prints (142, 80, 444)

由于已经消除了大多数长时间的多精度计算,因此不需要gmpy,并且在IDEOne上,对于endolith的解决方案,上面的代码需要0.11秒而不是0.48秒,以找到大于第1百万个的下一个常规数字,如图所示; 找到下一个十亿分之一的下一个常规数字(下一个是(761,572,489)过去(1334,335,404) + 1 )需要0.49秒而不是5.48秒,随着范围的增加,差异会变得更大内部版本的精度计算越来越长,而这里几乎没有。 因此,这个版本可以在IDEOne上大约50秒内从序列中的万亿分之一计算下一个常规数字,在那里使用endolith版本可能需要一个多小时。

该算法的英文描述与endolith版本几乎相同,区别如下:1)计算参数目标值的浮点日志估计(我们不能直接使用内置log函数,因为范围可能是对于64位浮点表示来说太大了,2)比较日志表示值来确定估计范围内的限定值,该估计范围高于和低于目标值,只有大约两个或三个数字(取决于舍入),3 )仅在上面定义的窄带内比较多精度值,4)输出三重索引而不是全长多精度整数(对于十亿分之一的十分之一,将是大约840个十进制数字,是万亿分之一的十倍) ),如果需要,可以很容易地将其转换为长多精度值。

除了可能非常大的多精度整数目标值,大约相同大小的中间评估比较值以及三元组的输出扩展(如果需要)之外,该算法几乎不使用任何存储器。 该算法是对endolith版本的改进,因为它成功地使用对数值进行大多数比较,尽管它们缺乏精确度,并且它将比较数字的范围缩小到几个。

根据@WillNess的讨论,由于缺少日志表示值的精确度,该算法将适用于稍微超过10万亿(在IDEOne速率下几分钟的计算时间)的参数范围。 为了解决这个问题,我们可以将日志表示更改为“自行滚动”对数表示,该表示由一个固定长度的整数组成(124位,大约是指数范围的两倍,对于超过十万个数字的目标,如果一个人愿意等待); 由于小的多精度整数运算比float64运算慢,所以这会慢一点,但由于大小有限(可能慢了三倍左右),因此速度要慢一些。

现在这些Python实现中没有一个(不使用C或Cython或PyPy或其他东西)特别快,因为它们比在编译语言中实现的慢大约一百倍。 为了参考,这是一个Haskell版本:

{-# OPTIONS_GHC -O3 #-}

import Data.Word
import Data.Bits

nextRegular :: Integer -> ( Word32, Word32, Word32 )
nextRegular target
  | target < 2                   = ( 0, 0, 0 )
  | target .&. (target - 1) == 0 = ( fromIntegral lg2hi - 1, 0, 0 )
  | target < 9                   = case target of
                                     3 -> ( 0, 1, 0 )
                                     5 -> ( 0, 0, 1 )
                                     6 -> ( 1, 1, 0 )
                                     _ -> ( 3, 0, 0 )
  | otherwise                    = match
 where
  lg3 = logBase 2 3 :: Double; lg5 = logBase 2 5 :: Double
  lg2hi = let cntplcs v cnt =
                let nv = v `shiftR` 31 in
                if nv <= 0 then
                  let cntbts x c =
                        if x <= 0 then c else
                        case c + 1 of
                          nc -> nc `seq` cntbts (x `shiftR` 1) nc in
                  cntbts (fromIntegral v :: Word32) cnt
                else case cnt + 31 of ncnt -> ncnt `seq` cntplcs nv ncnt
          in cntplcs target 0
  lg2tgt = let mant = if lg2hi <= 53 then target `shiftL` (53 - lg2hi)
                      else target `shiftR` (lg2hi - 53)
           in fromIntegral lg2hi +
                logBase 2 (fromIntegral mant / 2^53 :: Double)
  lg2top = (lg2tgt^3 + 2 * 6 * lg3 * lg5)**(1/3) -- for 2 numbers or so higher
  lg2btm = 2* lg2tgt - lg2top -- or two numbers or so lower
  match =
    let klmt = floor (lg2top / lg5)
        loopk k mtchlgk mtchtplk =
          if k > klmt then mtchtplk else
          let p = lg2top - fromIntegral k * lg5
              jlmt = fromIntegral $ floor (p / lg3)
              loopj j mtchlgj mtchtplj =
                if j > jlmt then loopk (k + 1) mtchlgj mtchtplj else
                let q = p - fromIntegral j * lg3
                    ( i, frac ) = properFraction q; r = lg2top - frac
                    ( nmtchlg, nmtchtpl ) =
                      if r < lg2btm || r >= mtchlgj then
                        ( mtchlgj, mtchtplj ) else
                      if 2^i * 3^j * 5^k >= target then
                        ( r, ( i, j, k ) ) else ( mtchlgj, mtchtplj )
                in nmtchlg `seq` nmtchtpl `seq` loopj (j + 1) nmtchlg nmtchtpl
          in loopj 0 mtchlgk mtchtplk
    in loopk 0 (fromIntegral lg2hi) ( fromIntegral lg2hi, 0, 0 )


trival :: ( Word32, Word32, Word32 ) -> Integer
trival (i,j,k) = 2^i * 3^j * 5^k

main = putStrLn $ show $ nextRegular $ (trival (1334,335,404)) + 1 -- (1126,16930,40)

此代码计算在十亿分之一之后的下一个常规数字,在非常小的时间内进行测量,并且在IDEOne上以0.69秒的速度跟随万亿分之一(并且可能运行得更快,除了IDEOne不支持LLVM)。 在JIT编译的“热身”之后,甚至朱莉娅都会以类似Haskell的速度运行。

EDIT_ADD: Julia代码如下:

function nextregular(target :: BigInt) :: Tuple{ UInt32, UInt32, UInt32 }
    # trivial case of first value or anything less...
    target < 2 && return ( 0, 0, 0 )

    # Check if it's already a power of 2 (or a non-integer)
    mant = target & (target - 1)

    # Quickly find next power of 2 >= target
    log2hi :: UInt32 = 0
    test = target
    while true
        next = test & 0x7FFFFFFF
        test >>>= 31; log2hi += 31
        test <= 0 && (log2hi -= leading_zeros(UInt32(next)) - 1; break)
    end

    # exit if this is a power of two already...
    mant == 0 && return ( log2hi - 1, 0, 0 )

    # take care of trivial cases...
    if target < 9
        target < 4 && return ( 0, 1, 0 )
        target < 6 && return ( 0, 0, 1 )
        target < 7 && return ( 1, 1, 0 )
        return ( 3, 0, 0 )
    end

    # find log of target, which may exceed the Float64 limit...
    if log2hi < 53 mant = target << (53 - log2hi)
    else mant = target >>> (log2hi - 53) end
    log2target = log2hi + log(2, Float64(mant) / (1 << 53))

    # log2 constants
    log2of2 = 1.0; log2of3 = log(2, 3); log2of5 = log(2, 5)

    # calculate range of log2 values close to target;
    # desired number has a logarithm of log2target <= x <= top...
    fctr = 6 * log2of3 * log2of5
    top = (log2target^3 + 2 * fctr)^(1/3) # for 2 numbers or so higher
    btm = 2 * log2target - top # or 2 numbers or so lower

    # scan for values in the given narrow range that satisfy the criteria...
    match = log2hi # Anything found will be smaller
    result :: Tuple{UInt32,UInt32,UInt32} = ( log2hi, 0, 0 ) # placeholder for eventual matches
    fives :: UInt32 = 0; fiveslmt = UInt32(ceil(top / log2of5))
    while fives < fiveslmt
        log2p = top - fives * log2of5
        threes :: UInt32 = 0; threeslmt = UInt32(ceil(log2p / log2of3))
        while threes < threeslmt
            log2q = log2p - threes * log2of3
            twos = UInt32(floor(log2q)); log2this = top - log2q + twos

            if log2this >= btm && log2this < match
                # logarithm precision may not be enough to differential between
                # the next lower regular number and the target, so do
                # a full resolution comparison to eliminate this case...
                if (big(2)^twos * big(3)^threes * big(5)^fives) >= target
                    match = log2this; result = ( twos, threes, fives )
                end
            end
            threes += 1
        end
        fives += 1
    end
    result
end

这是我想到的另一种可能性:

如果NX位长,那么最小的规则数R≥N将在范围
[2 X-1 , 2 X ]

例如,如果N = 257(二进制100000001 ),那么我们知道R1 xxxxxxxx除非R恰好等于2的下一个幂(512)

为了生成该范围内的所有常规数,我们可以首先生成奇数正则数(即3和5的幂的倍数),然后取每个值并乘以2(通过位移),根据需要多次它进入这个范围。

在Python中:

from itertools import ifilter, takewhile
from Queue import PriorityQueue

def nextPowerOf2(n):
    p = max(1, n)
    while p != (p & -p):
        p += p & -p
    return p

# Generate multiples of powers of 3, 5
def oddRegulars():
    q = PriorityQueue()
    q.put(1)
    prev = None
    while not q.empty():
        n = q.get()
        if n != prev:
            prev = n
            yield n
            if n % 3 == 0:
                q.put(n // 3 * 5)
            q.put(n * 3)

# Generate regular numbers with the same number of bits as n
def regularsCloseTo(n):
    p = nextPowerOf2(n)
    numBits = len(bin(n))
    for i in takewhile(lambda x: x <= p, oddRegulars()):
        yield i << max(0, numBits - len(bin(i)))

def nextRegular(n):
    bigEnough = ifilter(lambda x: x >= n, regularsCloseTo(n))
    return min(bigEnough)

我写了一个小的c#程序来解决这个问题。 它不是很优化,但它是一个开始。 对于大到11位的数字,此解决方案非常快。

private long GetRegularNumber(long n)
{
    long result = n - 1;
    long quotient = result;

    while (quotient > 1)
    {
        result++;
        quotient = result;

        quotient = RemoveFactor(quotient, 2);
        quotient = RemoveFactor(quotient, 3);
        quotient = RemoveFactor(quotient, 5);
    }

    return result;
}

private static long RemoveFactor(long dividend, long divisor)
{
    long remainder = 0;
    long quotient = dividend;
    while (remainder == 0)
    {
        dividend = quotient;
        quotient = Math.DivRem(dividend, divisor, out remainder);
    }
    return dividend;
}

你知道吗? 我会把钱投入到这个命题上,实际上,'哑'算法是最快的。 这是基于以下观察:下一个常规数字通常看起来不比给定的输入大得多。 因此,只需开始计数,并在每次增量后重构,看看你是否找到了常规数字。 但是为每个可用的核心创建一个处理线程,对于N个核心,每个线程检查每个第N个数字。 当每个线程找到一个数字或超过2的幂阈值时,比较结果(保持运行中的最佳数字),然后就可以了。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM