// Copyright (c) 1999, Silicon Graphics, Inc. -- ALL RIGHTS RESERVED // // Permission is granted free of charge to copy, modify, use and distribute // this software provided you include the entirety of this notice in all // copies made. // // THIS SOFTWARE IS PROVIDED ON AN AS IS BASIS, WITHOUT WARRANTY OF ANY // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, // WARRANTIES THAT THE SUBJECT SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT // FOR A PARTICULAR PURPOSE OR NON-INFRINGING. SGI ASSUMES NO RISK AS TO THE // QUALITY AND PERFORMANCE OF THE SOFTWARE. SHOULD THE SOFTWARE PROVE // DEFECTIVE IN ANY RESPECT, SGI ASSUMES NO COST OR LIABILITY FOR ANY // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES // AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY SUBJECT SOFTWARE IS // AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. // // UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING, // WITHOUT LIMITATION, NEGLIGENCE OR STRICT LIABILITY), CONTRACT, OR // OTHERWISE, SHALL SGI BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL, // INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER WITH RESPECT TO THE // SOFTWARE INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK // STOPPAGE, LOSS OF DATA, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL // OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SGI SHALL HAVE BEEN INFORMED OF // THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT // APPLY TO LIABILITY RESULTING FROM SGI's NEGLIGENCE TO THE EXTENT APPLICABLE // LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE // EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THAT // EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. // // These license terms shall be governed by and construed in accordance with // the laws of the United States and the State of California as applied to // agreements entered into and to be performed entirely within California // between California residents. Any litigation relating to these license // terms shall be subject to the exclusive jurisdiction of the Federal Courts // of the Northern District of California (or, absent subject matter // jurisdiction in such courts, the courts of the State of California), with // venue lying exclusively in Santa Clara County, California. // // 5/2014 Added Strings to ArithmeticExceptions. // 5/2015 Added support for direct asin() implementation in CR. package com.hp.creals; // import android.util.Log; import java.math.BigInteger; /** * Unary functions on constructive reals implemented as objects. * The execute member computes the function result. * Unary function objects on constructive reals inherit from * UnaryCRFunction. */ // Naming vaguely follows ObjectSpace JGL convention. public abstract class UnaryCRFunction { abstract public CR execute(CR x); /** * The function object corresponding to the identity function. */ public static final UnaryCRFunction identityFunction = new identity_UnaryCRFunction(); /** * The function object corresponding to the negate method of CR. */ public static final UnaryCRFunction negateFunction = new negate_UnaryCRFunction(); /** * The function object corresponding to the inverse method of CR. */ public static final UnaryCRFunction inverseFunction = new inverse_UnaryCRFunction(); /** * The function object corresponding to the abs method of CR. */ public static final UnaryCRFunction absFunction = new abs_UnaryCRFunction(); /** * The function object corresponding to the exp method of CR. */ public static final UnaryCRFunction expFunction = new exp_UnaryCRFunction(); /** * The function object corresponding to the cos method of CR. */ public static final UnaryCRFunction cosFunction = new cos_UnaryCRFunction(); /** * The function object corresponding to the sin method of CR. */ public static final UnaryCRFunction sinFunction = new sin_UnaryCRFunction(); /** * The function object corresponding to the tangent function. */ public static final UnaryCRFunction tanFunction = new tan_UnaryCRFunction(); /** * The function object corresponding to the inverse sine (arcsine) function. * The argument must be between -1 and 1 inclusive. The result is between * -PI/2 and PI/2. */ public static final UnaryCRFunction asinFunction = new asin_UnaryCRFunction(); // The following also works, but is slower: // CR half_pi = CR.PI.divide(CR.valueOf(2)); // UnaryCRFunction.sinFunction.inverseMonotone(half_pi.negate(), // half_pi); /** * The function object corresponding to the inverse cosine (arccosine) function. * The argument must be between -1 and 1 inclusive. The result is between * 0 and PI. */ public static final UnaryCRFunction acosFunction = new acos_UnaryCRFunction(); /** * The function object corresponding to the inverse cosine (arctangent) function. * The result is between -PI/2 and PI/2. */ public static final UnaryCRFunction atanFunction = new atan_UnaryCRFunction(); /** * The function object corresponding to the ln method of CR. */ public static final UnaryCRFunction lnFunction = new ln_UnaryCRFunction(); /** * The function object corresponding to the sqrt method of CR. */ public static final UnaryCRFunction sqrtFunction = new sqrt_UnaryCRFunction(); /** * Compose this function with f2. */ public UnaryCRFunction compose(UnaryCRFunction f2) { return new compose_UnaryCRFunction(this, f2); } /** * Compute the inverse of this function, which must be defined * and strictly monotone on the interval [low, high]. * The resulting function is defined only on the image of * [low, high]. * The original function may be either increasing or decreasing. */ public UnaryCRFunction inverseMonotone(CR low, CR high) { return new inverseMonotone_UnaryCRFunction(this, low, high); } /** * Compute the derivative of a function. * The function must be defined on the interval [low, high], * and the derivative must exist, and must be continuous and * monotone in the open interval [low, high]. * The result is defined only in the open interval. */ public UnaryCRFunction monotoneDerivative(CR low, CR high) { return new monotoneDerivative_UnaryCRFunction(this, low, high); } } // Subclasses of UnaryCRFunction for various built-in functions. class sin_UnaryCRFunction extends UnaryCRFunction { public CR execute(CR x) { return x.sin(); } } class cos_UnaryCRFunction extends UnaryCRFunction { public CR execute(CR x) { return x.cos(); } } class tan_UnaryCRFunction extends UnaryCRFunction { public CR execute(CR x) { return x.sin().divide(x.cos()); } } class asin_UnaryCRFunction extends UnaryCRFunction { public CR execute(CR x) { return x.asin(); } } class acos_UnaryCRFunction extends UnaryCRFunction { public CR execute(CR x) { return x.acos(); } } // This uses the identity (sin x)^2 = (tan x)^2/(1 + (tan x)^2) // Since we know the tangent of the result, we can get its sine, // and then use the asin function. Note that we don't always // want the positive square root when computing the sine. class atan_UnaryCRFunction extends UnaryCRFunction { CR one = CR.valueOf(1); public CR execute(CR x) { CR x2 = x.multiply(x); CR abs_sin_atan = x2.divide(one.add(x2)).sqrt(); CR sin_atan = x.select(abs_sin_atan.negate(), abs_sin_atan); return sin_atan.asin(); } } class exp_UnaryCRFunction extends UnaryCRFunction { public CR execute(CR x) { return x.exp(); } } class ln_UnaryCRFunction extends UnaryCRFunction { public CR execute(CR x) { return x.ln(); } } class identity_UnaryCRFunction extends UnaryCRFunction { public CR execute(CR x) { return x; } } class negate_UnaryCRFunction extends UnaryCRFunction { public CR execute(CR x) { return x.negate(); } } class inverse_UnaryCRFunction extends UnaryCRFunction { public CR execute(CR x) { return x.inverse(); } } class abs_UnaryCRFunction extends UnaryCRFunction { public CR execute(CR x) { return x.abs(); } } class sqrt_UnaryCRFunction extends UnaryCRFunction { public CR execute(CR x) { return x.sqrt(); } } class compose_UnaryCRFunction extends UnaryCRFunction { UnaryCRFunction f1; UnaryCRFunction f2; compose_UnaryCRFunction(UnaryCRFunction func1, UnaryCRFunction func2) { f1 = func1; f2 = func2; } public CR execute(CR x) { return f1.execute(f2.execute(x)); } } class inverseMonotone_UnaryCRFunction extends UnaryCRFunction { // The following variables are final, so that they // can be referenced from the inner class inverseIncreasingCR. // I couldn't find a way to initialize these such that the // compiler accepted them as final without turning them into arrays. final UnaryCRFunction f[] = new UnaryCRFunction[1]; // Monotone increasing. // If it was monotone decreasing, we // negate it. final boolean f_negated[] = new boolean[1]; final CR low[] = new CR[1]; final CR high[] = new CR[1]; final CR f_low[] = new CR[1]; final CR f_high[] = new CR[1]; final int max_msd[] = new int[1]; // Bound on msd of both f(high) and f(low) final int max_arg_prec[] = new int[1]; // base**max_arg_prec is a small fraction // of low - high. final int deriv_msd[] = new int[1]; // Rough approx. of msd of first // derivative. final static BigInteger BIG1023 = BigInteger.valueOf(1023); static final boolean ENABLE_TRACE = false; // Change to generate trace static void trace(String s) { if (ENABLE_TRACE) { System.out.println(s); // Change to Log.v("UnaryCRFunction", s); for Android use. } } inverseMonotone_UnaryCRFunction(UnaryCRFunction func, CR l, CR h) { low[0] = l; high[0] = h; CR tmp_f_low = func.execute(l); CR tmp_f_high = func.execute(h); // Since func is monotone and low < high, the following test // converges. if (tmp_f_low.compareTo(tmp_f_high) > 0) { f[0] = UnaryCRFunction.negateFunction.compose(func); f_negated[0] = true; f_low[0] = tmp_f_low.negate(); f_high[0] = tmp_f_high.negate(); } else { f[0] = func; f_negated[0] = false; f_low[0] = tmp_f_low; f_high[0] = tmp_f_high; } max_msd[0] = low[0].abs().max(high[0].abs()).msd(); max_arg_prec[0] = high[0].subtract(low[0]).msd() - 4; deriv_msd[0] = f_high[0].subtract(f_low[0]) .divide(high[0].subtract(low[0])).msd(); } class inverseIncreasingCR extends CR { final CR arg; inverseIncreasingCR(CR x) { arg = f_negated[0]? x.negate() : x; } // Comparison with a difference of one treated as equality. int sloppy_compare(BigInteger x, BigInteger y) { BigInteger difference = x.subtract(y); if (difference.compareTo(big1) > 0) { return 1; } if (difference.compareTo(bigm1) < 0) { return -1; } return 0; } protected BigInteger approximate(int p) { final int extra_arg_prec = 4; final UnaryCRFunction fn = f[0]; int small_step_deficit = 0; // Number of ineffective steps not // yet compensated for by a binary // search step. int digits_needed = max_msd[0] - p; if (digits_needed < 0) return big0; int working_arg_prec = p - extra_arg_prec; if (working_arg_prec > max_arg_prec[0]) { working_arg_prec = max_arg_prec[0]; } int working_eval_prec = working_arg_prec + deriv_msd[0] - 20; // initial guess // We use a combination of binary search and something like // the secant method. This always converges linearly, // and should converge quadratically under favorable assumptions. // F_l and f_h are always the approximate images of l and h. // At any point, arg is between f_l and f_h, or no more than // one outside [f_l, f_h]. // L and h are implicitly scaled by working_arg_prec. // The scaled values of l and h are strictly between low and high. // If at_left is true, then l is logically at the left // end of the interval. We approximate this by setting l to // a point slightly inside the interval, and letting f_l // approximate the function value at the endpoint. // If at_right is true, r and f_r are set correspondingly. // At the endpoints of the interval, f_l and f_h may correspond // to the endpoints, even if l and h are slightly inside. // F_l and f_u are scaled by working_eval_prec. // Working_eval_prec may need to be adjusted depending // on the derivative of f. boolean at_left, at_right; BigInteger l, f_l; BigInteger h, f_h; BigInteger low_appr = low[0].get_appr(working_arg_prec) .add(big1); BigInteger high_appr = high[0].get_appr(working_arg_prec) .subtract(big1); BigInteger arg_appr = arg.get_appr(working_eval_prec); boolean have_good_appr = (appr_valid && min_prec < max_msd[0]); if (digits_needed < 30 && !have_good_appr) { trace("Setting interval to entire domain"); h = high_appr; f_h = f_high[0].get_appr(working_eval_prec); l = low_appr; f_l = f_low[0].get_appr(working_eval_prec); // Check for clear out-of-bounds case. // Close cases may fail in other ways. if (f_h.compareTo(arg_appr.subtract(big1)) < 0 || f_l.compareTo(arg_appr.add(big1)) > 0) { throw new ArithmeticException("inverse(out-of-bounds)"); } at_left = true; at_right = true; small_step_deficit = 2; // Start with bin search steps. } else { int rough_prec = p + digits_needed/2; if (have_good_appr && (digits_needed < 30 || min_prec < p + 3*digits_needed/4)) { rough_prec = min_prec; } BigInteger rough_appr = get_appr(rough_prec); trace("Setting interval based on prev. appr"); trace("prev. prec = " + rough_prec + " appr = " + rough_appr); h = rough_appr.add(big1) .shiftLeft(rough_prec - working_arg_prec); l = rough_appr.subtract(big1) .shiftLeft(rough_prec - working_arg_prec); if (h.compareTo(high_appr) > 0) { h = high_appr; f_h = f_high[0].get_appr(working_eval_prec); at_right = true; } else { CR h_cr = CR.valueOf(h).shiftLeft(working_arg_prec); f_h = fn.execute(h_cr).get_appr(working_eval_prec); at_right = false; } if (l.compareTo(low_appr) < 0) { l = low_appr; f_l = f_low[0].get_appr(working_eval_prec); at_left = true; } else { CR l_cr = CR.valueOf(l).shiftLeft(working_arg_prec); f_l = fn.execute(l_cr).get_appr(working_eval_prec); at_left = false; } } BigInteger difference = h.subtract(l); for(int i = 0;; ++i) { if (Thread.interrupted() || please_stop) throw new AbortedException(); trace("***Iteration: " + i); trace("Arg prec = " + working_arg_prec + " eval prec = " + working_eval_prec + " arg appr. = " + arg_appr); trace("l = " + l); trace("h = " + h); trace("f(l) = " + f_l); trace("f(h) = " + f_h); if (difference.compareTo(big6) < 0) { // Answer is less than 1/2 ulp away from h. return scale(h, -extra_arg_prec); } BigInteger f_difference = f_h.subtract(f_l); // Narrow the interval by dividing at a cleverly // chosen point (guess) in the middle. { BigInteger guess; boolean binary_step = (small_step_deficit > 0 || f_difference.signum() == 0); if (binary_step) { // Do a binary search step to guarantee linear // convergence. trace("binary step"); guess = l.add(h).shiftRight(1); --small_step_deficit; } else { // interpolate. // f_difference is nonzero here. trace("interpolating"); BigInteger arg_difference = arg_appr.subtract(f_l); BigInteger t = arg_difference.multiply(difference); BigInteger adj = t.divide(f_difference); // tentative adjustment to l to compute guess // If we are within 1/1024 of either end, back off. // This greatly improves the odds of bounding // the answer within the smaller interval. // Note that interpolation will often get us // MUCH closer than this. if (adj.compareTo(difference.shiftRight(10)) < 0) { adj = adj.shiftLeft(8); trace("adjusting left"); } else if (adj.compareTo(difference.multiply(BIG1023) .shiftRight(10)) > 0){ adj = difference.subtract(difference.subtract(adj) .shiftLeft(8)); trace("adjusting right"); } if (adj.signum() <= 0) adj = big2; if (adj.compareTo(difference) >= 0) adj = difference.subtract(big2); guess = (adj.signum() <= 0? l.add(big2) : l.add(adj)); } int outcome; BigInteger tweak = big2; BigInteger f_guess; for(boolean adj_prec = false;; adj_prec = !adj_prec) { CR guess_cr = CR.valueOf(guess) .shiftLeft(working_arg_prec); trace("Evaluating at " + guess_cr + " with precision " + working_eval_prec); CR f_guess_cr = fn.execute(guess_cr); trace("fn value = " + f_guess_cr); f_guess = f_guess_cr.get_appr(working_eval_prec); outcome = sloppy_compare(f_guess, arg_appr); if (outcome != 0) break; // Alternately increase evaluation precision // and adjust guess slightly. // This should be an unlikely case. if (adj_prec) { // adjust working_eval_prec to get enough // resolution. int adjustment = -f_guess.bitLength()/4; if (adjustment > -20) adjustment = - 20; CR l_cr = CR.valueOf(l) .shiftLeft(working_arg_prec); CR h_cr = CR.valueOf(h) .shiftLeft(working_arg_prec); working_eval_prec += adjustment; trace("New eval prec = " + working_eval_prec + (at_left? "(at left)" : "") + (at_right? "(at right)" : "")); if (at_left) { f_l = f_low[0].get_appr(working_eval_prec); } else { f_l = fn.execute(l_cr) .get_appr(working_eval_prec); } if (at_right) { f_h = f_high[0].get_appr(working_eval_prec); } else { f_h = fn.execute(h_cr) .get_appr(working_eval_prec); } arg_appr = arg.get_appr(working_eval_prec); } else { // guess might be exactly right; tweak it // slightly. trace("tweaking guess"); BigInteger new_guess = guess.add(tweak); if (new_guess.compareTo(h) >= 0) { guess = guess.subtract(tweak); } else { guess = new_guess; } // If we keep hitting the right answer, it's // important to alternate which side we move it // to, so that the interval shrinks rapidly. tweak = tweak.negate(); } } if (outcome > 0) { h = guess; f_h = f_guess; at_right = false; } else { l = guess; f_l = f_guess; at_left = false; } BigInteger new_difference = h.subtract(l); if (!binary_step) { if (new_difference.compareTo(difference .shiftRight(1)) >= 0) { ++small_step_deficit; } else { --small_step_deficit; } } difference = new_difference; } } } } public CR execute(CR x) { return new inverseIncreasingCR(x); } } class monotoneDerivative_UnaryCRFunction extends UnaryCRFunction { // The following variables are final, so that they // can be referenced from the inner class inverseIncreasingCR. final UnaryCRFunction f[] = new UnaryCRFunction[1]; // Monotone increasing. // If it was monotone decreasing, we // negate it. final CR low[] = new CR[1]; // endpoints and mispoint of interval final CR mid[] = new CR[1]; final CR high[] = new CR[1]; final CR f_low[] = new CR[1]; // Corresponding function values. final CR f_mid[] = new CR[1]; final CR f_high[] = new CR[1]; final int difference_msd[] = new int[1]; // msd of interval len. final int deriv2_msd[] = new int[1]; // Rough approx. of msd of second // derivative. // This is increased to be an appr. bound // on the msd of |(f'(y)-f'(x))/(x-y)| // for any pair of points x and y // we have considered. // It may be better to keep a copy per // derivative value. monotoneDerivative_UnaryCRFunction(UnaryCRFunction func, CR l, CR h) { f[0] = func; low[0] = l; high[0] = h; mid[0] = l.add(h).shiftRight(1); f_low[0] = func.execute(l); f_mid[0] = func.execute(mid[0]); f_high[0] = func.execute(h); CR difference = h.subtract(l); // compute approximate msd of // ((f_high - f_mid) - (f_mid - f_low))/(high - low) // This should be a very rough appr to the second derivative. // We add a little slop to err on the high side, since // a low estimate will cause extra iterations. CR appr_diff2 = f_high[0].subtract(f_mid[0].shiftLeft(1)).add(f_low[0]); difference_msd[0] = difference.msd(); deriv2_msd[0] = appr_diff2.msd() - difference_msd[0] + 4; } class monotoneDerivativeCR extends CR { CR arg; CR f_arg; int max_delta_msd; monotoneDerivativeCR(CR x) { arg = x; f_arg = f[0].execute(x); // The following must converge, since arg must be in the // open interval. CR left_diff = arg.subtract(low[0]); int max_delta_left_msd = left_diff.msd(); CR right_diff = high[0].subtract(arg); int max_delta_right_msd = right_diff.msd(); if (left_diff.signum() < 0 || right_diff.signum() < 0) { throw new ArithmeticException("fn not monotone"); } max_delta_msd = (max_delta_left_msd < max_delta_right_msd? max_delta_left_msd : max_delta_right_msd); } protected BigInteger approximate(int p) { final int extra_prec = 4; int log_delta = p - deriv2_msd[0]; // Ensure that we stay within the interval. if (log_delta > max_delta_msd) log_delta = max_delta_msd; log_delta -= extra_prec; CR delta = ONE.shiftLeft(log_delta); CR left = arg.subtract(delta); CR right = arg.add(delta); CR f_left = f[0].execute(left); CR f_right = f[0].execute(right); CR left_deriv = f_arg.subtract(f_left).shiftRight(log_delta); CR right_deriv = f_right.subtract(f_arg).shiftRight(log_delta); int eval_prec = p - extra_prec; BigInteger appr_left_deriv = left_deriv.get_appr(eval_prec); BigInteger appr_right_deriv = right_deriv.get_appr(eval_prec); BigInteger deriv_difference = appr_right_deriv.subtract(appr_left_deriv).abs(); if (deriv_difference.compareTo(big8) < 0) { return scale(appr_left_deriv, -extra_prec); } else { if (Thread.interrupted() || please_stop) throw new AbortedException(); deriv2_msd[0] = eval_prec + deriv_difference.bitLength() + 4/*slop*/; deriv2_msd[0] -= log_delta; return approximate(p); } } } public CR execute(CR x) { return new monotoneDerivativeCR(x); } }