如何在合理的时间内将绝对庞大的数字转换为字符串?

Posted

技术标签:

【中文标题】如何在合理的时间内将绝对庞大的数字转换为字符串?【英文标题】:How can I convert an absolutely massive number to a string in a reasonable amount of time? 【发布时间】:2016-04-28 10:26:00 【问题描述】:

我知道这是一个很奇怪的问题,但我正在尝试获取文件中当前最大素数的副本。以整数形式获取数字相当容易。我只是运行它。

prime = 2**74207281 - 1

大约需要半秒钟,它工作得很好。操作也相当快。将其除以 10(不带小数)以快速移动数字。但是,str(prime) 需要很长时间。我像这样重新实现了str,发现它每秒处理大约一百个数字。

while prime > 0:
    strprime += str(prime%10)
    prime //= 10

有没有办法更有效地做到这一点?我正在用 Python 做这个。我应该用 Python 试试这个,还是有更好的工具呢?

【问题讨论】:

此解决方案将以相反的顺序为您提供数字。 我怀疑一个朴素的 Python 实现会比在 C 端实现的 str 更快(这可能也是相当优化的)。 @HugoRune 一个快速的速度测试表明,除以一个巨大的数字比除以 10 快一个数量级。 (比如说,100000000000000000000000000000000000000000000000000000000。) 如果你用 base 16 打印就不会那么麻烦了。 如果您可以使用 Python 以外的其他工具,在 PARI/GP 计算器(我使用新的 64 位 Windows 版本)中,您可以说 write("primedigits.txt", 1<<74207281 - 1),它将在 7 秒内将所有内容写入文件(在我的机器上)。不确定在 I/O 上花费了多少时间。如果您的默认堆栈大小不够大,您可能需要在计算数字之前执行allocatemem(10^9)(单独一行)。使用符号2^74207281 - 1 也可以快速计算。 【参考方案1】:

众所周知,重复的字符串连接效率低下,因为 Python 字符串是不可变的。我会去的

strprime = str(prime)

在我的基准测试中,这始终是最快的解决方案。这是我的小基准程序:

import decimal

def f1(x):
    ''' Definition by OP '''
    strprime = ""
    while x > 0:
        strprime += str(x%10)
        x //= 10
    return strprime

def digits(x):
    while x > 0:
        yield x % 10
        x //= 10

def f2(x):
    ''' Using string.join() to avoid repeated string concatenation '''
    return "".join((chr(48 + d) for d in digits(x)))

def f3(x):
    ''' Plain str() '''
    return str(x)

def f4(x):
    ''' Using Decimal class'''
    return decimal.Decimal(x).to_eng_string()

x = 2**100

if __name__ == '__main__':
    import timeit
    for i in range(1,5):
        funcName = "f" + str(i)
        print(funcName+ ": " + str(timeit.timeit(funcName + "(x)", setup="from __main__ import " + funcName + ", x")))

对我来说,这会打印(使用 Python 2.7.10):

f1: 15.3430171013
f2: 20.8928260803
f3: 0.310356140137
f4: 2.80087995529

【讨论】:

附加列表会更有效吗? 感谢基准测试。我不知道它有那么大的不同。我试图避免使用 str() 的原因是它没有取得任何进展。我想我得硬着头皮去用 str() 了。 @user1193112 PyPy 使用上述基准(f1: 4.15663290024f2: 7.74465799332f3: 0.276544809341f4: 0.298784971237)给出了更好的结果,因此值得一试。仅供参考,文字中的数字大约为 22MiB。 @Jason 我忘记了 PyPy 的存在!谢谢! +1。但是缓慢与字符串的不可变性质无关。 (最近版本的 Python 优化了对字符串的重复追加。旧版本每次追加一个字符串对象,花费 O(n**2) 时间!)基本问题是 Python 代码的运行速度比 C 代码慢约 50 倍,因此用 Python 编码的循环总是比使用 Python 内置的 C 慢。使用 str() 将所有工作都放在 Python 的内置 C 代码上,并且应该始终是在 Python 中完成此工作的最快方式。【参考方案2】:

