/*
* Copyright (c) 2003, the JUNG Project and the Regents of the University 
* of California
* All rights reserved.
*
* This software is open-source under the BSD license; see either
* "license.txt" or
* http://jung.sourceforge.net/license.txt for a description.
*/
package edu.uci.ics.jung.utils;

import java.io.PrintStream;
/**
 * Implements additional mathematical functions to within numerical
 * precision tolerances, and determines the parameters of the 
 * floating point representation.
 */
public final class NumericalPrecision {
	/**
	 * Typical meaningful precision for numerical calculations.
	 */
	static private double defaultNumericalPrecision = 0;
	/**
	 * Typical meaningful small number for numerical calculations.
	 */
	static private double smallNumber = 0;
	/**
	 * Radix used by floating-point numbers.
	 */
	static private int radix = 0;
	/**
	 * Largest positive value which, when added to 1.0, yields 0.
	 */
	static private double machinePrecision = 0;
	/**
	 * Largest positive value which, when subtracted to 1.0, yields 0.
	 */
	static private double negativeMachinePrecision = 0;
	/**
	 * Smallest number different from zero.
	 */
	static private double smallestNumber = 0;
	/**
	 * Largest possible number
	 */
	static private double largestNumber = 0;
	/**
	 * Largest argument for the exponential
	 */
	static private double largestExponentialArgument = 0;
	/**
	 * Values used to compute human readable scales.
	 */
	private static final double scales[] = { 1.25, 2, 2.5, 4, 5, 7.5, 8, 10 };
	private static final double semiIntegerScales[] =
		{ 2, 2.5, 4, 5, 7.5, 8, 10 };
	private static final double integerScales[] = { 2, 4, 5, 8, 10 };

