簡體   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