Python 的整数到字符串的转换算法使用运行 O(n**2) 的简单算法。随着数字的长度翻倍,转换时间翻了两番。

在我的电脑上进行的一些简单测试显示运行时间有所增加:

$ time py35 -c "n=str(2**1000000)"
user    0m1.808s
$ time py35 -c "n=str(2**2000000)"
user    0m7.128s
$ time py35 -c "n=str(2**4000000)"
user    0m28.444s
$ time py35 -c "n=str(2**8000000)"
user    1m54.164s

由于实际指数大约是我上次测试值的 10 倍,所以它应该需要大约 100 倍的时间。或者只是 3 多小时。

可以做得更快吗?是的。有几种方法更快。

方法一

将非常大的数除以 10 的幂成两个大致相等但较小的数会更快。重复该过程,直到数量相对较小。然后在每个数字上使用str(),并使用前导零将结果填充到与最后一个 10 的幂相同的长度。然后将字符串连接起来形成最终结果。 mpmath 库使用此方法,文档暗示它应该快 3 倍左右。

方法二

Python 的整数以二进制格式存储。二进制非常适合计算,但二进制到十进制的转换是瓶颈。可以定义自己的整数类型,将值存储在 100 个(或一些类似的值)十进制数字块中。运算(取幂、乘法、除法)会较慢,但转换为字符串会非常快。

多年前,我实现了这样一个类,并使用高效的乘法和除法算法。该代码在 Internet 上不再可用,但我确实找到了我测试过的备份副本。运行时间减少到约 14 秒。

更新

我更新了上面引用的 DecInt 代码,现在可以在 https://github.com/casevh/DecInt 找到它。

如果使用 Python 原生的整数类型,在我的电脑上总运行时间不到 14 秒。如果改用gmpy2的整数类型,则运行时间约为3.5秒。

$ py35 DecInt.py
Calculating 2^74207281
Exponentiation time: 3.236
Conversion to decimal format: 0.304
Total elapsed time: 3.540
Length of result: 22338618 digits

方法3

我维护gmpy2 库,该库可轻松访问 GMP 库以实现快速整数运算。 GMP 在高度优化的 C 和汇编代码中实现方法 1,并在约 5 秒内计算素数和字符串表示。

方法四

Python 中的decimal 模块将值存储为十进制数字。最新版本的 Python 3 包含十进制库的 C 实现,它比 Python 2 中包含的纯 Python 实现快得多。C 实现在我的计算机上运行只需 3 秒多一点。

from decimal import *
getcontext().prec = 23000000
getcontext().Emin = -999999999
getcontext().Emax = 999999999
x=Decimal(2)**74207281 - 1
s=str(x)

【讨论】:

【参考方案3】:

使用 WinGhci(Haskell 语言)输出文件大约需要 32 秒:

import System.IO

main = writeFile "prime.txt" (show (2^74207281 - 1))

该文件为 21 兆字节;最后四位数字,6351。

【讨论】:

不可能,这是一个素数,没有以 4 结尾的素数 @Copperfield 哎呀,6351 怎么样?【参考方案4】:

有 gmp,即 GNU 多精度算术库。 它专为快速处理大量数据而设计。

【讨论】:

它是否能以同样快的速度从大数字转换为字符串?所涉及的数学并不难,困扰我的是从整数到字符串的转换。 我没有任何基准测试 - 但是有转换函数:gmplib.org/manual/I_002fO-of-Integers.html#I_002fO-of-Integers 甚至还有一个网页展示了某人如何将它与 python 进行比较和耦合:jasonstitt.com/c-extension-n-choose-k 我只是碰巧用 GMP 计算了那个素数。我的 2 GHz i7 花了 5.5 秒来计算素数,将其转换为十进制,然后将十进制数字写入文件。

以上是关于如何在合理的时间内将绝对庞大的数字转换为字符串?的主要内容,如果未能解决你的问题,请参考以下文章

如何将二进制表示的数字转换为雪花数

将数字数组转换为二进制数

java中如何将Timestamp转换为毫秒数

Javascript如何将十进制数转换为具有特定小数位数的字符串

python中的函数

如何实现秒数和日期之间的转换