/*
 * @(#)MathUtilities.java    2.5.0   5 September 2006
 *
 * Copyright 2006
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.util;

/**
 * <p>The class <code>MathUtilities</code> collects several useful
 * static mathematical functions.  The class may be viewed as an
 * extension of the tools found in <code>java.lang.Math</code>.</p>
 *
 * @author Richard Rasala
 * @version 2.4.0
 * @since   1.2
 */
public class MathUtilities {

    // Random numbers in a specified range

    /**
     * Return a random int r in the range
     * between 0 and a inclusive.
     * 
     * @param a an endpoint of a range
     */
    public static int randomInt(int a) {
        return randomInt(0, a);
    }
    
    
    /**
     * Return a random int r in the range
     * between a and b inclusive.
     * 
     * @param a endpoint 1 of a range
     * @param b endpoint 2 of a range
     */
    public static int randomInt(int a, int b) {
        // To prevent overflow in the calculation, switch to long
        // Also, sort the parameters
        long u;
        long v;
        
        if (a <= b) {
            u = a;
            v = b;
        }
        else {
            u = b;
            v = a;
        }
        
        long r = u + (long)((v - u + 1) * Math.random());
        
        // To prevent round off problems, make sure that r <= v
        if (r > v)
            r = v;
        
        // Now return the int result
        return (int) r;
    }
    
    
    /**
     * Return a random double r in the range
     * between 0 and x.
     * 
     * @param x an endpoint of a range
     */
    public static double randomDouble(double x) {
        return Math.random() * x;
    }
    
    
    /**
     * Return a random double r in the range
     * between x and y.
     * 
     * @param x endpoint 1 of a range
     * @param y endpoint 2 of a range
     */
    public static double randomDouble(double x, double y) {
        // Sort the parameters
        double a;
        double b;
        
        if (x <= y) {
            a = x;
            b = y;
        }
        else {
            a = y;
            b = x;
        }
        
        // Obtain a random value between 0 and 1
        double t = Math.random();
        
        // Perform affine interpolation to
        // obtain a random value between a and b
        double r = a + t * (b - a);
        
        // To prevent round off problems, make sure that a <= r <= b
        if (r < a)
            r = a;
        else if (r > b)
            r = b;
        
        // Now return the result
        return r;
    }
    
    
    // Trigonometric functions in degrees
    
    /** Return the sine of the given angle specified in degrees. */
    public static double sindeg(double degrees) {
        return Math.sin(Math.toRadians(degrees));
    }
    
    
    /** Return the cosine of the given angle specified in degrees. */
    public static double cosdeg(double degrees) {
        return Math.cos(Math.toRadians(degrees));
    }
    
    
    /** Return the tangent of the given angle specified in degrees. */
    public static double tandeg(double degrees) {
        return Math.tan(Math.toRadians(degrees));
    }
    
    
    /**
     * Return the arc sine in degrees of the specified input value
     * in the range -1 to +1.
     */
    public static double asindeg(double value) {
        return Math.toDegrees(Math.asin(value));
    }
    
    
    /**
     * Return the arc cosine in degrees of the specified input value
     * in the range -1 to +1.
     */
    public static double acosdeg(double value) {
        return Math.toDegrees(Math.acos(value));
    }
    
    
    /** Return the arc tangent in degrees of the specified input value. */
    public static double atandeg(double value) {
        return Math.toDegrees(Math.atan(value));
    }
    
    
    /**
     * Return the polar angle in degrees of the point (x, y) where the
     * y-coordinate is given first and the x-coordinate second.
     *
     * @param y the y-coordinate of the point
     * @param x the x-coordinate of the point
     */
    public static double atan2deg(double y, double x) {
        return Math.toDegrees(Math.atan2(y, x));
    }
    
    
    // Greatest Common Divisor
    
    /**
     * <p>Returns the greatest common divisor of the int inputs a and b.</p>
     *
     * <p>The return value will be positive or zero and will be zero only if
     * both a and b are zero.</p>
     */
    public static int GCD(int a, int b) {
        if (b == 0)
            return Math.abs(a);
        
        return GCD(b, a % b);
    }
    
    
    /**
     * <p>Returns the greatest common divisor of the long inputs a and b.</p>
     *
     * <p>The return value will be positive or zero and will be zero only if
     * both a and b are zero.</p>
     */
    public static long GCD(long a, long b) {
        if (b == 0)
            return Math.abs(a);
        
        return GCD(b, a % b);
    }
    
    
    // Least Common Multiple
    
