Java的快速超越/三角函数

由于java.lang.Math中的三角函数非常慢:是否有一个快速且良好近似的库? 似乎可以在不损失太多精度的情况下快速进行几次计算。 (在我的机器上,乘法需要1.5ns,而java.lang.Math.sin需要46ns到116ns)。 遗憾的是,还没有办法使用硬件function。

更新:function应该足够准确,比如GPS计算。 这意味着您需要至少7个十进制数字的精度,这排除了简单的查找表。 它应该比基本x86系统上的java.lang.Math.sin快得多。 否则就没有意义了。

对于超过pi / 4的值,除了硬件function之外,Java还会进行一些昂贵的计算 。 这样做有充分的理由,但有时你更关心速度而不是最后一位精度。

哈特的计算机近似 。 将Chebyshev经济的近似公式列表为不同精度的一系列函数。

编辑:将我的副本从架子上取下来,结果certificate这是一本听起来非常相似的书。 这是使用其表格的sin函数。 (在C中测试,因为这对我来说更方便。)我不知道这是否会比Java内置更快,但至少可以保证它不那么准确。 :)您可能需要先缩小参数范围; 看约翰库克的建议 。 这本书还有arcsin和arctan。

#include  #include  // 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; } 

这是一组用于快速逼近trig函数的低级技巧。 在C中有一些示例代码,我觉得很难遵循,但这些技术在Java中也很容易实现。

这是我在Java中的invsqrt和atan2的等效实现。

我可以为其他trig函数做类似的事情,但我没有发现它是必要的,因为分析显示只有sqrt和atan / atan2是主要的瓶颈。

 public class FastTrig { /** Fast approximation of 1.0 / sqrt(x). * See http://www.beyond3d.com/content/articles/8/ * @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; } } } 

这可能会成为: http : //sourceforge.net/projects/jafama/

我很惊讶内置的Java函数会很慢。 当然,JVM正在调用CPU上的本机trig函数,而不是在Java中实现算法。 你确定你的瓶颈是调用trig函数而不是周围的代码吗? 也许一些内存分配?

你能用C ++重写你的数学代码部分吗? 只是调用C ++代码来计算trig函数可能不会加快速度,但是将一些上下文(如外循环)移动到C ++可能会加快速度。

如果您必须滚动自己的trig函数,请不要单独使用Taylor系列。 除非您的参数非常小,否则CORDIC算法要快得多。 你可以使用CORDIC开始,然后用一个简短的泰勒系列抛光结果。 请参阅有关如何实现trig函数的 StackOverflow问题。

在x86上,java.lang.Math sin和cos函数不直接调用硬件函数,因为英特尔并不总是做得如此好。 bug#4857011中有一个很好的解释。

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4857011

你可能想要仔细思考一个不精确的结果。 有趣的是我经常花时间在其他代码中找到它。

“但评论说罪……”

如果您只需要一些近似值,则可以将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来使用此数组。

我没有听说过任何libs,可能是因为它很少见到trig重型Java应用程序。 使用JNI(相同的精度,更好的性能),数值方法(可变精度/性能)或简单的近似表也很容易。

与任何优化一样,最好测试这些function实际上是一个瓶颈,然后再来重新发明轮子。

三角函数是查找表的经典示例。 看到优秀

  • 在维基百科上查找表文章

如果您在库中搜索J2ME,可以尝试:

  • 定点整数数学库 MathFP

java.lang.Math函数调用硬件函数。 你应该做出简单的批评,但它们不会那么准确。

在我的labtop上,sin和cos需要大约144 ns。

在sin / cos测试中,我的整数表现为零到一百万。 我认为144 ns对你来说不够快。

您对所需速度有特定要求吗?

您是否可以按照每次操作的时间来满足您的要求,这是令人满意的?

如果您想使用现有的东西,请查看Apache Commons Math包 。

如果性能真的非常重要,那么你可以自己使用标准数学方法 – 泰勒/麦克劳林系列’来实现这些function。

例如,以下是几个可能有用的泰勒级数展开(取自维基百科 ):

替代文字

替代文字

替代文字

如果这些例程太慢,你能详细说明你需要做什么吗? 您可能会以某种方式提前做一些坐标转换。