【问题标题】:Most efficient way to get digit count of arbitrarily big number获得任意大数字的位数的最有效方法
【发布时间】:2014-07-28 23:12:01
【问题描述】:

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

让我们从一个例子开始:

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

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

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

所以回到我的问题:确定以 10 为基数的位数的最佳(最有效)方法是什么?是真的将其转换为字符串并使用它的长度还是有像0x5f3759df 这样的“黑客”技巧?

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

【问题讨论】:

  • ~308 位听起来像是 64 位的双重限制..
  • @user2864740 是的,它确实是 64 位双精度的最大可能值。然而,Haskell 支持任意长的数字(使用 Integer),这就是为什么我的问题包括更大的数字。

标签: haskell numbers digit


【解决方案1】:

为什么不使用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 是位数,您可以通过检查100010010 来轻松加快速度,但这可能就足够了大多数用途。


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

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

所以它看起来相当快,它可以在不到十分之一秒的时间内处理一个 10001 位的数字而无需优化。


如果您真的想要 O(log(n)) 复杂性,我建议您编写自己的版本,每次除以 2,但这个版本比除以 10 更复杂,更棘手。为了您的目的,这个版本很容易计算最多约 20000 位的位数没有问题。

【讨论】:

  • 忘记了那个方法,昨天可能已经太晚了 :D 不管怎样,对于斐波那契数列中比10^10000 更大的数字甚至更大,你会怎么做呢?难道没有办法轻松确定a^x 形式的函数的log10,其中斐波那契数a 等于黄金比例?
  • @ThreeFx 我想你有点困惑,因为phi ^ x 不会是斐波那契数。黄金比例与斐波那契数的关系是,当您接近无穷大时,斐波那契数列的相邻元素之间的比率接近phi。如果你想加快速度,可能你会得到的最好的方法是找出精确的细节来知道digitCount n 大约等于2 * digitCount (2 * sqrt n),而不是除以几百万次,你最终只需要平方根几百而不是。
  • 作为“优化”,不是每次除以 10,而是除以 10^300,一旦得到小于 10^300 的值,请使用 logBase 10。我不知道这样会不会更快,我想这取决于logBase 函数的速度。
【解决方案2】:

如果您只想在列表中找到第一个至少包含digitCount 数字的数字,您可以通过检查fibBeingTested &gt;= 10<sup>digitCount - 1</sup> 来测试O(1) 中的每个数字。这有效,因为10<sup>digitCount - 1</sup> 是具有至少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^1,例如,是10,它有两个数字。

由于这种比较具有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)

【讨论】:

    【解决方案3】:

    对于一个超过 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)
    

    对于 Double 范围之外的浮点数(因此您可以使用 logBase),请查看 numbers 包。它们的速度非常慢,但您确实需要为这种准确性付出一些代价。

    【讨论】:

      【解决方案4】:

      你总是可以尝试二分查找来找到 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)
      

      对于斐波那契数的特定情况,您也可以尝试数学:第 n 个斐波那契数 F(n) 介于 (φ^n-1)/√5 和 (φⁿ+ 1)/√5 所以我们有以 10 为底的对数:

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

      这个间隔马上变小了。

      【讨论】:

        猜你喜欢
        • 2019-08-24
        • 2011-08-14
        • 1970-01-01
        • 2018-05-02
        • 1970-01-01
        • 2012-03-21
        • 2012-04-26
        • 2016-02-22
        • 2021-03-17
        相关资源
        最近更新 更多