import com.sgi.math.CR; // See http://www.hpl.hp.com/personal/Hans_Boehm/crcalc/CRCalc.html import java.util.Random; class TrueSine { static CR ln2 = CR.valueOf(2).ln(); static CR zero = CR.valueOf(0); static CR half = CR.valueOf(0.5); static CR one = CR.valueOf(1); static CR two = CR.valueOf(2); /** If paranoid, then compare two different ways of computing highOrderBit. */ static boolean paranoid = false; /* This is used in a series of comparisons. Taking logarithms and converting to integers seems to have a minor rounding bug in it. */ static CR[] negativePowersOfTwo; static { negativePowersOfTwo = new CR[2000]; negativePowersOfTwo[0] = one; for (int i = 1; i < negativePowersOfTwo.length; i++) { negativePowersOfTwo[i] = negativePowersOfTwo[i-1].divide(two); } } /** Return the power of two equal to the high order bit of b. Assumes |b| < 2. */ static CR highOrderBit(CR b) { b = b.abs(); for (int i = 0; i < negativePowersOfTwo.length; i++) { CR test = negativePowersOfTwo[i]; if (b.compareTo(test, -64, -128) >= 0) { return test; } } return zero; } /** Buggy version of highOrderBit */ static CR buggyHighOrderBit(CR b) { long d = b.abs().ln().divide(ln2).subtract(half).longValue(); CR hob = CR.valueOf(d).multiply(ln2).exp(); return hob; } /** Takes the difference of a and b divided by the high order bit of b, and returns the negative of the logarithm base 2 of that quantity. It is the "bit" in which a and b differ, where the actual mantissa bits are numbered from 0 (implicit leading 1) to 52. */ static double relativeDiff(CR a, CR b) { CR tb = highOrderBit(b); if (paranoid) { CR tb2 = buggyHighOrderBit(b); if (tb.doubleValue() != tb2.doubleValue()) { System.out.println("High order bits differ for " + b.doubleValue() + ", good=" + tb.doubleValue() + ", bad=" + tb2.doubleValue()); } } return - (a.subtract(b).divide(tb).abs().ln().divide(ln2).doubleValue()); } /** For unexceptional values, next(x) and prev(x) are the closest unequal doubles greater and less than d in magnitude. */ static double next(double d) { return Double.longBitsToDouble(Double.doubleToLongBits(d) + 1); } static double prev(double d) { return Double.longBitsToDouble(Double.doubleToLongBits(d) - 1); } /** Do a test of the high order bit function. */ static void testHighOrderBit() { double a = 1.0; double b = next(a); double c = prev(a); for (int i = 0; i < 64; i++) { double hoba = highOrderBit(CR.valueOf(a)).doubleValue(); double hobb = highOrderBit(CR.valueOf(b)).doubleValue(); double hobc = highOrderBit(CR.valueOf(c)).doubleValue(); System.out.println("a = " + a + ", b = " + b + ", c = " + c + ", hoba = " + hoba + ", hobb = " + hobb + ", hobc = " + hobc); a = a / 2.0; b = b / 2.0; c = c / 2.0; } } /** Do a test of the high order bit function. This test doesn't seem to reveal the problems exposed by setting "paranoid" to true. */ static void testBuggyHighOrderBit() { double a = 1.0; double b = Math.sqrt(2.0); for (int i = 0; i < 64; i++) { double a_minus = prev(a); double a_plus = next(a); double b_minus = prev(b); double b_plus = next(b); double hoba = highOrderBit(CR.valueOf(a)).doubleValue(); double hobb = highOrderBit(CR.valueOf(b)).doubleValue(); double hobam = highOrderBit(CR.valueOf(a_minus)).doubleValue(); double hobbm = highOrderBit(CR.valueOf(b_minus)).doubleValue(); double hobap = highOrderBit(CR.valueOf(a_plus)).doubleValue(); double hobbp = highOrderBit(CR.valueOf(b_plus)).doubleValue(); System.out.println(" a = " + a + ", am = " + a_minus + ", ap = " + a_plus); System.out.println("ha = " + hoba + ", ham = " + hobam + ", hap = " + hobap); System.out.println(" b = " + b + ", bm = " + b_minus + ", bp = " + b_plus); System.out.println("hb = " + hobb + ", hbm = " + hobbm + ", hbp = " + hobbp); System.out.println(); a = a / 2.0; b = b / 2.0; } } /** Do a test of the relative difference function */ static void testRelativeDiff() { double a = 0.5; double b = 1.0; for (int i = 1; i < 60; i++) { double c = a + b; if (c == b) break; System.out.println("i = " + i + ", a = " + a + ", rd = " + relativeDiff(CR.valueOf(c), CR.valueOf(b))); a = a / 2.0; } } /** Test Math.sin() (repeatably). If the result differs in by more than 1/2 LSB, report it. */ static void testSine() { double scale9 = 1024 * 1024 * 1024; // 10 ** 9 double scale18 = scale9 * scale9; // 10 ** 18 double scale36 = scale18 * scale18; // 10 ** 36 double scale288 = scale36 * scale36 * scale36 * scale36 * scale36 * scale36 * scale36 * scale36 ; // 10 ** 288; double scale = scale288 * scale9 * 1024.0; // 10 ** 300; Random r = new Random(0x123456787654321L); for (int i = 0; i < 100000; i++) { double d = r.nextDouble() * Math.PI * scale; double js = Math.sin(d); CR crs = CR.valueOf(d).sin(); double dcrs = crs.doubleValue(); double relative_diff = relativeDiff(crs, CR.valueOf(js)); if (relative_diff < 53.0) { System.out.println("I=" + i + "\tX=" + d + "\tJS=" + js+"\tDIFF=" + Long.toHexString(Math.abs(Double.doubleToLongBits(js)- Double.doubleToLongBits(dcrs))) + " RELATIVE_DIFF=" + relative_diff); } } } public static void main (String[] args) { // testBuggyHighOrderBit(); // testHighOrderBit(); // testRelativeDiff(); testSine(); } }