我需要计算mpmath的“mpf”浮点bignums形式的小值的反正弦函数。
我称之为“小”值的是例如e / 4 /(10 ** 7)= 0.000000067957045711476130884 ...
以下是使用mpmath内置asin功能对我的机器进行测试的结果:
import gmpy2
from mpmath import *
from time import time
mp.dps = 10**6
val=e/4/(10**7)
print "ready"
start=time()
temp=asin(val)
print "mpmath asin: "+str(time()-start)+" seconds"
>>> 155.108999968 seconds
这是一个特例:我使用的数字有点小,所以我问自己是否有一种方法可以在python中计算它,实际上对于这种特殊情况(=对于较小的值)来说是mpmath。
泰勒系列实际上是一个不错的选择,因为它们可以非常快速地收敛到小参数。但我还是需要以某种方式进一步加速计算。
实际上存在一些问题:
1)二进制分裂在这里是无效的,因为它只有在你可以将参数写成一小部分时才会发光。这里给出了一个全精度浮子
2)arcsin是一个非交替序列,因此Van Wijngaarden或sumalt变换也是无效的(除非有一种方法我不知道将它们推广到非交替序列)。
https://en.wikipedia.org/wiki/Van_Wijngaarden_transformation
我能想到的唯一加速度是切比雪夫多项式。切比雪夫多项式可以应用于arcsin函数吗?怎么样?
答案 0 :(得分:2)
您可以使用gmpy2中包含的mpfr
类型吗?
>>> import gmpy2
>>> gmpy2.get_context().precision = 3100000
>>> val = gmpy2.exp(1)/4/10**7
>>> from time import time
>>> start=time();r=gmpy2.asin(val);print time()-start
3.36188197136
除支持GMP库外,gmpy2还支持MPFR和MPC多精度库。
免责声明:我维持gmpy2。
答案 1 :(得分:1)
实际上二进制拆分确实很有效,如果结合迭代参数减少来平衡术语数量与分子和分母的大小(这就是比特突发算法)。
这是基于重复应用公式atan(t)= atan(p / 2 ^ q)+ atan((t * 2 ^ qp)/(2 ^ q + p * t)的mpmath的二元分裂实现))。这个公式最近由Richard Brent提出(事实上,mpmath的atan已经在低精度下使用了这个公式的单个调用,以便从缓存中查找atan(p / 2 ^ q))。如果我没记错的话,MPFR也会使用比特脉冲算法来评估atan,但是它使用了稍微不同的公式,这可能更有效(而不是评估几个不同的反正切值,它使用反正切微分方程进行解析延续)。
from mpmath.libmp import MPZ, bitcount
from mpmath import mp
def bsplit(p, q, a, b):
if b - a == 1:
if a == 0:
P = p
Q = q
else:
P = p * p
Q = q * 2
B = MPZ(1 + 2 * a)
if a % 2 == 1:
B = -B
T = P
return P, Q, B, T
else:
m = a + (b - a) // 2
P1, Q1, B1, T1 = bsplit(p, q, a, m)
P2, Q2, B2, T2 = bsplit(p, q, m, b)
T = ((T1 * B2) << Q2) + T2 * B1 * P1
P = P1 * P2
B = B1 * B2
Q = Q1 + Q2
return P, Q, B, T
def atan_bsplit(p, q, prec):
"""computes atan(p/2^q) as a fixed-point number"""
if p == 0:
return MPZ(0)
# FIXME
nterms = (-prec / (bitcount(p) - q) - 1) * 0.5
nterms = int(nterms) + 1
if nterms < 1:
return MPZ(0)
P, Q, B, T = bsplit(p, q, 0, nterms)
if prec >= Q:
return (T << (prec - Q)) // B
else:
return T // (B << (Q - prec))
def atan_fixed(x, prec):
t = MPZ(x)
s = MPZ(0)
q = 1
while t:
q = min(q, prec)
p = t >> (prec - q)
if p:
s += atan_bsplit(p, q, prec)
u = (t << q) - (p << prec)
v = (MPZ(1) << (q + prec)) + p * t
t = (u << prec) // v
q *= 2
return s
def atan1(x):
prec = mp.prec
man = x.to_fixed(prec)
return mp.mpf((atan_fixed(man, prec), -prec))
def asin1(x):
x = mpf(x)
return atan1(x/sqrt(1-x**2))
使用此代码,我得到:
>>> from mpmath import *
>>> mp.dps = 1000000
>>> val=e/4/(10**7)
>>> from time import time
>>> start = time(); y1 = asin(x); print time() - start
58.8485069275
>>> start = time(); y2 = asin1(x); print time() - start
8.26498985291
>>> nprint(y2 - y1)
-2.31674e-1000000
警告:atan1假定为0&lt; = x&lt; 1/2,并且术语数量的确定可能不是最佳或正确的(修复这些问题留给读者练习。)
答案 2 :(得分:0)
快速的方法是使用预先计算的查找表。
但如果你看一下,例如asin的泰勒系列;
def asin(x):
rv = (x + 1/3.0*x**3 + 7/30.0*x**5 + 64/315.0*x**7 + 4477/22680.0*x**9 +
28447/138600.0*x**11 + 23029/102960.0*x**13 +
17905882/70945875.0*x**15 + 1158176431/3958416000.0*x**17 +
9149187845813/26398676304000.0*x**19)
return rv
你会看到x的小值,asin(x)≈x。
In [19]: asin(1e-7)
Out[19]: 1.0000000000000033e-07
In [20]: asin(1e-9)
Out[20]: 1e-09
In [21]: asin(1e-11)
Out[21]: 1e-11
In [22]: asin(1e-12)
Out[22]: 1e-12
E.g。对于我们使用的价值:
In [23]: asin(0.000000067957045711476130884)
Out[23]: 6.795704571147624e-08
In [24]: asin(0.000000067957045711476130884)/0.000000067957045711476130884
Out[24]: 1.0000000000000016
当然,这取决于这种差异是否与您相关。