package javacodebook.core.bignumber;

import java.math.*;

/**
 * Mit Hilfe dieser Klasse lassen sich die mathematischen
 * Konstanten Pi und e mit sowie einige trig. Funktionen
 * mit beliebiger Genauigkeit berechnen.
 * @author Mark Donnermeyer
 */
public class CalcExample {
    
    static final BigDecimal ZERO = new BigDecimal(0);
    static final BigDecimal ONE  = new BigDecimal(1);
    static final BigDecimal FOUR = new BigDecimal(4);
    
    static final int ROUND_ME = BigDecimal.ROUND_HALF_EVEN;
    
    /**
     * Berechnet den Wert von e nach der Summen-Formel
     * e = 1/0! + 1/1! + 1/2! + 1/3! + ...
     */
    public static BigDecimal euler(int scale) {

        BigDecimal factor  = new BigDecimal(1);
        BigDecimal factmul = new BigDecimal(1);
        BigDecimal result  = new BigDecimal(0);
        
        while (true) {
            // Berechne die Zahl 1 / akt. Faktor. Es wird
            // eine Nachkommastellen mehr gespeichert um
            // evtl. Rundungsfehler zu vermeiden.
            BigDecimal x = ONE.divide(factor, scale+ 1,
            ROUND_ME);
            
            // Wenn der Faktor Null ist, dann abbrechen
            if (x.compareTo(ZERO) == 0)
                break;
            
            // Das aktuelle Ergebniss wird zum
            // Gesamtergebnis addiert
            result = result.add(x);
            
            // Den neuen Summanden berechnen. Der Summand
            // ergibt sich aus 1/x! = 1/(x-1)! * 1/x
            factor = factor.multiply(factmul);
            factmul = factmul.add(ONE);
        }
        return result.setScale(scale, ROUND_ME);
    }
    
    /**
     * Berechnet den Wert von pi nach der Machin-Formel:
     * pi/4 = 4*arctan(1/5) - arctan(1/239)
     */
    public static BigDecimal pi(int scale) {

        BigDecimal arctan_1_5 = arctan(0.2, scale+5);
        BigDecimal arctan_1_239 = arctan(1d/239d, scale+5);
        BigDecimal pi = arctan_1_5.multiply(FOUR).subtract(
        arctan_1_239).multiply(FOUR);
        return pi.setScale(scale, ROUND_ME);
    }
    
    /**
     * Berechnung den arctan(x) nach der folgenden Formel:
     * arctan(x) = x - (x^3)/3 + (x^5)/5 - (x^7)/7 + ...
     */
    public static BigDecimal arctan(double x, int scale) {

        if (x>=1 || x<=-1)
            return null;
        
        BigDecimal result = new BigDecimal(x);
        BigDecimal numer  = new BigDecimal(x);
        BigDecimal denom  = new BigDecimal(0);
        BigDecimal help   = new BigDecimal(x*x);
        BigDecimal term   = new BigDecimal(1);
        
        int i = 1;
        while (true) {
            numer = numer.multiply(help);
            denom = new BigDecimal(2*i+1);
            
            term = numer.divide(denom, scale, ROUND_ME);
            
            if (term.compareTo(ZERO) == 0)
                break;
            
            if (i%2 != 0)
                result = result.subtract(term);
            else
                result = result.add(term);
            
            i++;
        }
        return result;
    }
    
    /**
     * Berechnung des Sinus mit der foldenen Formel:
     * sin(x) = x - (x^3)/3! + (x^5)/5! - (x^7)/7! + ...
     */
    public static BigDecimal sin(double x, int scale) {
        
        BigDecimal result = new BigDecimal(0);
        BigDecimal numer  = new BigDecimal(x);
        BigDecimal denom  = new BigDecimal(1);
        BigDecimal help   = new BigDecimal(x*x);
        BigDecimal term   = new BigDecimal(1);
        
        int i=1;
        while (true) {
            term = numer.divide(denom, scale+1, ROUND_ME);
            
            if (term.compareTo(ZERO) == 0)
                break;
            
            if (i%2 == 1)
                result = result.add(term);
            else
                result = result.subtract(term);
            
            numer = numer.multiply(help);
            denom = denom.multiply(
                        new BigDecimal(2*i*(2*i+1)));
            i++;
        }
        
        return result.setScale(scale, ROUND_ME);
    }
    
    /**
     * Berechnung des Cosinus mit der foldenen Formel:
     * cos(x) = 1 - (x^2)/2! + (x^4)/4! - (x^6)/6! + ...
     */
    public static BigDecimal cos(double x, int scale) {

        BigDecimal result = new BigDecimal(0);
        BigDecimal numer  = new BigDecimal(1);
        BigDecimal denom  = new BigDecimal(1);
        BigDecimal help   = new BigDecimal(x*x);
        BigDecimal term   = new BigDecimal(1);
        
        int i=1;
        while (true) {
            term = numer.divide(denom, scale+1, ROUND_ME);
            
            if (term.compareTo(ZERO) == 0)
                break;
            
            if (i%2 == 1)
                result = result.add(term);
            else
                result = result.subtract(term);
            
            numer = numer.multiply(help);
            denom = denom.multiply(
                        new BigDecimal((2*i)*(2*i-1)));
            i++;
        }
        
        return result.setScale(scale, ROUND_ME);
    }    
}