    /**
     * <p>Returns the least common multiple of the int inputs a and b.</p>
     *
     * <p>The return value will be positive or zero and will be zero only if
     * both a and b are zero.</p>
     */
    public static int LCM(int a, int b) {
        if ((a == 0) && (b == 0))
            return 0;
        
        a = Math.abs(a);
        
        b = Math.abs(b);
        
        return a * (b / GCD(a, b));
    }
    
    
    /**
     * <p>Returns the least common multiple of the long inputs a and b.</p>
     *
     * <p>The return value will be positive or zero and will be zero only if
     * both a and b are zero.</p>
     */
    public static long LCM(long a, long b) {
        if ((a == 0) && (b == 0))
            return 0;
        
        a = Math.abs(a);
        
        b = Math.abs(b);
        
        return a * (b / GCD(a, b));
    }
    
    
    // Modulus
    
    /**
     * <p>Returns the modulus of int number relative to int base.</p>
     *
     * <p>Returns n such that n is congruent to number modulo base
     * and 0 &lt;= n &lt; Math.abs(base).</p>
     *
     * <p>Throws <code>ArithmeticException</code> if base is zero.</p>
     */
    public static int modulus(int number, int base) {
        if (base == 0)
            throw new ArithmeticException
                ("Error in modulus: base is zero");
        
        base = Math.abs(base);
        
        number = number % base;
        
        if (number < 0)
            number += base;
        
        return number;
    }
    
    
    /**
     * <p>Returns the modulus of long number relative to long base.</p>
     *
     * <p>Returns n such that n is congruent to number modulo base
     * and 0 &lt;= n &lt; Math.abs(base).</p>
     *
     * <p>Throws <code>ArithmeticException</code> if base is zero.</p>
     */
    public static long modulus(long number, long base) {
        if (base == 0)
            throw new ArithmeticException
                ("Error in modulus: base is zero");
        
        base = Math.abs(base);
        
        number = number % base;
        
        if (number < 0)
            number += base;
        
        return number;
    }
    
    
    /**
     * <p>Returns the n-th power of x, <code>x<sup>n</sup></code>, using
     * the given integer exponent n.</p>
     *
     * <p>Computes using ordinary arithmetic operations.</p>
     *
     * <p>Returns 1.0 regardless of x if n equals 0.</p>
     *
     * <p>Returns <code>Double.NaN</code> if n is negative and x is zero.</p>
     * 
     * <p>Note: Departs from the convention of the Java <code>Math</code>
     * class by calling the method <code>power</code> rather than simply
     * <code>pow</code>.</p>
     *
     * <p>Note added in 2.5.0: This method uses only multiplication and
     * division and does not use exp or log. Recents experiments showed
     * that this method may be <i>less accurate</i> than calling
     * <code>Math.pow</code>. Hence the use of this method is no longer
     * recommended.  It is retained in JPT to avoid breaking old code.</p>
     * 
     * @param x the base
     * @param n the exponent
     */
    public static double power(double x, int n) {
        if (n == 0)
            return 1.0;
            
        if ((n < 0) && (x == 0))
            return Double.NaN;
        
        if (n < 0) {
            n = -n;
            x = 1.0 / x;
        }
        
        // Loop invariant: The final result is the product of the current
        // result and the yet to be completed part of x-to-the-power-n.
        
        // Loop termination: n decreases in each loop cycle; when n == 0,
        // result holds the final value.
        
        double result = 1.0;
        
        while (n > 0) {
            if ((n % 2) != 0) {
                // add one factor of x to result and decrease
                // the exponent n by one
                result *= x;
                n--;
            }
            else {
                // square x and reduce the exponent n by half
                // result is unchanged in this step
                x = x * x;
                n /= 2;
            }
        }
        
        return result;
    }
    
    
    /**
     * <p>Returns the n-th root of x.</p>
     * 
     * <p>If n is 0, returns NaN.</p>
     * 
     * <p>If x is 0 and n &gt; 0, returns 0; otherwise returns NaN.</p>
     * 
     * <p>If x &gt; 0, returns Math.pow(x,1.0/n).</p>
     * 
     * <p>If x &lt; 0 and n is an odd integer, then, with z = -x,
     * returns -Math.pow(z,1.0/n); otherwise, returns NaN.</p>
     * 
     * @param x the base
     * @param n the inverse of the exponent
     */
    public static double root(double x, double n) {
        double NaN = Double.NaN;
        
        if (n == 0)
            return NaN;
        
        if (x == 0) {
            if (n > 0)
                return 0;
            else
                return NaN;
        }
        
        double y = 1.0 / n;
        
        if (x > 0)
            return Math.pow(x, y);
        
        int nn = (int) n;
        
        if (n == nn) {
            if ((nn % 2) != 0) {
                return - Math.pow(-x, y);
            }
        }
        
        return NaN;
    }
    
}
