
// FunNumbers.java
// Andrew Davison, ad@coe.psu.ac.th, Nov. 2022

/*
   Usage (use bat files):
     > compile FunNumbers.java
     > run FunNumbers
*/

import java.util.*;

import org.apfloat.*;


public class FunNumbers
{
  public static void main(String[] args)
  {
    // =======================================
    // Apfloat and ApfloatMath, using 50 digit precision

    Apfloat f13 = new Apfloat(1, 50).divide(new Apfloat(13, 50));
    System.out.printf("Periodic 1/13 = %#s\n", f13);
    System.out.println("Periodic 1/13 = " + spaces3(f13) + "\n");

    // sqrt 163, precision 50 digits
    Apfloat root163 = ApfloatMath.sqrt(new Apfloat(163, 50));
    System.out.printf("sqrt of 163 = %#s\n", root163);
    System.out.println("sqrt of 163 = " + spaces3(root163) + "\n");

    Apfloat pi = new Apfloat(4, 50).multiply(
                     ApfloatMath.atan(new Apfloat(1, 50)));  // 4*atan(1)
    System.out.println("Pi = " + pi + "\n");
           // could use ApfloatMath.pi(50);

    System.out.printf("Euler-Mascheroni constant = %#s\n\n", 
                             ApfloatMath.euler(50));

    Apfloat phi = ApfloatMath.sqrt(new Apfloat(5, 50)).add(
                     new Apfloat(1, 50)).divide(new Apfloat(2, 50));
    System.out.println("Phi = " + phi + "\n");   // (sqrt(5)+1)/2

    /*  Ramanujan's constant
      https://math.stackexchange.com/questions/3131986/examples-of-transcendental-functions-giving-almost-integers
      https://math.stackexchange.com/questions/4544/why-is-e-pi-sqrt163-almost-an-integer
    */ 
    Apfloat res = ApfloatMath.exp(pi.multiply(root163));  // e^(pi*sqrt(163))
    System.out.printf("e^(pi*sqrt(163)): %#s\n", res);
    System.out.println("e^(pi*sqrt(163)): " + spaces3(res) + "\n");

    // https://mathworld.wolfram.com/AlmostInteger.html
    // https://chamberland.math.grinnell.edu/courses/444/worksheets/high-precision-fraud.pdf
    res = ApfloatMath.exp(pi).subtract(pi);  // e^(pi)-pi = 20
    System.out.printf("e^(pi)-pi = %#s\n\n", res); 

    res = ApfloatMath.sin(new Apfloat(11, 50));  // sin(11)
    System.out.printf("sin(11) = %#s\n\n", res); 

    res = ApfloatMath.log(new Apfloat(7, 50), new Apfloat(10, 50)).
                             multiply(new Apfloat(510, 50));
    System.out.printf("510*log10(7) = %#s\n\n", res); 

    // The Simpsons, "Treehouse of Horror VI". 
    // 1782^12 + 1841^12 = 1922^12
    Apfloat p12 = new Apfloat(12, 50);
    Apfloat f12 = new Apfloat(1, 50).divide(p12);

    Apfloat v1 = ApfloatMath.pow( new Apfloat(1782, 50), p12);
    Apfloat v2 = ApfloatMath.pow( new Apfloat(1841, 50), p12);
    Apfloat v3 = new Apfloat(1922, 50);
    Apfloat rootLeft = ApfloatMath.pow( v1.add(v2), f12);
    System.out.println("pow(1782^12 + 1841^12, 1/12) - 1922 = " + 
                               rootLeft.subtract(v3));
    System.out.println("pow(1782^12 + 1841^12, 1/12) - 1922 = " + 
                          spaces3(rootLeft.subtract(v3)) + "\n");

    // The Simpsons, "The Wizard of Evergreen Terrace"
    // 3987^12 + 4365^12 = 4472^12
    v1 = ApfloatMath.pow( new Apfloat(3987, 50), p12);
    v2 = ApfloatMath.pow( new Apfloat(4365, 50), p12);
    v3 = new Apfloat(4472, 50);
    rootLeft = ApfloatMath.pow( v1.add(v2), f12);
    System.out.println("pos(3987^12 + 4365^12, 1/12) - 4472 = " + 
                             rootLeft.subtract(v3) + "\n");
  
    // The first 144 decimal digits of pi add up to 666
    String pifStr = ApfloatMath.pi(145).frac().toString(true);
    int sum = 0;
    for (char ch : pifStr.toCharArray())
      if (Character.isDigit(ch)) 
        sum += (ch - '0');
    System.out.println("Frac pi = " + pifStr + " (sum = " + sum + ")\n");

    // ======================================================
    // Apint and ApiintMath

    System.out.println("Factorial 100 = " + 
              spaces3(ApintMath.factorial(100)) + "\n");

    List<Apint> pFactors = primeFactors(new Apint(1000));
    System.out.println("Prime factors of 1000: " + pFactors + "\n");

    // https://en.wikipedia.org/wiki/List_of_Mersenne_primes_and_perfect_numbers
    pFactors = primeFactors( ApintMath.pow(new Apint(2),31).
                                         subtract(new Apint(1)));   
          // I gave up waiting for 2^61-1, the 9th Mersenne prime
    System.out.println("Prime factors of 2^31-1: " + pFactors + "\n");

    List<Apint> primes = getPrimes(new Apint(1000));
    System.out.println("Primes to 1000: " + primes + "\n");

  }  // end of main


