由于java.lang.Math中的三角函数非常慢:是否有一个快速且良好的近似值的库?似乎可以在不损失太多精度的情况下快速进行几次计算。 (在我的机器上,乘法需要1.5ns,而java.lang.Math.sin需要46ns到116ns)。不幸的是,还没有办法使用硬件功能。
更新:功能应该足够准确,比如GPS计算。这意味着您需要至少7个十进制数字的精度,这排除了简单的查找表。它应该比基本x86系统上的java.lang.Math.sin快得多。否则就没有意义了。
对于pi / 4以上的值,除硬件功能外,Java还会some expensive computations。这样做有充分理由,但有时你更关心速度而不是最后一位精度。
答案 0 :(得分:16)
Computer Approximations。将Chebyshev-economized近似公式列为不同精度的一系列函数。
编辑:将我的副本从架子上取下来,原来是a different book听起来非常相似。这是使用其表格的sin函数。 (在C中测试,因为这对我来说更方便。)我不知道这是否会比Java内置更快,但至少可以保证它不那么准确。 :)您可能需要先缩小参数范围;见John Cook's suggestions。这本书也有arcsin和arctan。
#include <math.h>
#include <stdio.h>
// Return an approx to sin(pi/2 * x) where -1 <= x <= 1.
// In that range it has a max absolute error of 5e-9
// according to Hastings, Approximations For Digital Computers.
static double xsin (double x) {
double x2 = x * x;
return ((((.00015148419 * x2
- .00467376557) * x2
+ .07968967928) * x2
- .64596371106) * x2
+ 1.57079631847) * x;
}
int main () {
double pi = 4 * atan (1);
printf ("%.10f\n", xsin (0.77));
printf ("%.10f\n", sin (0.77 * (pi/2)));
return 0;
}
答案 1 :(得分:13)
Here是用于快速逼近trig函数的低级技巧的集合。在C中有一些示例代码,我觉得很难遵循,但这些技术在Java中很容易实现。
这是我在Java中的invsqrt和atan2的等效实现。
我可以为其他trig函数做类似的事情,但我没有发现它是必要的,因为分析显示只有sqrt和atan / atan2是主要的瓶颈。
public class FastTrig
{
/** Fast approximation of 1.0 / sqrt(x).
* See <a href="http://www.beyond3d.com/content/articles/8/">http://www.beyond3d.com/content/articles/8/</a>
* @param x Positive value to estimate inverse of square root of
* @return Approximately 1.0 / sqrt(x)
**/
public static double
invSqrt(double x)
{
double xhalf = 0.5 * x;
long i = Double.doubleToRawLongBits(x);
i = 0x5FE6EB50C7B537AAL - (i>>1);
x = Double.longBitsToDouble(i);
x = x * (1.5 - xhalf*x*x);
return x;
}
/** Approximation of arctangent.
* Slightly faster and substantially less accurate than
* {@link Math#atan2(double, double)}.
**/
public static double fast_atan2(double y, double x)
{
double d2 = x*x + y*y;
// Bail out if d2 is NaN, zero or subnormal
if (Double.isNaN(d2) ||
(Double.doubleToRawLongBits(d2) < 0x10000000000000L))
{
return Double.NaN;
}
// Normalise such that 0.0 <= y <= x
boolean negY = y < 0.0;
if (negY) {y = -y;}
boolean negX = x < 0.0;
if (negX) {x = -x;}
boolean steep = y > x;
if (steep)
{
double t = x;
x = y;
y = t;
}
// Scale to unit circle (0.0 <= y <= x <= 1.0)
double rinv = invSqrt(d2); // rinv ≅ 1.0 / hypot(x, y)
x *= rinv; // x ≅ cos θ
y *= rinv; // y ≅ sin θ, hence θ ≅ asin y
// Hack: we want: ind = floor(y * 256)
// We deliberately force truncation by adding floating-point numbers whose
// exponents differ greatly. The FPU will right-shift y to match exponents,
// dropping all but the first 9 significant bits, which become the 9 LSBs
// of the resulting mantissa.
// Inspired by a similar piece of C code at
// http://www.shellandslate.com/computermath101.html
double yp = FRAC_BIAS + y;
int ind = (int) Double.doubleToRawLongBits(yp);
// Find φ (a first approximation of θ) from the LUT
double φ = ASIN_TAB[ind];
double cφ = COS_TAB[ind]; // cos(φ)
// sin(φ) == ind / 256.0
// Note that sφ is truncated, hence not identical to y.
double sφ = yp - FRAC_BIAS;
double sd = y * cφ - x * sφ; // sin(θ-φ) ≡ sinθ cosφ - cosθ sinφ
// asin(sd) ≅ sd + ⅙sd³ (from first 2 terms of Maclaurin series)
double d = (6.0 + sd * sd) * sd * ONE_SIXTH;
double θ = φ + d;
// Translate back to correct octant
if (steep) { θ = Math.PI * 0.5 - θ; }
if (negX) { θ = Math.PI - θ; }
if (negY) { θ = -θ; }
return θ;
}
private static final double ONE_SIXTH = 1.0 / 6.0;
private static final int FRAC_EXP = 8; // LUT precision == 2 ** -8 == 1/256
private static final int LUT_SIZE = (1 << FRAC_EXP) + 1;
private static final double FRAC_BIAS =
Double.longBitsToDouble((0x433L - FRAC_EXP) << 52);
private static final double[] ASIN_TAB = new double[LUT_SIZE];
private static final double[] COS_TAB = new double[LUT_SIZE];
static
{
/* Populate trig tables */
for (int ind = 0; ind < LUT_SIZE; ++ ind)
{
double v = ind / (double) (1 << FRAC_EXP);
double asinv = Math.asin(v);
COS_TAB[ind] = Math.cos(asinv);
ASIN_TAB[ind] = asinv;
}
}
}
答案 2 :(得分:5)
答案 3 :(得分:4)
在x86上,java.lang.Math sin和cos函数不会直接调用硬件函数,因为英特尔并不总是做得如此好。 bug#4857011中有一个很好的解释。
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4857011
你可能想要仔细思考一个不精确的结果。有趣的是我经常花时间在其他代码中找到它。
“但评论说罪......”
答案 4 :(得分:4)
我很惊讶内置的Java函数会很慢。当然,JVM正在调用CPU上的本机trig函数,而不是在Java中实现算法。你确定你的瓶颈是调用trig函数而不是周围的代码吗?也许是一些内存分配?
你能用C ++重写代码中的数学部分吗?只是调用C ++代码来计算trig函数可能不会加快速度,但是将一些上下文(如外循环)移动到C ++可能会加快速度。
如果您必须滚动自己的触发功能,请不要单独使用泰勒系列。除非你的论点非常小,否则CORDIC算法要快得多。你可以使用CORDIC开始,然后用一个简短的泰勒系列来修饰结果。请参阅how to implement trig functions上的StackOverflow问题。
答案 5 :(得分:2)
如果您只需要一些近似值,您可以在阵列中预先存储sin和cos。 例如,如果要存储0°到360°的值:
double sin[]=new double[360];
for(int i=0;i< sin.length;++i) sin[i]=Math.sin(i/180.0*Math.PI):
然后使用度/整数而不是radians / double来使用此数组。
答案 6 :(得分:1)
我没有听说过任何libs,可能是因为它很少见到trig重的Java应用程序。使用JNI(相同的精度,更好的性能),数值方法(可变精度/性能)或简单的近似表,也可以轻松实现自己的推广。
与任何优化一样,最好先测试这些功能实际上是一个瓶颈,然后再费心重新发明轮子。
答案 7 :(得分:1)
答案 8 :(得分:0)
java.lang.Math函数调用硬件函数。你应该做出简单的评论,但它们不会那么准确。
在我的labtop上,sin和cos需要大约144 ns。
答案 9 :(得分:0)
在sin / cos测试中,我的整数为零到一百万。我假设144 ns对你来说不够快。
您对所需的速度有特定要求吗?
您是否可以按照每次操作的时间来满足您的要求,这是令人满意的?
答案 10 :(得分:-1)
如果您想使用现有资料,请查看Apache Commons Math package。
如果性能真正的本质,那么你可以自己使用标准的数学方法 - 泰勒/麦克劳林系列'来实现这些功能,具体而言。
例如,以下是几个可能有用的泰勒级数展开式(取自wikipedia):
答案 11 :(得分:-1)
如果这些例程太慢,你能否详细说明你需要做什么?您可能会提前做某些坐标转换。