	private static void computeLargestNumber() {
		double floatingRadix = getRadix();
		double fullMantissaNumber =
			1.0d - floatingRadix * getNegativeMachinePrecision();
		while (!Double.isInfinite(fullMantissaNumber)) {
			largestNumber = fullMantissaNumber;
			fullMantissaNumber *= floatingRadix;
		}
	}
	private static void computeMachinePrecision() {
		double floatingRadix = getRadix();
		double inverseRadix = 1.0d / floatingRadix;
		machinePrecision = 1.0d;
		double tmp = 1.0d + machinePrecision;
		while (tmp - 1.0d != 0.0d) {
			machinePrecision *= inverseRadix;
			tmp = 1.0d + machinePrecision;
		}
	}
	private static void computeNegativeMachinePrecision() {
		double floatingRadix = getRadix();
		double inverseRadix = 1.0d / floatingRadix;
		negativeMachinePrecision = 1.0d;
		double tmp = 1.0d - negativeMachinePrecision;
		while (tmp - 1.0d != 0.0d) {
			negativeMachinePrecision *= inverseRadix;
			tmp = 1.0d - negativeMachinePrecision;
		}
	}
	private static void computeRadix() {
		double a = 1.0d;
		double tmp1, tmp2;
		do {
			a += a;
			tmp1 = a + 1.0d;
			tmp2 = tmp1 - a;
		} while (tmp2 - 1.0d != 0.0d);
		double b = 1.0d;
		while (radix == 0) {
			b += b;
			tmp1 = a + b;
			radix = (int) (tmp1 - a);
		}
	}
	private static void computeSmallestNumber() {
		double floatingRadix = getRadix();
		double inverseRadix = 1.0d / floatingRadix;
		double fullMantissaNumber =
			1.0d - floatingRadix * getNegativeMachinePrecision();
		while (fullMantissaNumber != 0.0d) {
			smallestNumber = fullMantissaNumber;
			fullMantissaNumber *= inverseRadix;
		}
	}
	public static double defaultNumericalPrecision() {
		if (defaultNumericalPrecision == 0)
			defaultNumericalPrecision = Math.sqrt(getMachinePrecision());
		return defaultNumericalPrecision;
	}
	/**
	 * @return boolean	true if the difference between a and b is
	 * less than the default numerical precision
	 * @param a double
	 * @param b double
	 */
	public static boolean equal(double a, double b) {
		return equal(a, b, defaultNumericalPrecision());
	}
	/**
	 * @return boolean	true if the relative difference between a and b
	 * is less than precision
	 * @param a double
	 * @param b double
	 * @param precision double
	 */
	public static boolean equal(double a, double b, double precision) {
		double norm = Math.max(Math.abs(a), Math.abs(b));
		return norm < precision || Math.abs(a - b) < precision * norm;
	}
	public static double getLargestExponentialArgument() {
		if (largestExponentialArgument == 0)
			largestExponentialArgument = Math.log(getLargestNumber());
		return largestExponentialArgument;
	}
	/**
	 * (c) Copyrights Didier BESSET, 1999, all rights reserved.
	 */
	public static double getLargestNumber() {
		if (largestNumber == 0)
			computeLargestNumber();
		return largestNumber;
	}
	public static double getMachinePrecision() {
		if (machinePrecision == 0)
			computeMachinePrecision();
		return machinePrecision;
	}
	public static double getNegativeMachinePrecision() {
		if (negativeMachinePrecision == 0)
			computeNegativeMachinePrecision();
		return negativeMachinePrecision;
	}
	public static int getRadix() {
		if (radix == 0)
			computeRadix();
		return radix;
	}
	public static double getSmallestNumber() {
		if (smallestNumber == 0)
			computeSmallestNumber();
		return smallestNumber;
	}
	public static void printParameters(PrintStream printStream) {
		printStream.println("Floating-point machine parameters");
		printStream.println("---------------------------------");
		printStream.println(" ");
		printStream.println("radix = " + getRadix());
		printStream.println("Machine precision = " + getMachinePrecision());
		printStream.println(
			"Negative machine precision = " + getNegativeMachinePrecision());
		printStream.println("Smallest number = " + getSmallestNumber());
		printStream.println("Largest number = " + getLargestNumber());
		return;
	}
	public static void reset() {
		defaultNumericalPrecision = 0;
		smallNumber = 0;
		radix = 0;
		machinePrecision = 0;
		negativeMachinePrecision = 0;
		smallestNumber = 0;
		largestNumber = 0;
	}
	/**
	 * This method returns the specified value rounded to
	 * the nearest integer multiple of the specified scale.
	 *
	 * @param value number to be rounded
	 * @param scale defining the rounding scale
	 * @return rounded value
	 */
	public static double roundTo(double value, double scale) {
		return Math.round(value / scale) * scale;
	}
	/**
	 * Round the specified value upward to the next scale value.
	 * @param value the value to be rounded.
	 * @param integerValued a flag specified whether integer scale are used, otherwise double scale is used.
	 * @return a number rounded upward to the next scale value.
	 */
	public static double roundToScale(double value, boolean integerValued) {
		double[] scaleValues;
		int orderOfMagnitude =
			(int) Math.floor(Math.log(value) / Math.log(10.0));
		if (integerValued) {
			orderOfMagnitude = Math.max(1, orderOfMagnitude);
			if (orderOfMagnitude == 1)
				scaleValues = integerScales;
			else if (orderOfMagnitude == 2)
				scaleValues = semiIntegerScales;
			else
				scaleValues = scales;
		} else
			scaleValues = scales;
		double exponent = Math.pow(10.0, orderOfMagnitude);
		double rValue = value / exponent;
		for (int n = 0; n < scaleValues.length; n++) {
			if (rValue <= scaleValues[n])
				return scaleValues[n] * exponent;
		}
		return exponent; // Should never reach here
	}

	/**
	 * Returns the smallest number that the system can handle.
	 * 
	 * @return double The smallest number the machine can handle.
	 */
	public static double smallNumber() {
		if (smallNumber == 0)
			smallNumber = Math.sqrt(getSmallestNumber());
		return smallNumber;
	}
}