  private static String spaces3(Apfloat f)
  /* convert the float to a string and add a space after 
     each group of three digits
  */
  {
    String sign = "";
    if (f.signum() == -1) {
      sign = "-";
      f = f.negate();
    }
    
    char[] chsTrunc = f.truncate().toString(true).toCharArray();

    String sFrac = f.frac().toString(true);
    int dotPos = sFrac.indexOf(".");
    sFrac = sFrac.substring(dotPos+1);   // cut off "0."
    char[] chsFrac = sFrac.toCharArray();

    StringBuilder sbTrunc = new StringBuilder();
    int count = 0;
    for (int i = chsTrunc.length-1; i >= 0; i--) {
      sbTrunc.append(chsTrunc[i]);   // reverse order
      count++;
      if (count%3 == 0)
        sbTrunc.append(' ');
    }
    sbTrunc = sbTrunc.reverse();

    StringBuilder sbFrac = new StringBuilder();
    count = 0;
    for (int i=0; i < chsFrac.length; i++) {
      sbFrac.append(chsFrac[i]);
      count++;
      if (count%3 == 0)
        sbFrac.append(' ');
    }

    String spacedVal = (sign + sbTrunc.toString() + "." + 
                               sbFrac.toString()).trim();
    // System.out.println("*" + spacedVal + "*");

    return spacedVal;
  } // end of spaces3()


  private static String spaces3(Apint v)
  /* convert the integer to a string and add a space after 
     each group of three digits
  */
  {
    char[] chsTrunc = v.toString(true).toCharArray();

    StringBuilder sbTrunc = new StringBuilder();
    int count = 0;
    for (int i = chsTrunc.length-1; i >= 0; i--) {
      sbTrunc.append(chsTrunc[i]);   // reverse order
      count++;
      if (count%3 == 0)
        sbTrunc.append(' ');
    }
    sbTrunc = sbTrunc.reverse();

    String spacedVal = sbTrunc.toString().trim();
    // System.out.println("*" + spacedVal + "*");

    return spacedVal;
  } // end of spaces3()


  // slow but simple primes calculations...
  // https://en.wikipedia.org/wiki/Integer_factorization?wprov=sfti1
  
  private static List<Apint> getPrimes(Apint n) 
  {
    Apint twoAp = new Apint(2);

    List<Apint> primes = new ArrayList<>();
    if (n.compareTo(twoAp) != -1)   // >= 2
      primes.add(twoAp);

    Apint i = new Apint(3);    
    while (i.compareTo(n) != 1) {   // i <= n
      if (isPrime(i))
        primes.add(i);
      i = i.add(twoAp);    // i += 2
    }
    return primes;
  }  // end of getPrimes()


  private static boolean isPrime(Apint n) 
  {
    Apint oneAp = new Apint(1);
    Apint i = new Apint(2);    
    while (i.multiply(i).compareTo(n) == -1) {  // i*i < n
      if (n.mod(i).equals(new Apint(0)))    // n%i == 0
        return false;
      i = i.add(oneAp);  // i++
    }
    return true;
  }  // end of isPrime()


  private static List<Apint> primeFactors(Apint n)
  {
    Apint twoAp = new Apint(2);
    List<Apint> pFactors = new ArrayList<>();

    // collect the 2s that divide n
    while (n.mod(twoAp).equals(new Apint(0))) {   // n%2 == 0
      pFactors.add(twoAp);
      n = n.divide(twoAp);
    }
 
    // n must be odd at this point.  So we can
    // take steps of 2 (i = i+2)
    Apint i = new Apint(3);
    while (i.multiply(i).compareTo(n) == -1) {  // i*i < n
      // While i divides n, collect i and divide n
      while (n.mod(i).equals(new Apint(0))) {   // n%i == 0
        pFactors.add(i);
        n = n.divide(i);
      }
      i = i.add(twoAp);    // i += 2
    }
 
    // handle the case when
    // n is a prime number greater than 2
    if (n.compareTo(twoAp) == 1)  // n > 2
      pFactors.add(n);

    return pFactors;
  } // end of primeFactors()

}  // end of FunNumbers class