最有效的方法来获得任意大数字的数字计数

时间:2014-07-28 23:12:02

标签: haskell numbers digit

获取数字数字的最有效方法是什么?

让我们从一个例子开始:

想象一下Fibonacci序列。现在让我们说我们想知道哪个Fibonacci数是第一个有1000个数字(以10为基数表示)。最多308位数(第1476个Fibonacci数)我们可以使用logBase 10 <number>轻松完成此操作。如果该数字大于第1476个Fibonacci数,logBase将返回Infinity,计算将失败。问题是308距离1000有点远,这是我们最初的目标。

一种可能的解决方案是将我们想要知道的数字转换为字符串的数字,并使用它的长度来确定数字计数。对于我的目的来说这有点效率低,因为用10000来尝试它会花费它的甜蜜时间。

other questions中显示的最有效的方法是硬编码我确实不想做的所有可能情况,特别是因为在建议的解决方案中需要的位数超过10。

回到我的问题:确定基数为10的数字计数的最佳(最有效)方法是什么?是真的将它转换为字符串并使用它的长度,还是有任何&#34;黑客&#34;像0x5f3759df这样的技巧?

注意:我很欣赏任何语言的解决方案,即使它被标记为&#34; haskell&#34;。

4 个答案:

答案 0 :(得分:7)

为什么不使用div,直到它不再大于10?

digitCount :: Integer -> Int
digitCount = go 1 . abs
    where
        go ds n = if n >= 10 then go (ds + 1) (n `div` 10) else ds

O(n)复杂度为n,其中1000是位数,您可以通过查看100,然后10,然后{{}来轻松加快速度。 1}},但这可能足以满足大多数用途。


作为参考,在我不太好的笔记本电脑上运行它只在GHCi中运行并使用非常不准确的:set +s统计标记:

> let x = 10 ^ 10000 :: Integer
> :force x
<prints out 10 ^ 10000>
> digitCount x
10001
it :: Int
(0.06 secs, 23759220 bytes)

所以看起来非常快,它可以在不到10秒的时间内通过一个10001位的数字而无需优化。


如果你真的想要O(log(n))的复杂性,我建议你写一个你自己的版本,每次除以2,但是这个版本比10除以10更多参与和棘手。为了你的目的这个版本将轻松计算最多约20000位数字的位数而没有问题。

答案 1 :(得分:2)

如果您只想查找列表中至少有digitCount位数的第一个数字,可以通过检查O(1)来测试fibBeingTested >= 10digitCount - 1中的每个数字。这是有效的,因为10digitCount - 1是具有至少digitCount位数的最低数字:

import Data.List (find)

fibs :: [Integer]
-- ...

findFib :: Int -> Integer
findFib digitCount =
  let Just solution = find (>= tenPower) fibs
  in
  solution
  where
    tenPower = 10 ^ (digitCount - 1)

我们使用digitCount - 1,因为10^110,有两位数。

由于此比较具有O(1)复杂性,您可以非常快速地找到斐波那契数字。在我的机器上:

λ> :set +s
λ> findFib 10000
[... the first Fibonacci number with at least 10,000 digits ...]
(0.23 secs, 121255512 bytes)

如果fibs的列表已经计算到10,000th数字斐波纳契(例如,如果你运行findFib 10000两次),它的速度会更快,这表明更多的计算是计算每个斐波纳契数而不是找到你要找的那个:

λ> findFib 10000   -- Second run of findFib 10000
[... the first Fibonacci number with at least 10,000 digits ...]
(0.04 secs, 9922000 bytes)

答案 2 :(得分:0)

如果只是为了获得超过1000位的斐波那契数字,length . showInteger}就足够了。

GHCi> let fibs = Data.Function.fix $ (0:) . scanl (+) 1
GHCi> let digits = length . (show :: Integer -> String)
GHCi> :set +t +s
GHCi> fst . head . dropWhile ((1000>) . digits . snd) $ zip [0..] fibs
4782
it :: Integer
(0.10 secs, 149103264 bytes)

对于浮点数(因此可以使用logBase),在Double numbers包的范围之外。它们的速度很慢,但你必须为这种准确性付出代价。

答案 3 :(得分:0)

您总是可以尝试使用二进制搜索来查找 n 的位数:首先找到 k ,使得10 ^ 2 ^k≥n,然后除以n连续10 ^ 2 ^(k-1),10 ^ 2 ^(k-2),...,10 ^ 2 ^ 0:

numDigits n = fst $ foldr step (1,n) tenToPow2s
  where
    pow2s = iterate (*2) 1
    tenToPow2s = zip pow2s . takeWhile (<=n) . iterate (^2) $ 10
    step (k,t) (d,n) = if n>=t then (d+k, n `div` t) else (d,n)

对于Fibonacci数的特定情况,您也可以尝试数学: n -th Fibonacci数F(n)介于(φ^ n-1)/√5和(φⁿ+之间) 1)/√5所以对于基数10的对数,我们有:

  

log(F(n)) - n log(φ)+ log(√5)∈[log(1 - 1 /φⁿ),log(1 + 1 /φⁿ)]

这个间隔很快就会消失。