任意有限十进制数字串,都能在某个 2 的幂中作为连续片段出现。Python 暴力搜索可以找到很多例子,但有限实验不能证明“所有数字串”都成立;数学解释来自 log10(2) 是无理数,以及 n × log10(2) 的小数部分在 0 到 1 之间稠密分布。
内容说明:旧版文章大段转载知乎证明,并热链 134 张公式图片。本次改为原创概述和可运行代码,只保留原知乎文章作为延伸阅读。
问题到底在问什么
给定一个有限数字串,例如 31415、20260714 或 000123,是否存在自然数 n,使十进制表示的 2^n 中包含这段连续字符?这里问的是“至少出现一次”,不要求出现在开头。
最直接的 Python 暴力搜索
def first_power_containing(target, max_n=10_000):
target = str(target)
value = 1
for n in range(max_n + 1):
if target in str(value):
return n, value
value <<= 1
return None
result = first_power_containing("31415", 10_000)
print(result)
这里每轮把当前数左移一位,相当于乘以 2,避免重复计算 2 ** n。示例数字串 31415 会在 2^144 中出现。
为什么旧程序会越来越慢
n增大后,2^n的十进制位数近似按n × log10(2)增长。- 每一轮都把大整数转换成字符串,再执行子串搜索,时间和内存都会增加。
- Python 3.11+ 默认还会限制超长整数转字符串,暴力搜索很大指数时可能直接报位数限制错误。
- 为 0 到 599999 每个目标都从
n=0重新搜索,会重复做大量工作。 - 把所有大整数写入文本文件可能生成数百 MB 甚至更大的结果,但仍不能构成数学证明。
先研究“出现在开头”会更简单
若一个 L 位正整数 m 是 2^n 的开头,那么 n × log10(2) 的小数部分会落进一个很短的区间:
log10(m) - (L - 1)
<= fractional(n * log10(2))
< log10(m + 1) - (L - 1)
flowchart LR
A["选择目标前缀 m"] --> B["计算对应对数区间"]
B --> C["遍历 n × log10(2) 的小数部分"]
C --> D{"落入目标区间?"}
D -->|"是"| E["2 的 n 次方以 m 开头"]
D -->|"否"| C
用对数搜索指定前缀
import math
def first_power_starting(prefix, max_n=1_000_000):
text = str(prefix)
if not text.isdigit() or text.startswith("0"):
raise ValueError("prefix 必须是不以 0 开头的十进制数字")
number = int(text)
digits = len(text)
low = math.log10(number) - (digits - 1)
high = math.log10(number + 1) - (digits - 1)
alpha = math.log10(2)
for n in range(max_n + 1):
fractional = (n * alpha) % 1
if low <= fractional < high:
return n
return None
这种写法不需要生成巨大的 2^n,适合搜索前缀。它使用浮点数,只适合作为实验;目标区间非常窄或需要严谨证明时,应使用更高精度计算并检查边界误差。
数学上为什么一定能找到
log10(2) 是无理数。对无理数 α,序列 {nα} 的小数部分会在区间 [0, 1) 中稠密分布。任何目标前缀都对应这个区间中的一个非空小区间,因此总能找到某个 n 落进去,让 2^n 以指定数字开头。
“作为开头出现”比“在任意位置出现”更强。只要每个有限数字串都能构造为某个幂的前缀或前缀的一部分,它自然也会作为连续片段出现。
以 0 开头的数字串怎么办
十进制整数不会以 0 开头,因此不能直接要求 2^n 以 000123 开头。可以改为寻找前缀 1000123;一旦找到,这个幂的第 2 位开始就包含目标串 000123。
实验能证明什么,不能证明什么
- 代码可以找到具体例子、发现性能瓶颈、验证数学结论与实现是否一致。
- 搜索到 60 万个目标,只能证明这些目标已经找到,不能自动推广到所有有限数字串。
- 设定最大指数后返回
None,只代表搜索范围内没找到,不代表永远不存在。 - 真正的普遍性结论依赖数学证明,而不是输出文件大小。