我一直在尝试创建用于计算三角函数的自定义计算器。除了Chebyshev pylonomials和/或Cordic算法,我使用了泰勒系列,它已经精确到几个小数位。
这是我在没有任何模块的情况下计算简单三角函数所创建的:
class Component
{
public:
virtual ~Component() {}
};
#include <map>
// Implement GameObject with no knowledge of any of the classes derived
// from Component. That will be one sure fire way of making sure that it can
// be used for any class derived from Component.
class GameObject
{
public:
//Add functionality
template <class T>
T& AddComponent()
{
T* obj = new T{};
AddComponent(getTypeID<T>(), obj);
return *obj;
}
//Get functionality
template <typename T>
T& GetComponent()
{
T* obj = dynamic_cast<T*>(GetComponent(getTypeID<T>()));
return *obj;
}
private:
// If necessary, the implementation can be moved to a .cpp file.
void AddComponent(int typeID, Component* obj)
{
components[typeID] = obj;
}
// If necessary, the implementation can be moved to a .cpp file.
Component* GetComponent(int typeID)
{
return components[typeID];
}
// Key function.
// It gets a unique integer for every type.
template <class T>
static int getTypeID()
{
static int typeID = getNextID();
return typeID;
}
// If necessary, the implementation can be moved to a .cpp file.
static int getNextID()
{
static int nextID = 0;
return ++nextID;
}
std::map<int, Component*> components;
};
class Collider : public Component
{
};
class SphereCollider :public Collider
{
};
int main()
{
GameObject go;
go.AddComponent<Collider>();
go.AddComponent<SphereCollider>();
Collider c = go.GetComponent<Collider>();
SphereCollider sc = go.GetComponent<SphereCollider>();
}
不幸的是,我找不到任何可以帮助我解释Python的反三角函数公式的资源。我也尝试将sin(x)置于-1(from __future__ import division
def sqrt(n):
ans = n ** 0.5
return ans
def factorial(n):
k = 1
for i in range(1, n+1):
k = i * k
return k
def sin(d):
pi = 3.14159265359
n = 180 / int(d) # 180 degrees = pi radians
x = pi / n # Converting degrees to radians
ans = x - ( x ** 3 / factorial(3) ) + ( x ** 5 / factorial(5) ) - ( x ** 7 / factorial(7) ) + ( x ** 9 / factorial(9) )
return ans
def cos(d):
pi = 3.14159265359
n = 180 / int(d)
x = pi / n
ans = 1 - ( x ** 2 / factorial(2) ) + ( x ** 4 / factorial(4) ) - ( x ** 6 / factorial(6) ) + ( x ** 8 / factorial(8) )
return ans
def tan(d):
ans = sin(d) / sqrt(1 - sin(d) ** 2)
return ans
)的幂,但是没有按预期工作。
在Python中做到这一点的最佳解决方案是什么(在最好的情况下,我的意思是最简单的,具有与泰勒系列相似的精度)?这是可能的电源系列还是我需要使用cordic算法?
答案 0 :(得分:2)
问题的范围很广,但这里有一些简单的想法(和代码!)可以作为计算arctan
的起点。首先,好老泰勒系列。为简单起见,我们使用固定数量的术语;实际上,您可能希望根据x
的大小来决定动态使用的术语数,或者引入某种收敛准则。使用固定数量的术语,我们可以使用类似于Horner方案的方法进行有效评估。
def arctan_taylor(x, terms=9):
"""
Compute arctan for small x via Taylor polynomials.
Uses a fixed number of terms. The default of 9 should give good results for
abs(x) < 0.1. Results will become poorer as abs(x) increases, becoming
unusable as abs(x) approaches 1.0 (the radius of convergence of the
series).
"""
# Uses Horner's method for evaluation.
t = 0.0
for n in range(2*terms-1, 0, -2):
t = 1.0/n - x*x*t
return x * t
上面的代码为小x
(比如绝对值小于0.1
)提供了良好的结果,但随着x
变大,精度下降,{{1}无论我们抛出多少个术语(或多少精确度),该系列都不会收敛。因此,我们需要一种更好的方法来计算更大的abs(x) > 1.0
。一种解决方案是通过身份x
使用参数减少。这给出了以下代码,它构建在arctan(x) = 2 * arctan(x / (1 + sqrt(1 + x^2)))
之上,可以为各种arctan_taylor
提供合理的结果(但要注意计算x
时可能出现的溢出和下溢)。
x*x
或者,鉴于import math
def arctan_taylor_with_reduction(x, terms=9, threshold=0.1):
"""
Compute arctan via argument reduction and Taylor series.
Applies reduction steps until x is below `threshold`,
then uses Taylor series.
"""
reductions = 0
while abs(x) > threshold:
x = x / (1 + math.sqrt(1 + x*x))
reductions += 1
return arctan_taylor(x, terms=terms) * 2**reductions
的现有实施,您只需使用传统的根查找方法找到方程tan
的解y
。由于arctan已经自然地位于区间tan(y) = x
中,因此二分搜索效果很好:
(-pi/2, pi/2)
最后,只是为了好玩,这是一个类似CORDIC的实现,它更适合低级实现而不是Python。这里的想法是,您一劳永逸地预先计算def arctan_from_tan(x, tolerance=1e-15):
"""
Compute arctan as the inverse of tan, via bisection search. This assumes
that you already have a high quality tan function.
"""
low, high = -0.5 * math.pi, 0.5 * math.pi
while high - low > tolerance:
mid = 0.5 * (low + high)
if math.tan(mid) < x:
low = mid
else:
high = mid
return 0.5 * (low + high)
,1
1/2,
等的arctan值表,然后使用这些值来计算一般arctan值,主要是计算真实角度的逐次逼近。值得注意的是,在预计算步骤之后,arctan计算仅涉及2的幂的加法,减法和乘法。(当然,这些乘法并不比Python级别的任何其他乘法更有效,但更接近硬件,这可能会产生很大的不同。)
1/4,
上述每种方法都有其优点和缺点,所有上述代码都可以通过多种方式得到改进。我鼓励你进行实验和探索。
要总结一下,以下是在少量非常精心挑选的测试值上调用上述函数的结果,与标准库cordic_table_size = 60
cordic_table = [(2**-i, math.atan(2**-i))
for i in range(cordic_table_size)]
def arctan_cordic(y, x=1.0):
"""
Compute arctan(y/x), assuming x positive, via CORDIC-like method.
"""
r = 0.0
for t, a in cordic_table:
if y < 0:
r, x, y = r - a, x - t*y, y + t*x
else:
r, x, y = r + a, x + t*y, y - t*x
return r
函数的输出进行比较:
math.atan
我机器上的输出:
test_values = [2.314, 0.0123, -0.56, 168.9]
for value in test_values:
print("{:20.15g} {:20.15g} {:20.15g} {:20.15g}".format(
math.atan(value),
arctan_taylor_with_reduction(value),
arctan_from_tan(value),
arctan_cordic(value),
))
答案 1 :(得分:1)
执行任何反函数的最简单方法是使用二进制搜索。
<强>定义强>
让假设功能
x = g(y)
我们想要编码它的逆码:
y = f(x) = f(g(y))
x = <x0,x1>
y = <y0,y1>
在浮动广告上搜索
你可以在整数数学中访问尾数位,如下所示:
但是如果你在计算之前不知道结果的指数,那么你也需要使用浮点数进行bin搜索。
所以二进制搜索背后的想法是将y
的尾数从y1
改为y0
从 MSB 到 LSB } STRONG>。然后调用直接函数g(y)
,如果结果交叉x
,则返回最后一位更改。
在使用浮点数的情况下,您可以使用将保持尾数位的近似值而不是整数位访问的变量。这将消除未知的指数问题。因此,在开始时将y = y0
和实际位设置为 MSB ,以便b=(y1-y0)/2
。在每次迭代后将其减半并执行与尾数位n
一样多的迭代...这样,您可以在n
精度内获得(y1-y0)/2^n
次迭代的结果。
如果您的反函数不是单调的,则将其分解为单调区间并将其作为单独的二元搜索处理。
函数增加/减少只是确定交叉条件方向(使用<
或>
)。
C ++ acos示例
所以y = acos(x)
定义在x = <-1,+1> , y = <0,M_PI>
上并且正在减少:
double f64_acos(double x)
{
const int n=52; // mantisa bits
double y,y0,b;
int i;
// handle domain error
if (x<-1.0) return 0;
if (x>+1.0) return 0;
// x = <-1,+1> , y = <0,M_PI> , decreasing
for (y= 0.0,b=0.5*M_PI,i=0;i<n;i++,b*=0.5) // y is min, b is half of max and halving each iteration
{
y0=y; // remember original y
y+=b; // try set "bit"
if (cos(y)<x) y=y0; // if result cross x return to original y decreasing is < and increasing is >
}
return y;
}
我测试了这样:
double x0,x1,y;
for (x0=0.0;x0<M_PI;x0+=M_PI*0.01) // cycle all angle range <0,M_PI>
{
y=cos(x0); // direct function (from math.h)
x1=f64_acos(y); // my inverse function
if (fabs(x1-x0)>1e-9) // check result and output to log if error
Form1->mm_log->Lines->Add(AnsiString().sprintf("acos(%8.3lf) = %8.3lf != %8.3lf",y,x0,x1));
}
没有发现任何差异......所以实施工作正常。在52位尾数上的粗二进制搜索通常比多项式近似慢...另一方面,实现是如此简单......
<强> [注释] 强>
如果您不想处理单调音程,可以尝试
当您处理测角函数时,您需要处理奇点以避免NaN
或除零等...
如果您对此感兴趣的是更多bin搜索示例(主要是整数)