如何在合理的时间内将绝对庞大的数字转换为字符串?
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.15663290024
、f2: 7.74465799332
、f3: 0.276544809341
、f4: 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 秒来计算素数,将其转换为十进制,然后将十进制数字写入文件。以上是关于如何在合理的时间内将绝对庞大的数字转换为字符串?的主要内容,如果未能解决你的问题,请参考以下文章