Fix cos() prescaling, clean up inverse, add tests

Cosine arguments close to pi were not correctly prescaled, resulting
in a significantly more expensive computation.

The choice of binary search vs interpolation in the inverse function
algorithm was somewhat random, in that it sometimes counted binary
search steps as "successful" and sometimes not.  This caused more binary
search steps than intended, which unfortunately sometimes improved matters.
This fixes the unpredictability and adjust the heuristics to get
performance on tan(atan(large)) back to roughly where we started.

Increase precision a bit more aggressively in the inverse function to
potentially allow superlinear convergence in more cases.  I couldn't
reconstruct any meaningful rationale for the old formula.

Add SlowCRTest.  Reorganize and fix tests to be consistent with Android
guidelines, more or less.  Update COPYRIGHT.txt to reflect the fact
that we own SlowCRTest.

Change-Id: I8e2d3d11672ddfe24fd8ad0c74d05ef11b12cb03
diff --git a/Android.mk b/Android.mk
index 0457bd2..a42c943 100644
--- a/Android.mk
+++ b/Android.mk
@@ -22,21 +22,11 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_MODULE := cr
+LOCAL_MODULE := CR
 LOCAL_MODULE_TAGS := optional
 LOCAL_SDK_VERSION := 8
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
-# build the test apk
-#-------------------------------
-include $(CLEAR_VARS)
+include $(call all-makefiles-under,$(LOCAL_PATH))
 
-LOCAL_PACKAGE_NAME := CRTest
-LOCAL_SDK_VERSION := 8
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, tests)
-LOCAL_MANIFEST_FILE := tests/AndroidManifest.xml
-LOCAL_STATIC_JAVA_LIBRARIES := cr
-
-include $(BUILD_PACKAGE)
diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt
index 8ff6d2e..ac64049 100644
--- a/COPYRIGHT.txt
+++ b/COPYRIGHT.txt
@@ -1,4 +1,5 @@
-The following license covers all material remaining in this directory.
+The following license covers all material remaining in this directory,
+except SlowCRTest.java, which is covered by an Apache 2.0 license.
 
 Copyright © 1999, Silicon Graphics, Inc. -- ALL RIGHTS RESERVED
 
