/* * @(#)MathG.java * * $Date: 2012-07-03 01:10:05 -0500 (Tue, 03 Jul 2012) $ * * Copyright (c) 2011 by Jeremy Wood. * All rights reserved. * * The copyright of this software is owned by Jeremy Wood. * You may not use, copy or modify this software, except in * accordance with the license agreement you entered into with * Jeremy Wood. For details see accompanying license terms. * * This software is probably, but not necessarily, discussed here: * http://javagraphics.java.net/ * * That site should also contain the most recent official version * of this software. (See the SVN repository for more details.) */ package com.bric.math; import com.bric.math.function.Function; import com.bric.math.function.PiecewiseFunction; import com.bric.math.function.PolynomialFunction; /** This provides some alternative implementations of a few methods from * the Math class. * <P>This class may use approximations with various levels of error. The "G" * in the name stands for "Graphics", because it was originally conceived * as a tool to speed up graphics. When I iterate over every pixel in an image * to perform some operation: I don't really need the precision that the Math * class offers. * <P>Many thanks to Oleg E. for some insights regarding machine error and * design. * <P>See MathGDemo.java for a set of tests comparing the speed/accuracy * of java.lang.Math and com.bric.math.MathG. * */ public abstract class MathG { /** Finds the closest integer that is less than or equal to the argument as a double. * <BR>Warning: do not use an argument greater than 1e10, or less than 1e-10. */ public static final double floorDouble(double d) { int id = (int)d; return d==id || d > 0 ? id : id-1; } /** Finds the closest integer that is less than or equal to the argument as an int. * <BR>Warning: do not use an argument greater than 1e10, or less than 1e-10. */ public static final int floorInt(double d) { int id = (int)d; return d==id || d > 0 ? id : id-1; } /** Rounds a double to the nearest integer value. * <BR>Warning: do not use an argument greater than 1e10, or less than 1e-10. */ public static final int roundInt(double d) { int i; if(d>=0) { i = (int)(d+.5); } else { i = (int)(d-.5); } return i; } /** Rounds a double to the nearest integer value. * <BR>Warning: do not use an argument greater than 1e10, or less than 1e-10. */ public static final double roundDouble(double d) { int i; if(d>=0) { i = (int)(d+.5); } else { i = (int)(d-.5); } return i; } /** Finds the closest integer that is greater than or equal to the argument as an int. * <BR>Warning: do not use an argument greater than 1e10, or less than 1e-10. */ public static final int ceilInt(double d) { int id = (int)d; return d==id || d < 0 ? id : -((int)(-d))+1; } /** Finds the closest integer that is greater than or equal to the argument as a double. * <BR>Warning: do not use an argument greater than 1e10, or less than 1e-10. */ public static final double ceilDouble(double d) { int id = (int)d; return d==id || d < 0 ? id : -((int)(-d))+1; } private static final double PI = Math.PI; private static final double TWO_PI = 2.0*Math.PI; private static final double PI_OVER_2 = Math.PI/2.0; private static Function sinFunction01 = PolynomialFunction.createFit( new double[] { 0, Math.PI/2}, new double[] { Math.sin(0), Math.sin(Math.PI/2)}, new double[] { Math.cos(0), Math.cos(Math.PI/2)} ); private static Function sinFunction00004 = PolynomialFunction.createFit( new double[] { 0, Math.PI/4, Math.PI/2}, new double[] { Math.sin(0), Math.sin(Math.PI/4), Math.sin(Math.PI/2)}, new double[] { Math.cos(0), Math.cos(Math.PI/4), Math.cos(Math.PI/2)} ); private static Function acosFunction; //define the acosFunction: static { Function acos = new Function() { public double evaluate(double x) { return Math.acos(x); } public double[] evaluateInverse(double y) { throw new UnsupportedOperationException(); } }; Function acosD = new Function() { public double evaluate(double x) { return -1.0/Math.sqrt(1-x*x); } public double[] evaluateInverse(double y) { throw new UnsupportedOperationException(); } }; PiecewiseFunction p = PiecewiseFunction.create(acos, acosD, 0, 1, 512); p.setFunction(p.getFunctionCount()-1, PiecewiseFunction.create(acos, acosD, 1.0-1.0/(p.getFunctionCount()), 1, 64)); acosFunction = p; } /** Returns an approximate value of the sin(v) that should be * within plus-or-minus .0108 of the value returned by Math.sin(). * <P>If the argument is greater in magnitude than 1e10, then * this delegates to Math.sin(). * * @param v * @return an approximate value of sin(v) */ public static final double sin01(double v) { /** This is exactly the same code as sin00004, except it * uses the smaller polynomial. I avoided refactoring * the code to use a common method in the theory that * avoiding adding a method to the stack trace may * shave off just a tiny bit of performance. Normally * this would be excessive, but my goal is optimum * performance in really tight loops: every line counts! */ double finalMultiplier; if(v<0) { finalMultiplier = -1; v = -v; } else { finalMultiplier = 1; } if(v>1.0E10) { if(printedOverflowError==false) { printedOverflowError = true; System.err.println("Warning: MathG is not designed to estimate the sine of values of 1.0e10. Math.sin() will be used, which may result in slower performance."); } return finalMultiplier*Math.sin(v); } else if(v<.01) { //if we're that small, then y=sin(x) -> y=x //sin(.01)-.01 = -1.6666583333574403E-7 return v*finalMultiplier; } if(v>TWO_PI) { //v = v%TWO_PI; long m = (long)(v/TWO_PI); v = v-m*TWO_PI; } if(v>PI) { v = v-PI; finalMultiplier = -finalMultiplier; } if(v>PI_OVER_2) { v = PI-v; } double result = sinFunction01.evaluate(v); result = result*finalMultiplier; return result; } /** Controls whether an error message has been printed to * System.err. yet this session regarding calling * calculateSin() on values that are too large. */ private static boolean printedOverflowError = false; /** Returns an approximate value of the cos(v) that should be * within plus-or-minus .0108 of the value returned by Math.cos(). * <P>If the argument is greater in magnitude than 1e10, then * this delegates to Math.cos(). * * @param v * @return an approximate value of cos(v) */ public static final double cos01(double v) { if(v>1e10 || v<1e-10) return Math.cos(v); return sin01(v-PI_OVER_2); } /** Returns an approximate value of the sin(v) that should be * within plus-or-minus .00004 of the value returned by Math.sin(). * <P>If the argument is greater in magnitude than 1e10, then * this delegates to Math.sin(). * * @param v * @return an approximate value of sin(v) */ public static final double sin00004(double v) { double finalMultiplier; if(v<0) { finalMultiplier = -1; v = -v; } else { finalMultiplier = 1; } if(v<.01) { //if we're that small, then y=sin(x) -> y=x //sin(.01)-.01 = -1.6666583333574403E-7 return v*finalMultiplier; } else if(v>1.0E10) { if(printedOverflowError==false) { printedOverflowError = true; System.err.println("Warning: MathG is not designed to estimate the sine of values of 1.0e10. Math.sin() will be used, which may result in slower performance."); } return finalMultiplier*Math.sin(v); } if(v>TWO_PI) { //v = v%TWO_PI; long m = (long)(v/TWO_PI); v = v-m*TWO_PI; } if(v>PI) { v = v-PI; finalMultiplier = -finalMultiplier; } if(v>PI_OVER_2) { v = PI-v; } double result = sinFunction00004.evaluate(v); result = result*finalMultiplier; return result; } /** Returns an approximate value of the acos(v) that should be * within plus-or-minus .00004 of the value returned by Math.acos(). * * @param v * @return an approximate value of acos(v) */ public static final double acos(double v) { if(v<-1 || v>1) throw new IllegalArgumentException("v ("+v+") must be within [-1,1]"); if(v<0) { v = -v; return Math.PI-acos(v); } return acosFunction.evaluate(v); } /** Returns an approximate value of the cos(v) that should be * within plus-or-minus .00004 of the value returned by Math.cos(). * <P>If the argument is greater in magnitude than 1e10, then * this delegates to Math.cos(). * * @param v * @return an approximate value of cos(v) */ public static final double cos00004(double v) { if(v>1e10 || v<1e-10) return Math.cos(v); return sin00004(v-PI_OVER_2); } }