2 的 n 次方能包含任意十进制序列吗?Python 实验与数学解释

任意有限十进制数字串,都能在某个 2 的幂中作为连续片段出现。Python 暴力搜索可以找到很多例子,但有限实验不能证明“所有数字串”都成立;数学解释来自 log10(2) 是无理数,以及 n × log10(2) 的小数部分在 0 到 1 之间稠密分布。

内容说明:旧版文章大段转载知乎证明,并热链 134 张公式图片。本次改为原创概述和可运行代码,只保留原知乎文章作为延伸阅读。

问题到底在问什么

给定一个有限数字串,例如 3141520260714000123,是否存在自然数 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 位正整数 m2^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^n000123 开头。可以改为寻找前缀 1000123;一旦找到,这个幂的第 2 位开始就包含目标串 000123

实验能证明什么,不能证明什么

  • 代码可以找到具体例子、发现性能瓶颈、验证数学结论与实现是否一致。
  • 搜索到 60 万个目标,只能证明这些目标已经找到,不能自动推广到所有有限数字串。
  • 设定最大指数后返回 None,只代表搜索范围内没找到,不代表永远不存在。
  • 真正的普遍性结论依赖数学证明,而不是输出文件大小。

延伸阅读与站内 Python 实践