弧形函数在小的任意浮点值上的最快方法

时间:2013-08-14 16:41:53

标签: python trigonometry bignum taylor-series mpmath

我需要计算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函数吗?怎么样?

3 个答案:

答案 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

当然,这取决于这种差异是否与您相关。