我想在低内存环境中计算Pi的第i个数字。由于我没有可用的小数,这个integer-only BBP algorithm in Python是一个很好的起点。我只需要一次计算一位Pi。 如何确定可设置的最低值D,即“工作精度的位数”?
D = 4给了我许多正确的数字,但是一些数字会被一个数字关闭。例如,计算精度为4的数字393给出了0xafda,我从中提取数字0xa。但是,正确的数字是0xb。
无论我设置多高D,似乎测试足够数量的数字都会找到公式返回错误值的数字。
当数字与另一个数字“接近”时,我尝试提高精度,例如0x3fff或0x1000,但找不到“关闭”的任何好定义;例如,在数字9798处计算得到0x c de6,它不是非常接近0xd000,但正确的数字是0xd。
任何人都可以帮我弄清楚使用这种算法计算给定数字需要多少工作精度?
谢谢,
修改的
供参考:
precision (D) first wrong digit ------------- ------------------ 3 27 4 161 5 733 6 4329 7 21139 8+ ???
请注意,我一次只计算一个数字,例如:
for i in range(1,n):
D = 3 # or whatever precision I'm testing
digit = pi(i) # extracts most significant digit from integer-only BBP result
if( digit != HARDCODED_PI[i] ):
print("non matching digit #%d, got %x instead of %x" % (i,digit,HARDCODED_PI[i]) )
答案 0 :(得分:3)
无论我设置D有多高,看来 测试足够数量的 数字找到公式中的一个 返回不正确的值。
如果您测试足够数量的数字,您将始终收到错误 - 算法不使用任意精度,因此最终会出现舍入错误。
当数字不变时,带有中断的无界迭代很难确定给定位数所需的最小精度。
你最好的选择是根据经验来确定它,理想情况是通过与已知的正确来源进行比较,并增加位数精度直到你得到匹配,或者如果没有正确的来源,从你的最大精度开始(我猜测是14,因为第15位数几乎总是包含舍入误差。)
编辑:更准确地说,算法包括一个循环 - 从0..n开始,其中n是要计算的数字。循环的每次迭代都会引入一定量的错误。循环足够多次后,错误将侵入您正在计算的最重要的数字,因此结果将是错误的。
维基百科文章使用14位精度,这足以正确计算10 ** 8位数。正如您所示,精度较低的数字会导致较早出现的错误,因为精度较低,错误变得可见,迭代次数较少。最终结果是,我们可以正确计算数字的n的值随着精度数字的减少而变得更低。
如果你有精确的D十六进制数字,那就是D * 4位。对于每次迭代,在最低有效位中引入0.5位的误差,因此在2次迭代中LSB有可能是错误的。在求和期间,这些误差被添加,因此被累积。如果总和的误差数达到最高有效位的LSB,那么您提取的单个数字将是错误的。粗略地说,那是当N> 2 **(d-0.75)。 (纠正一些对数基数。)
根据经验推断你的数据,似乎近似拟合是N =〜(2 **(2.05 * D)),尽管数据点很少,所以这可能不是一个准确的预测器。
您选择的BBP算法是迭代的,因此计算序列中的数字需要花费更长的时间。要计算数字0..n,需要O(n^2)
步。
维基百科文章给出了计算第n个数字的公式,该数字不需要迭代,只需要求幂和有理数。这不会像迭代算法那样遭受同样的精度损失,你可以根据需要在恒定时间内计算任意数量的pi(或者在最差的对数类型,取决于带模数的指数的实现),因此计算n
数字将花费O(n)
时间O(n log n)。
答案 1 :(得分:0)
from typing import TypeVar
from gmpy2 import mpz, mpq, powmod as gmpy2_powmod, is_signed as gmpy2_is_signed
__all__ = ['PiSlice']
Integer = TypeVar('Integer', int, mpz)
class PiSlice:
'''
References
----------
"BBP digit-extraction algorithm for π"
https://en.wikipedia.org/wiki/Bailey%E2%80%93Borwein%E2%80%93Plouffe_formula
'''
version = '1.0.0'
def __spigot(self, p: Integer, a: Integer, accuracy: mpq) -> mpq:
def search_junction(p: Integer, a: Integer) -> Integer:
n = mpz(0)
divisor = 8 * p + a
while 16 ** n < divisor:
n += 1
divisor -= 8
return p - (n - 1)
p = mpz(p)
junction = search_junction(p, a)
s = 0
divisor = a
for k in range(junction):
s += mpq(gmpy2_powmod(16, p - k, divisor), divisor)
divisor += 8
for n in range(mpz(p - junction), -1, -1):
if (intermediate := mpq(16 ** n, divisor)) >= accuracy:
s += intermediate
divisor += 8
else:
return s
n = mpz(1)
while (intermediate := mpq(mpq(1, 16 ** n), divisor)) >= accuracy:
s += intermediate
n += 1
divisor += 8
return s
def __init__(self, p: Integer):
'''
'''
self.p = p
def raw(self, m: Integer) -> Integer:
'''
Parameters
----------
m: Integer
Sets the number of slices to return.
Return
------
random_raw: Integer
Returns a hexadecimal slice of Pi.
'''
p = self.p
spigot = self.__spigot
accuracy = mpq(1, 2 ** (mpz(m + 64) * 4)) #64 is the margin of accuracy.
sum_spigot = 4 * spigot(p, 1, accuracy) - 2 * spigot(p, 4, accuracy) - spigot(p, 5, accuracy) - spigot(p, 6, accuracy)
proper_fraction_of_sum_spigot = mpq(sum_spigot.numerator % sum_spigot.denominator, sum_spigot.denominator)
if gmpy2_is_signed(proper_fraction_of_sum_spigot):
proper_fraction_of_sum_spigot += 1
return mpz(mpz(16) ** m * proper_fraction_of_sum_spigot)