diff --git a/src/com/hp/creals/CR.java b/src/com/hp/creals/CR.java
index 3b76df7..76df263 100644
--- a/src/com/hp/creals/CR.java
+++ b/src/com/hp/creals/CR.java
@@ -825,12 +825,13 @@
 * The trigonometric cosine function.
 */
     public CR cos() {
-        BigInteger pi_multiples = divide(PI).get_appr(0);
-        BigInteger abs_pi_multiples = pi_multiples.abs();
-        if (abs_pi_multiples.compareTo(big2) >= 0) {
+        BigInteger halfpi_multiples = divide(PI).get_appr(-1);
+        BigInteger abs_halfpi_multiples = halfpi_multiples.abs();
+        if (abs_halfpi_multiples.compareTo(big2) >= 0) {
             // Subtract multiples of PI
+            BigInteger pi_multiples = scale(halfpi_multiples, -1);
             CR adjustment = PI.multiply(CR.valueOf(pi_multiples));
-            if (abs_pi_multiples.and(big1).signum() != 0) {
+            if (pi_multiples.and(big1).signum() != 0) {
                 return subtract(adjustment).cos().negate();
             } else {
                 return subtract(adjustment).cos();
diff --git a/src/com/hp/creals/UnaryCRFunction.java b/src/com/hp/creals/UnaryCRFunction.java
index 47c626c..594e811 100644
--- a/src/com/hp/creals/UnaryCRFunction.java
+++ b/src/com/hp/creals/UnaryCRFunction.java
@@ -39,6 +39,7 @@
 // 5/2014 Added Strings to ArithmeticExceptions
 
 package com.hp.creals;
+// import android.util.Log;
 
 import java.math.BigInteger;
 
@@ -283,6 +284,14 @@
     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);
@@ -322,13 +331,11 @@
             return 0;
         }
         protected BigInteger approximate(int p) {
-            final boolean trace = false;        // Change to generate trace
             final int extra_arg_prec = 4;
             final UnaryCRFunction fn = f[0];
-            int small_steps = 0;        // Number of preceding ineffective
-                                        // steps.  If this number gets >= 2,
-                                        // we perform a binary search step
-                                        // to ensure forward progress.
+            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;
@@ -339,8 +346,7 @@
                         // initial guess
             // We use a combination of binary search and something like
             // the secant method.  This always converges linearly,
-            // and should converge quadratically for well-behaved
-            // functions.
+            // 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].
@@ -366,9 +372,7 @@
             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) {
-                if (trace) {
-                    System.out.println("Setting interval to entire domain");
-                }
+                trace("Setting interval to entire domain");
                 h = high_appr;
                 f_h = f_high[0].get_appr(working_eval_prec);
                 l = low_appr;
@@ -381,7 +385,7 @@
                   }
                 at_left = true;
                 at_right = true;
-                small_steps = 2;        // Start with bin search step.
+                small_step_deficit = 2;        // Start with bin search steps.
             } else {
                 int rough_prec = p + digits_needed/2;
 
@@ -390,11 +394,8 @@
                     rough_prec = min_prec;
                 }
                 BigInteger rough_appr = get_appr(rough_prec);
-                if (trace) {
-                    System.out.println("Setting interval based on prev. appr");
-                    System.out.println("prev. prec = " + rough_prec
-                                       + " appr = " + rough_appr);
-                }
+                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)
@@ -422,14 +423,12 @@
             for(int i = 0;; ++i) {
                 if (Thread.interrupted() || please_stop)
                     throw new AbortedError();
-                if (trace) {
-                    System.out.println("***Iteration: " + i);
-                    System.out.println("Arg prec = " + working_arg_prec
-                                + " eval prec = " + working_eval_prec
-                                + " arg appr. = " + arg_appr);
-                    System.out.println("l = " + l + "; h = " + h);
-                    System.out.println("f(l) = " + f_l + "; f(h) = " + f_h);
-                }
+                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);
@@ -439,29 +438,35 @@
                 // chosen point (guess) in the middle.
                 {
                     BigInteger guess;
-                    if (small_steps >= 2 || f_difference.signum() == 0) {
+                    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);
-                      if (adj.compareTo(difference.shiftRight(2)) < 0) {
-                        // Very close to left side of interval;
-                        // move closer to center.
-                        // If one of the endpoints is very close to
-                        // the answer, this slows conversion a bit.
-                        // But it greatly increases the probability
-                        // that the answer will be in the smaller
-                        // subinterval.
-                        adj = adj.shiftLeft(1);
-                      } else if (adj.compareTo(difference.multiply(CR.big3)
-                                                       .shiftRight(2)) > 0){
+                          // 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(1));
+                                                  .shiftLeft(8));
+                        trace("adjusting right");
                       }
                       if (adj.signum() <= 0)
                           adj = big2;
@@ -475,15 +480,10 @@
                     for(boolean adj_prec = false;; adj_prec = !adj_prec) {
                         CR guess_cr = CR.valueOf(guess)
                                         .shiftLeft(working_arg_prec);
-                        if (trace) {
-                            System.out.println("Evaluating at " + guess_cr
-                                        + " with precision "
-                                        + working_eval_prec);
-                        }
+                        trace("Evaluating at " + guess_cr
+                              + " with precision " + working_eval_prec);
                         CR f_guess_cr = fn.execute(guess_cr);
-                        if (trace) {
-                            System.out.println("fn value = " + f_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;
@@ -493,19 +493,16 @@
                         if (adj_prec) {
                             // adjust working_eval_prec to get enough
                             // resolution.
-                            int adjustment = deriv_msd[0] > 0 ? -20 :
-                                                        deriv_msd[0] - 20;
+                            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;
-                            if (trace) {
-                                System.out.println("New eval prec = "
-                                        + working_eval_prec
-                                        + (at_left? "(at left)" : "")
-                                        + (at_right? "(at right)" : ""));
-                            }
+                            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 {
@@ -522,7 +519,7 @@
                         } else {
                             // guess might be exactly right; tweak it
                             // slightly.
-                            if (trace) System.out.println("tweaking guess");
+                            trace("tweaking guess");
                             BigInteger new_guess = guess.add(tweak);
                             if (new_guess.compareTo(h) >= 0) {
                                 guess = guess.subtract(tweak);
@@ -545,11 +542,13 @@
                         at_left = false;
                     }
                     BigInteger new_difference = h.subtract(l);
-                    if (new_difference.compareTo(difference
-                                                 .shiftRight(1)) >= 0) {
-                        ++small_steps;
-                    } else {
-                        small_steps = 0;
+                    if (!binary_step) {
+                        if (new_difference.compareTo(difference
+                                                     .shiftRight(1)) >= 0) {
+                            ++small_step_deficit;
+                        } else {
+                            --small_step_deficit;
+                        }
                     }
                     difference = new_difference;
                 }
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..59a564a
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Other files in this directory carry a different license.
+
+LOCAL_PATH := $(call my-dir)
+
+# build the test apk
+#-------------------------------
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CRTests
+LOCAL_SDK_VERSION := 8
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-java-files-under, ../src)
+# Empirically, LOCAL_INSTRUMENTATION_FOR doesn't work, perhaps because it
+# expects an apk, not a library.
+
+include $(BUILD_PACKAGE)
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index a411798..48e1c98 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -1,5 +1,5 @@
 <!--
- Copyright (C) 2014 The Android Open Source Project
+ Copyright (C) 2015 The Android Open Source Project
 
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
@@ -14,6 +14,9 @@
  limitations under the License.
 
  Other files in this directory carry a different license.
+
+ Note that the targetPackage is the test itself; that avoids building
+ a second apk solely to test the library.
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
@@ -26,7 +29,8 @@
 
     <instrumentation
         android:name="android.test.InstrumentationTestRunner"
-        android:targetPackage="com.hp.creals" />
+        android:targetPackage="com.hp.creals.tests"
+        android:label="Constructive Reals tests" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tests/README.txt b/tests/README.txt
new file mode 100644
index 0000000..a940b2c
--- /dev/null
+++ b/tests/README.txt
@@ -0,0 +1,16 @@
+Run on Android with
+
+1) Build the tests.
+2) adb install <tree root>/out/target/product/generic/data/app/CRTests/CRTests.apk
+3) adb shell am instrument -w com.hp.creals.tests/android.test.InstrumentationTestRunner
+
+The last step takes around 10 minutes on a Nexus 5.
+(CRTest is quick, SlowCRTest is not, especially not the final trig function
+test.)
+
+Note that Random seeds are not set.  Hence repreated runs should improve
+coverage at the cost of reproducibility.  Failing arguments should however
+be printed.
+
+We expect that this test is much too nondeterministic to be usable for any kind
+of performance evaluation.  Please don't try.
diff --git a/tests/com/hp/creals/CRTest.java b/tests/src/com/hp/creals/CRTest.java
similarity index 100%
rename from tests/com/hp/creals/CRTest.java
rename to tests/src/com/hp/creals/CRTest.java
diff --git a/tests/src/com/hp/creals/SlowCRTest.java b/tests/src/com/hp/creals/SlowCRTest.java
new file mode 100644
index 0000000..addfe92
--- /dev/null
+++ b/tests/src/com/hp/creals/SlowCRTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* THIS TYPICALLY TAKES > 10 MINUTES TO RUN!  It shold generate no output during that time. */
+
+package com.hp.creals;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import java.math.BigInteger;
+import java.util.Random;
+
+public class SlowCRTest extends TestCase {
+    private static void check(boolean x, String s) {
+        if (!x) throw new AssertionFailedError(s);
+    }
+    final static int TEST_PREC = -200; // 200 bits to the right of
+                                       // binary point.
+    final static int NRANDOM = 100;    // Number of random values to
+                                       // test.  Bigger ==> slower
+    private static void checkEq(CR x, CR y, String s) {
+        if (x.compareTo(y, TEST_PREC) != 0) throw new AssertionFailedError(s);
+    }
+    private static void checkApprEq(CR x, CR y, String s) {
+        BigInteger abs_difference = x.subtract(y).get_appr(TEST_PREC).abs();
+        if (abs_difference.compareTo(BigInteger.ONE) > 0)
+                throw new AssertionFailedError(s);
+    }
+    private static void checkApprEq(double x, double y, String s) {
+        if (Math.abs(x - y) > 0.000001) throw new AssertionFailedError(s);
+    }
+    final static BigInteger MASK =
+            BigInteger.ONE.shiftLeft(-TEST_PREC).subtract(BigInteger.ONE);
+    private static boolean isApprInt(CR x) {
+        BigInteger appr = x.get_appr(TEST_PREC);
+        return appr.and(MASK).signum() == 0;
+    }
+
+    final static CR ZERO = CR.valueOf(0);
+    final static CR ONE = CR.valueOf(1);
+    final static CR TWO = CR.valueOf(2);
+    final static CR BIG = CR.valueOf(200).exp();
+    final static CR SMALL = BIG.inverse();
+
+    final static UnaryCRFunction ASIN = UnaryCRFunction.asinFunction;
+    final static UnaryCRFunction ACOS = UnaryCRFunction.acosFunction;
+    final static UnaryCRFunction ATAN = UnaryCRFunction.atanFunction;
+    final static UnaryCRFunction TAN = UnaryCRFunction.tanFunction;
+    final static UnaryCRFunction COSINE = UnaryCRFunction.sinFunction
+                                             .monotoneDerivative(ZERO, CR.PI);
+
+    // Perform some consistency checks on trig functions at x
+    // We assume that x is within floating point range.
+    private static void checkTrig(CR x) {
+        double xAsDouble = x.doubleValue();
+        if (Math.abs(xAsDouble) < 1000000.0) {
+            checkApprEq(x.sin().doubleValue(), Math.sin(xAsDouble),
+                          "sin float compare:" + xAsDouble);
+            checkApprEq(x.cos().doubleValue(), Math.cos(xAsDouble),
+                          "cos float compare:" + xAsDouble);
+            checkApprEq(TAN.execute(x).doubleValue(), Math.tan(xAsDouble),
+                          "tan float compare:" + xAsDouble);
+            checkApprEq(ATAN.execute(x).doubleValue(), Math.atan(xAsDouble),
+                          "atan float compare:" + xAsDouble);
+        }
+        if (Math.abs(xAsDouble) < 1.0) {
+            checkApprEq(ASIN.execute(x).doubleValue(), Math.asin(xAsDouble),
+                          "asin float compare:" + xAsDouble);
+            checkApprEq(ACOS.execute(x).doubleValue(), Math.acos(xAsDouble),
+                          "acos float compare:" + xAsDouble );
+        }
+        if (xAsDouble < 3.1415926535 && xAsDouble > 0.0) {
+            checkApprEq(COSINE.execute(x).doubleValue(), Math.cos(xAsDouble),
+                          "deriv(sin) float compare:"  + xAsDouble);
+            checkApprEq(COSINE.execute(x), x.cos(),
+                          "deriv(sin) float compare:"  + xAsDouble);
+        }
+        // Check that sin(x+v) = sin(x)cos(v) + cos(x)sin(v)
+        // for a couple of different values of v.
+        for (int i = 1; i <= 5; ++i) {
+            CR v = CR.valueOf(i);
+            checkApprEq(
+                x.add(v).sin(),
+                x.sin().multiply(v.cos()).add(x.cos().multiply(v.sin())),
+                "Angle sum formula failed for " + xAsDouble + " + " + i);
+        }
+        checkApprEq(x.cos().multiply(x.cos()).add(x.sin().multiply(x.sin())),
+                    CR.valueOf(1),
+                    "sin(x)^2 + cos(x)^2 != 1:" + xAsDouble);
+        // Check that inverses are consistent
+        checkApprEq(x, TAN.execute(ATAN.execute(x)),
+                      "tan(atan(" + xAsDouble + ")" );
+        CR tmp = ACOS.execute(x.cos());
+        // Result or its inverse should differ from x by an
+        // exact multiple of pi.
+        check(isApprInt(tmp.subtract(x).divide(CR.PI))
+              || isApprInt(tmp.add(x).divide(CR.PI)),
+              "acos(cos):" + xAsDouble);
+        tmp = ASIN.execute(x.sin());
+        // Result or its inverse should differ from x by an
+        // exact multiple of pi.
+        check(isApprInt(tmp.subtract(x).divide(CR.PI))
+              || isApprInt(tmp.add(x).divide(CR.PI)),
+              "acos(cos):" + xAsDouble);
+    }
+
+    private static void checkExpLn(CR x) {
+        double xAsDouble = x.doubleValue();
+        if (Math.abs(xAsDouble) < 10.0) {
+            checkApprEq(x.exp().doubleValue(), Math.exp(xAsDouble),
+                          "exp float compare:" + xAsDouble);
+        }
+        if (Math.abs(xAsDouble) <= 1000.0) {
+            checkApprEq(x, x.exp().ln(), "ln(exp) failed:" + xAsDouble);
+            checkApprEq(x.multiply(CR.valueOf(2)).exp(),
+
+                        x.exp().multiply(x.exp()),
+                        "exp^2 failed:" + xAsDouble);
+        }
+        if (xAsDouble > 0.000000001) {
+            checkApprEq(x.ln().doubleValue(), Math.log(xAsDouble),
+                          "exp float compare:" + xAsDouble);
+            checkApprEq(x, x.ln().exp(), "exp(ln) failed:" + xAsDouble);
+            checkApprEq(x.ln().divide(CR.valueOf(2)), x.sqrt().ln(),
+                        "ln(sqrt) failed:" + xAsDouble);
+            // Check that ln(xv) = ln(x) + ln(v) for various v
+            for (int i = 1; i <= 5; ++i) {
+                CR v = CR.valueOf(i);
+                checkApprEq(
+                    x.ln().add(v.ln()),
+                    x.multiply(v).ln(),
+                    "ln(product) formula failed for:" + xAsDouble + "," + i);
+            }
+        }
+    }
+
+    private static void checkBasic(CR x) {
+        checkApprEq(x.abs().sqrt().multiply(x.abs().sqrt()), x.abs(),
+                    "sqrt*sqrt:" + x.doubleValue());
+        if (!x.get_appr(TEST_PREC).equals(BigInteger.ZERO)) {
+            checkApprEq(x.inverse().inverse(), x,
+                        "inverse(inverse):" + x.doubleValue());
+        }
+    }
+
+    public void testSlowTrig() {
+        checkApprEq(ACOS.execute(ZERO), CR.PI.divide(TWO), "acos(0)");
+        checkApprEq(ACOS.execute(ONE), ZERO, "acos(1)");
+        checkApprEq(ACOS.execute(ONE.negate()), CR.PI, "acos(-1)");
+        checkApprEq(ASIN.execute(ZERO), ZERO, "asin(0)");
+        checkApprEq(ASIN.execute(ONE), CR.PI.divide(TWO), "asin(1)");
+        checkApprEq(ASIN.execute(ONE.negate()),
+                                 CR.PI.divide(TWO).negate(), "asin(-1)");
+        checkTrig(ZERO);
+        CR BIG = CR.valueOf(200).exp();
+        checkTrig(BIG);
+        checkTrig(BIG.negate());
+        checkTrig(SMALL);
+        checkTrig(SMALL.negate());
+        checkTrig(CR.PI);
+        checkTrig(CR.PI.subtract(SMALL));
+        checkTrig(CR.PI.add(SMALL));
+        checkTrig(CR.PI.negate());
+        checkTrig(CR.PI.negate().subtract(SMALL));
+        checkTrig(CR.PI.negate().add(SMALL));
+        Random r = new Random();  // Random seed!
+        for (int i = 0; i < NRANDOM; ++i) {
+            double d = Math.exp(2.0 * r.nextDouble() - 1.0);
+            if (r.nextBoolean()) d = -d;
+            final CR x = CR.valueOf(d);
+            checkTrig(x);
+        }
+        // And a few big ones
+        for (int i = 0; i < 10; ++i) {
+            double d = Math.exp(200.0 * r.nextDouble());
+            if (r.nextBoolean()) d = -d;
+            final CR x = CR.valueOf(d);
+            checkTrig(x);
+        }
+    }
+
+    public void testSlowExpLn() {
+        checkApprEq(CR.valueOf(1).ln(), CR.valueOf(0), "ln(1) != 0");
+        checkExpLn(CR.valueOf(0));
+        CR BIG = CR.valueOf(200).exp();
+        checkExpLn(BIG);
+        checkExpLn(BIG.negate());
+        checkExpLn(SMALL);
+        checkExpLn(SMALL.negate());
+        checkExpLn(CR.PI);
+        checkExpLn(ONE);
+        checkExpLn(ONE.subtract(SMALL));
+        checkExpLn(ONE.negate().subtract(SMALL));
+        Random r = new Random();  // Random seed!
+        for (int i = 0; i < NRANDOM; ++i) {
+            double d = Math.exp(10.0 * r.nextDouble() - 1.0);
+            if (r.nextBoolean()) d = -d;
+            final CR x = CR.valueOf(d);
+            checkExpLn(x);
+        }
+        // And a few big ones
+        for (int i = 0; i < 10; ++i) {
+            double d = Math.exp(200.0 * r.nextDouble());
+            if (r.nextBoolean()) d = -d;
+            final CR x = CR.valueOf(d);
+            checkExpLn(x);
+        }
+    }
+
+    public void testSlowBasic() {
+        checkApprEq(ZERO.sqrt(), ZERO, "sqrt(0)");
+        checkApprEq(ZERO.abs(), ZERO, "abs(0)");
+        Random r = new Random();  // Random seed!
+        for (int i = 0; i < NRANDOM; ++i) {
+            double d = Math.exp(10.0 * r.nextDouble() - 1.0);
+            if (r.nextBoolean()) d = -d;
+            final CR x = CR.valueOf(d);
+            checkBasic(x);
+        }
+        // And a few very big ones, but within IEEE double range
+        for (int i = 0; i < 10; ++i) {
+            double d = Math.exp(600.0 * r.nextDouble());
+            if (r.nextBoolean()) d = -d;
+            final CR x = CR.valueOf(d);
+            checkBasic(x);
+        }
+    }
+}