Fast transcendent / trigonometric functions for Java

前端 未结 12 1804
面向向阳花
面向向阳花 2020-11-29 02:06

Since the trigonometric functions in java.lang.Math are quite slow: is there a library that does a quick and good approximation? It seems possible to do a calculation severa

相关标签:
12条回答
  • 2020-11-29 02:37

    Here is a collection of low-level tricks for quickly approximating trig functions. There is example code in C which I find hard to follow, but the techniques are just as easily implemented in Java.

    Here's my equivalent implementation of invsqrt and atan2 in Java.

    I could have done something similar for the other trig functions, but I have not found it necessary as profiling showed that only sqrt and atan/atan2 were major bottlenecks.

    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;
        }
      }
    }
    
    0 讨论(0)
  • 2020-11-29 02:37

    Check out Apache Commons Math package if you want to use existing stuff.

    If performance is really of the essence, then you can go about implementing these functions yourself using standard math methods - Taylor/Maclaurin series', specifically.

    For example, here are several Taylor series expansions that might be useful (taken from wikipedia):

    alt text

    alt text

    alt text

    0 讨论(0)
  • 2020-11-29 02:44

    You could pre-store your sin and cos in an array if you only need some approximate values. For example, if you want to store the values from 0° to 360°:

    double sin[]=new double[360];
    for(int i=0;i< sin.length;++i) sin[i]=Math.sin(i/180.0*Math.PI):
    

    you then use this array using degrees/integers instead of radians/double.

    0 讨论(0)
  • 2020-11-29 02:45

    The java.lang.Math functions call the hardware functions. There should be simple appromiations you can make but they won't be as accurate.

    On my labtop, sin and cos takes about 144 ns.

    0 讨论(0)
  • 2020-11-29 02:52

    Could you elaborate on what you need to do if these routines are too slow. You might be able to do some coordinate transformations ahead of time some way or another.

    0 讨论(0)
  • 2020-11-29 02:53

    Trigonometric functions are the classical example for a lookup table. See the excellent

    • Lookup table article at wikipedia

    If you're searching a library for J2ME you can try:

    • the Fixed Point Integer Math Library MathFP
    0 讨论(0)
提交回复
热议问题