作为开发人员,我们经常需要计算角度以执行旋转。通常我们可以使用 atan2() 函数,但有时需要更高的精度。那么你该怎么办呢?
我知道在理论上 atan2 是精确的,但在我的系统(iOS)中,它的精度约为 0.05 弧度,差异很大。这不仅是我的问题。我看到了类似的意见。
作为开发人员,我们经常需要计算角度以执行旋转。通常我们可以使用 atan2() 函数,但有时需要更高的精度。那么你该怎么办呢?
我知道在理论上 atan2 是精确的,但在我的系统(iOS)中,它的精度约为 0.05 弧度,差异很大。这不仅是我的问题。我看到了类似的意见。
atan2
用于从向量(x,y)
中获取角度a
。如果您使用此角度应用旋转,则将使用cos(a)
和sin(a)
。您可以通过对(x,y)进行归一化并保留它们而不是角度来简单地计算cos和sin。精度将更高,并且您将节省在三角函数中失去的大量周期。
编辑。如果您确实需要从(x,y)获得角度,则可以使用CORDIC的变体计算所需的精度。
atan2
计算角度(即知道delta_x
和delta_y
)时,三角函数的滥用比例接近95%... - gboffiatan()
的NSDecimal版本计算代码:NSDecimal DDDecimalAtan(NSDecimal x) {
// from: http://en.wikipedia.org/wiki/Inverse_trigonometric_functions#Infinite_series
// The normal infinite series diverges if x > 1
NSDecimal one = DDDecimalOne();
NSDecimal absX = DDDecimalAbsoluteValue(x);
NSDecimal z = x;
if (NSDecimalCompare(&one, &absX) == NSOrderedAscending)
{
// y = x / (1 + sqrt(1+x^2))
// Atan(x) = 2*Atan(y)
// From: http://www.mathkb.com/Uwe/Forum.aspx/math/14680/faster-Taylor-s-series-of-Atan-x
NSDecimal interiorOfRoot;
NSDecimalMultiply(&interiorOfRoot, &x, &x, NSRoundBankers);
NSDecimalAdd(&interiorOfRoot, &one, &interiorOfRoot, NSRoundBankers);
NSDecimal denominator = DDDecimalSqrt(interiorOfRoot);
NSDecimalAdd(&denominator, &one, &denominator, NSRoundBankers);
NSDecimal y;
NSDecimalDivide(&y, &x, &denominator, NSRoundBankers);
NSDecimalMultiply(&interiorOfRoot, &y, &y, NSRoundBankers);
NSDecimalAdd(&interiorOfRoot, &one, &interiorOfRoot, NSRoundBankers);
denominator = DDDecimalSqrt(interiorOfRoot);
NSDecimalAdd(&denominator, &one, &denominator, NSRoundBankers);
NSDecimal y2;
NSDecimalDivide(&y2, &y, &denominator, NSRoundBankers);
// NSDecimal two = DDDecimalTwo();
NSDecimal four = DDDecimalFromInteger(4);
NSDecimal firstArctangent = DDDecimalAtan(y2);
NSDecimalMultiply(&z, &four, &firstArctangent, NSRoundBankers);
}
else
{
BOOL shouldSubtract = YES;
for (NSInteger n = 3; n < 150; n += 2) {
NSDecimal numerator;
if (NSDecimalPower(&numerator, &x, n, NSRoundBankers) == NSCalculationUnderflow)
{
numerator = DDDecimalZero();
n = 150;
}
NSDecimal denominator = DDDecimalFromInteger(n);
NSDecimal term;
if (NSDecimalDivide(&term, &numerator, &denominator, NSRoundBankers) == NSCalculationUnderflow)
{
term = DDDecimalZero();
n = 150;
}
if (shouldSubtract) {
NSDecimalSubtract(&z, &z, &term, NSRoundBankers);
} else {
NSDecimalAdd(&z, &z, &term, NSRoundBankers);
}
shouldSubtract = !shouldSubtract;
}
}
return z;
}
这里使用了泰勒级数逼近法,并加入了一些快捷方式以加速收敛。我认为在接近 Pi / 4 弧度的结果中,精度可能不会达到完整的34位数字,所以我可能还需要修复它。
如果您需要极高的精度,这是一个选择,但是如果您使用的是 double
值,那么您报告的情况不应该发生,因此这里有一些奇怪的地方。
如果在您的系统中,long double
比 double
更精确,您可以使用 atan2l
。
long double atan2l(long double y, long double x);
atan2
产生错误答案的示例,无论在哪个平台上,请向平台供应商提交错误报告。在这种情况下,始终使用 bugreport.apple.com。 - Stephen Canon