【问题标题】:What is an efficient way to convert a bignum type structure to a human readable string?将 bignum 类型结构转换为人类可读字符串的有效方法是什么?
【发布时间】:2010-07-23 21:32:50
【问题描述】:

我有点问题。为了增加我的 C 知识,我决定尝试实现一个基本的 bigint 库。

bigint 结构的核心将是一个 32 位整数数组,之所以选择它们是因为它们适合寄存器。这将允许我在 64 位整数中溢出的数字之间进行操作(这也将适合寄存器,因为我在 x86-64 上),并且我可以位移出结果的每个部分。我已经实现了基本的加法,为了测试它是否工作,我必须打印数组。出于我自己的测试目的,如果我使用printf() 并以十六进制输出每个数字就可以了。我可以很好地阅读。

但是,大多数人无法阅读十六进制。由于数字存储在(基本上)以 2^32 为基数,因此打印有点问题。什么是转换为 base 10 的好方法?

编辑:

这不是关于知道如何从基础转换到基础,而是关于实现这一点的好方法。我正在考虑用另一个带有打印转换的基础制作另一个 bigint。

【问题讨论】:

  • 要测试 bignum 实现,我建议使用常见的 lisp 实现,例如 Clisp。 Common lisp 内置了 bignums,您只需启动解释器和类型表达式即可。您无需了解 lisp 即可执行基本操作,例如加法(+ 1 2 3) 或求幂(expt 42 10000)。十六进制常量是#x,后跟数字。 (= #x10 16) 显示T 为真,(= #x10 10) 显示NIL 为假。
  • 哦,如果您知道 Emacs 是它的计算器 (M-x calc),那么还有一个更简单的参考 bignum 实现。
  • @Giles:感谢您的想法,尽管我目前正在使用 GCHi 进行测试。不过,这是个好主意
  • bcdc 实用程序也进行 bignum 计算。

标签: c biginteger c99


【解决方案1】:

首先,如果没有基本操作(例如除法和模数),您就无法以合理的方式进行 I/O。为了提供将 bigint 转换为 base-10 字符串的有效实现,我正在研究两种可能的优化:

首先,您可以除以十的幂,而不是精确的十。这意味着,例如,每次将数字除以 10000 时,您将得到四个以 10 为基数的数字。

其次,你会如何选择除以十的幂? 10、100、1000、10000等...
似乎有一个不错的选择,即可以适合您的单词(32 位)的 10 的最大幂。幸运的是,与使用两个“bigint”相比,您可以通过一个单词更有效地实现除法/模数。

我还没有给出实现,因为我还在空闲时间研究这个问题,因为我已经在我的库中实现了基本操作,希望下一步是 I/O ;)

【讨论】:

  • +1,如果我没记错我的 Knuth,这是推荐的方法。
  • +1 并被接受。这几乎是我正在寻找的。我知道 IO 是为了以后,但这是一个需要解决的问题,所以我想我还是问问吧。
【解决方案2】:

除以适合您的基本类型的 10 的最大幂是最好的开始方式。在您的情况下,这将除以 10 ^ 9。此代码应该是通用的,因为您可以将它重用于您的通用除法/模代码的一部分。

运行时间为 O(n^2)(即,如果您的数字是原来的两倍,则转换时间会延长四倍),但对于中等大小的数字来说应该足够快。

对于非常大的值,您需要缓存 10 的大幂,例如 10^1000、10^2000、10^4000、10^8000 ......,然后除以 10 的幂,即大于或等于您尝试转换的数字的 1/2。重复此过程,直到数字小到可以使用除以 10^9 快速转换。根据您的除法算法的效率,这种方法可能不会更快,直到您遇到超过一百万位或更多的数字。

如果您正在编写一个将显示每个数字的交互式计算器,那么使用以 10^9 为底的显示速度会更快(它将是 O(n),即如果您的数字是两倍大,则转换只会需要两倍的时间)。

【讨论】:

    【解决方案3】:

    重复除以 10 的正常方法显然会非常缓慢。

    一个明显的快速方法是预先计算出与每个位置的每个数字的值相对应的 bigint 数组。然后,您可以进行二进制搜索和相对便宜的比较/减法来找到 ms 数字,然后依次找到每个数字。

    当您下降到最后 32(或 64)位时,您可以恢复除以 10。

    【讨论】:

    • 您打算缓存每个 bigint?它们有无数个。
    • @recursive- 不是所有可能的 bigint。只是所有可能的 bigint,其中除 1 之外的所有数字都为零。因此,如果最大的 bigint 会产生一个以 10 为底的 28 位数字,那么您将缓存 280 个值。
    • @crhisarris:对于大多数典型的 bigint 使用,与更复杂的操作(例如将两个 bignum 相乘(或更糟糕的是,将它们的矩阵相乘)或测试它们)相比,重复除以 10 的成本是微不足道的为素数。
    • 我想我理解你的方法,但我不确定它会比每一步除以 10 更快。请记住,占用 k 个 32 位字的两个 bigint 的比较需要 k 步。
    • @GregS - 比较不需要 k 步 - 您正在查看一个数字是否小于另一个数字,因此您可以在它们不同的第一步停止。显然减法需要 k 步,但每个非零数字只执行一次。
    【解决方案4】:

    我能想到的最有效的算法如下。它应该具有 O(n·(log n)²·log log n) 的运行时复杂度,而不是具有二次运行时复杂度的朴素算法。

    1. 不失一般性假设数字 A 的长度为 2n+1 位。它可能有前导零。
    2. 如果这是最高递归级别,则通过重复平方计算 i 到 n 的数字 22i 的十进制表示。
    3. 将输入数的位序列分为 B 和 C 两部分。具有较低有效位的部分 C 包含 A 的 2n 个最低有效位,而 B 部分为剩余的更重要的位。
    4. 将 B 和 C 转换为其十进制表示,或者使用 如果它们足够短,则为二次运行时算法,或者通过递归调用此算法。
    5. 将 B 的十进制表示乘以缓存的 22n 的十进制表示,再将 C 的十进制表示相加得到 A 的十进制表示。

    在第 2 步和第 5 步中,您需要一个十进制乘法算法。对于数万位数的数字,您应该使用以 10 为底的 Schönhage-Strassen 算法版本。这将导致上述运行时复杂性。对于较短的数字,根据其长度,应使用 Tom-Cook 算法、Karatsuba 算法或长乘法。但是,我目前无法说明如何以 10 为底实现 Schönhage-Strassen 算法,因为我能找到的所有完整描述都是以 2 为底的,而且我自己也没有足够的数论来推导它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-31
      • 1970-01-01
      • 2020-10-24
      • 2012-09-29
      • 2012-03-25
      • 2013-02-21
      • 2012-03-09
      • 1970-01-01
      相关资源
      最近更新 更多