三个计算ln(2)的python程序 - l1t1/note GitHub Wiki
1.利用AGM算法
这是在博客园:计算自然对数的快速算法基础上,让DeepSeek译成利用gmpy2库的Python, 并且将原文预计算常量π、ln(10), 改写为用DeepSeek给出的Brent-Salamin方法(Fast Multiple-Precision Evaluation of Elementary Functions RICHARD P. BRENT)AGM计算π函数、前面介绍过的二进制分裂+阶乘逆计算e函数,最终计算ln(2)的算法出自(T. Sasaki and Y. Kanada, Practically fast multiple-precision evaluation of log(x), J. Inf. Process. 5 (1982), 247–250.),从而实现不依赖高精度库内置常量的程序。因为是拼凑的,所以格式有点乱。
import gmpy2
from gmpy2 import mpz, mpfr, get_context
import time
import math
def agm_pi(prec: int) -> mpfr:
"""完全修正的AGM计算π实现"""
#ctx = gmpy2.get_context()
#ctx.precision = int(1.1 * prec) # 额外10%精度缓冲
# 初始化
a = mpfr(1)
b = 1 / gmpy2.sqrt(mpfr(2))
t = mpfr(0.25)
p = mpfr(1)
# 动态计算阈值
threshold = mpfr(2)**(-int(prec * 0.9)) # 关键修正
for _ in range(1000000):#int(log2(prec)) + 5):
a_new = (a + b) / 2
t -= p * (a - a_new)**2
a, b, p = a_new, gmpy2.sqrt(a*b), 2*p
# 动态收敛判断
if abs(a - b) < threshold:
break
pi_approx = (a + b)**2 / (4 * t)
return pi_approx
def calculate_e_terms(l, r):
if l == r:
return mpz(1), mpz(l)
mid = (l + r) // 2
p1, q1 = calculate_e_terms(l, mid)
p2, q2 = calculate_e_terms(mid + 1, r)
return p1 * q2 + p2, q1 * q2
def calculate_e_fast(n, target_digits):
"""优化版:纯整数运算生成小数部分"""
# 计算所需二进制精度
ln_n_fact = float(n * math.log(n) - n + 0.5*math.log(2*math.pi*n))
prec_bits = int((ln_n_fact + math.log(n)) / math.log(2)) + 128
get_context().precision = prec_bits
p, q = calculate_e_terms(2, n)
power = mpz(10)**target_digits
decimal_part = (p * power) // q # 关键优化点
return mpfr("2."+str(decimal_part))
def find_required_terms(digits):
"""计算所需最小n(使用斯特林公式)"""
ln10 = math.log(10)
n = 1
while True:
ln_n_fact = n * math.log(n) - n + 0.5*math.log(2*math.pi*n)
if (ln_n_fact - n * ln10) > digits * ln10:
return n
n += 1
# 初始化计算环境
def init_ln(precision=1000):
"""初始化高精度对数计算环境"""
# 设置计算精度(比特位数 = 十进制位数 * log2(10) + 安全余量)
get_context().precision = int(precision * 3.33)
prec= int(precision * 3.33)
# 定义全局常量
global pi2, ln10, eps2, eps1, e
pi2 = agm_pi(prec)*2#gmpy2.const_pi() * 2
ln10 = gmpy2.log(10)
#e=gmpy2.exp(1)
t=time.time()
#e = (1 + 1/mpfr(2)**(prec//2)) ** mpfr(2)**(prec//2)
e=calculate_e_fast(find_required_terms(precision),precision )
get_context().precision = int(precision * 3.33)
#error=e-gmpy2.exp(1)
#print(f" 误差: {error:.3e}")
print(float(e), time.time()-t, "s")
#exit()
eps2 = mpfr(10) ** (-precision)
eps1 = eps2 * eps2
def log(x):
"""计算自然对数ln(x)"""
if x <= 0:
raise ValueError("Must be positive")
if x == 1:
return mpfr(0)
x = mpfr(x)
k = 0
# 范围调整(将x规范到(0.01, 0.1]区间)
while x > 0.1:
x /= e
k += 1
while x <= 0.01:
x *= e
k -= 1
return k - negative_log(x)#k * ln10 - negative_log(x)
def negative_log(q):
"""辅助函数:计算(0.01, 0.1]区间内的对数"""
q = mpfr(q)
r = q
s = q
n = q
q2 = q * q
q1 = q2 * q
p = True
while (n * q1) > eps1:
n *= q1
s += n
q1 *= q2
r += n if (p := not p) else -n
u = 1 - 2 * r
v = 1 + 2 * s
t = u / v
a = mpfr(1)
b = gmpy2.sqrt(1 - t * t * t * t)
while (a - b) > eps2:
t = (a + b) / 2
b = gmpy2.sqrt(a * b)
a = t
return pi2 / (a + b) / v / v
def log10(x):
"""计算常用对数log10(x)"""
return log(x) / ln10
# 使用示例
if __name__ == "__main__":
# 初始化(精度设为1000位)
init_ln(100000)
# 测试计算
num = mpfr(2)#"2.718281828459045")
result=log(num)
print(str(result)[:100002])
#print(f"ln({num}) = {len(str(result))}")
#print(f"log10(100) = {log10(100)}")
#exact = gmpy2.log(mpfr(2))
#print(f"与内置函数差异: {abs(result - exact):.3e}")
# 验证1000位精度
#high_prec_num = mpfr("3.141592653589793238462643383279")
#print(f"ln(π) 前50位: {str(log(high_prec_num))[:50]}...")
2.利用Zuniga-II二进制分裂法
本程序和下一个程序均来自论文Fast Log() calculation for Arbitrary Precision number, 由DeepSeek一次性翻译完成,除了因为这个高精度库函数的精度采取的是二进制长度,做了*3.33的处理,其余一字未改。
import gmpy2
from gmpy2 import mpz, mpfr, get_context
import math
from functools import lru_cache
def compute_ln2_zuniga(precision):
"""基于Zuniga-II算法的ln(2)高精度计算"""
# 1. 参数初始化
ctx = get_context()
kmax = int(math.ceil(precision * math.log(10) / math.log(3888))) + 10 # 安全增量
workprec = int(precision + 2 * math.log(kmax + 1)) # 增强工作精度
# 2. 设置GMP上下文
ctx.precision = int(workprec ) + 256 # 二进制精度+额外缓冲
# 3. 定义二进制分裂递归函数
@lru_cache(maxsize=None)
def binary_split(a, b):
if a + 1 == b:
b6 = 6 * b
bb = mpz(b)
# 计算三项式系数
q = mpz(216) * mpz(b6 - 1) * mpz(b6 - 5)
r = mpz(b) * mpz(2 * b - 1)
p = mpz(1794) * bb - mpz(297)
return p, q, r
# 区间分裂
mid = (a + b) // 2
pL, qL, rL = binary_split(a, mid)
pR, qR, rR = binary_split(mid, b)
# 合并结果 (p = pL*qR + pR*rL)
return pL*qR + pR*rL, qL*qR, rL*rR
# 4. 执行主计算
p, q, _ = binary_split(0, kmax)
q *= mpz(2) # 最终修正 q *= 2
# 5. 高精度转换
ctx.precision = int(precision * 1.5) # 最终精度缓冲
result = mpfr(p) / mpfr(q)
# 精度锁定
ctx.precision = int(precision * 3.33)
return mpfr(result, precision)
# 验证测试
if __name__ == "__main__":
precision = int(100000*3.33) # 测试1000位精度
ln2 = compute_ln2_zuniga(precision)
exact = gmpy2.log(2)
print(f"计算结果[前50位]: {str(ln2)[:50]}")
print(f"标准值差异: {abs(ln2 - exact):.3e}")
print(f"有效位数: {-math.log10(abs(float(ln2 - exact))):.1f}")
3.利用Xiao二进制分裂法
import gmpy2
from gmpy2 import mpz, mpfr
import math
def compute_ln2_xiao(precision):
"""基于Xiao的二进制分裂算法计算ln(2)的gmpy2实现"""
# 1. 计算所需项数kmax和工作精度
kmax = int(math.ceil(precision * math.log(10) / math.log(450000)))
workprec = int(math.ceil(precision + 1 + 1 * math.log(kmax)))
# 2. 初始化gmpy2变量
ctx = gmpy2.get_context()
ctx.precision = int(precision) # 二进制位数 ≈ 十进制位数*log2(10)
# 3. 定义递归的二进制分裂函数
def binary_splitting(a, b):
if a + 1 == b:
b4 = 4 * b
b10 = 10 * b
bb = mpz(b)
# 计算q = 1440*(10b-1)(10b-3)(10b-7)(10b-9)
q = mpz(1440) * mpz((b10-1)*(b10-3)) * mpz((b10-7)*(b10-9))
# 计算r = b(2b-1)(4b-1)(4b-3)
r = mpz(b*(2*b-1)) * mpz((b4 - 1) * (b4 - 3))
# 计算p = (3927264b³ - 4300512b² + 1209726b - 81891)
p = ((mpz(3927264) * bb - mpz(4300512)) * bb + mpz(1209726)) * bb - mpz(81891)
return p, q, r
# 分裂区间[a,b] → [a,mid]和[mid,b]
mid = (a + b) // 2
p_left, q_left, r_left = binary_splitting(a, mid)
p_right, q_right, r_right = binary_splitting(mid, b)
# 合并结果: p = p_left*q_right + p_right*r_left
p = p_left * q_right + p_right * r_left
q = q_left * q_right
r = r_left * r_right
return p, q, r
# 4. 执行主计算
p, q, r = binary_splitting(0, kmax)
q *= mpz(4) # q *= 4
# 5. 转换为高精度浮点数
fp = mpfr(p, workprec)
fq = mpfr(q, workprec)
result = fp / fq
# 调整最终精度
ctx.precision = int(precision)
return mpfr(result, precision)
# 测试代码
if __name__ == "__main__":
precision = int(100000 *3.33) # 100位十进制精度
ln2 = compute_ln2_xiao(precision)
print(f"ln(2) [前{precision}位]: {len(str(ln2))}") #[:30]}")#precision+2]}")
print(f"与gmpy2内置函数差异: {abs(ln2 - gmpy2.log(2)):.3e}")
三个程序计算10万位ln(2)均低于1秒。