import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.util.HashMap;
import java.util.Map;

public class Twins {
    private static BigDecimal getRoot(BigDecimal value) {
        BigDecimal ZERO = new BigDecimal(0.0);
        BigDecimal ONE = new BigDecimal(1.0);
        BigDecimal TWO = new BigDecimal(2.0);
        if (value.compareTo(ZERO) <= 0return null;
        BigDecimal approx = getApproximation(value);
        BigDecimal last = ZERO;
        BigDecimal guess = new BigDecimal(approx.toString());
        BigDecimal error = ZERO;

        int iterations = 0;
        int maxIterations = 100;
        int scale = 40;
        boolean more = true;

        while (more) {
            last = guess;
            guess = value.divide(guess, scale, BigDecimal.ROUND_HALF_UP);
            guess = guess.add(last);
            guess = guess.divide(TWO, scale, BigDecimal.ROUND_HALF_UP);
            error = value.subtract(guess.multiply(guess));
            if (++iterations >= maxIterations) {
                more = false;
            }
            else if (last.equals(guess)) {
                more = error.abs().compareTo(ONE) >= 0;
            }
        }
        return guess;
    }

    private static BigDecimal getApproximation(BigDecimal value) {
        BigDecimal ONE = new BigDecimal(1.0);
        BigInteger bInt = value.toBigInteger();
        int length = bInt.toString().length();
        if ((length % 2) == 0) {
            length--;
        }
        length/=2;
        BigDecimal guess = ONE.movePointRight(length);
        return guess;
    }

    private static Map<BigInteger,Boolean>isPrimeCache = new HashMap<BigInteger,Boolean>();
    public static boolean isPrime(BigInteger test)
    {
        Boolean cachedResult = isPrimeCache.get(test);
        if(cachedResult!=null)
          return cachedResult.booleanValue();
        BigDecimal sqrt = new BigDecimal(test);
        sqrt = getRoot(sqrt);
        sqrt = sqrt.round(MathContext.UNLIMITED);
        BigInteger sqrtInt = sqrt.toBigInteger();
        for(BigInteger i = new BigInteger("3"); sqrtInt.compareTo(i)>-1; i=i.add(new BigInteger("2")))
        {
            if(test.remainder(i).compareTo(BigInteger.ZERO)==0)
            {
              isPrimeCache.put(test,Boolean.FALSE);
              return false;
            }
        }
        isPrimeCache.put(test,Boolean.TRUE);
        return true;
    }
    // return curr prime note I consider P(1) = 3
    public static BigInteger P( BigInteger n ) {
      BigInteger currPrime = new BigInteger("0");
      for ( BigInteger i = new BigInteger("3"); ; i = i.add(new BigInteger("2")) ) {
        if ( isPrime( i ) ) {
          currPrime = currPrime.add(BigInteger.ONE);
        }
        if ( currPrime.compareTo(n)==0 ) return i;
      }
    }
    // returns the number of 0's in Pat(f(n),n)
    public static BigInteger primeCandidates( BigInteger n ) {
      if ( n.compareTo(BigInteger.ONE) == 0 ) return new BigInteger("2");
      BigInteger nMinusOne = n.subtract(BigInteger.ONE);
      BigInteger pnMinusOne = P(n).subtract(new BigInteger("1"));
      return primeCandidates( nMinusOne ).multiply(pnMinusOne);
    }
    // returns the number of 00's in Pat(f(n),n)
    public static BigInteger twinCandidates( BigInteger n ) {
      if ( n.compareTo(BigInteger.ONE) == 0 ) return BigInteger.ONE;
      BigInteger nMinusOne = n.subtract(BigInteger.ONE);
      BigInteger pnMinusTwo = P(n).subtract(new BigInteger("2"));
      return twinCandidates( nMinusOne ).multiply(pnMinusTwo);
    }

    // returns the number of 0010's in Pat(f(n),n)
    public static BigInteger tripletCandidates( BigInteger n ) {
      if ( n.compareTo(new BigInteger("2")) == 0 ) return new BigInteger("2");
      BigInteger nMinusOne = n.subtract(BigInteger.ONE);
      BigInteger pnMinusThree = P(n).subtract(new BigInteger("3"));
      return tripletCandidates( nMinusOne ).multiply(pnMinusThree);
    }

    // returns the number of 00100's in Pat(f(n),n)
    public static BigInteger quadrupletCandidates( BigInteger n ) {
      if ( n.compareTo(new BigInteger("2")) == 0 ) return new BigInteger("1");
      BigInteger nMinusOne = n.subtract(BigInteger.ONE);
      BigInteger pnMinusFour = P(n).subtract(new BigInteger("4"));
      return quadrupletCandidates( nMinusOne ).multiply(pnMinusFour);
    }
    public static BigInteger numberPrimesBetweenPnAndPnSquared(BigInteger n, boolean addCorrection)
    {
      if(n.compareTo(new BigInteger("2"))==-1)
        return BigInteger.ZERO; //error
      BigInteger Pn = P(n);
      BigInteger PnSquared = Pn.multiply(Pn);
      BigInteger primorialIndex = n.subtract(BigInteger.ONE);
      BigInteger result = primeCandidates(n.subtract(BigInteger.ONE)).multiply((PnSquared.subtract(Pn)));
      // divide through by the primorial(n-1)
      while(primorialIndex.compareTo(BigInteger.ZERO)==1)
      {
        result = result.divide(P(primorialIndex));
        primorialIndex = primorialIndex.subtract(BigInteger.ONE);
      }
      // my P(n) function omits 2 so we'll add it back into the primorial here
      result = result.divide(new BigInteger("2"));
      if(addCorrection)
      {
        double nDouble = n.doubleValue();
        double correction = Math.log10(nDouble)*0.145348;
        nDouble *= correction;
        double nDoubleSquared = nDouble*nDouble;
        long nDoubleSquaredLong = Math.round(nDoubleSquared);
        result = result.subtract(new BigInteger(String.valueOf(nDoubleSquaredLong)));
      }
      return result;
    }

    public static BigInteger numberTwinsBetweenPnAndPnSquared(BigInteger n, boolean addCorrection)
    {
      if(n.compareTo(new BigInteger("2"))==-1)
        return BigInteger.ZERO; //error
      BigInteger Pn = P(n);
      BigInteger PnSquared = Pn.multiply(Pn);
      BigInteger primorialIndex = n.subtract(BigInteger.ONE);
      BigInteger result = twinCandidates(n.subtract(BigInteger.ONE)).multiply((PnSquared.subtract(Pn)));
      // divide through by the primorial(n-1)
      while(primorialIndex.compareTo(BigInteger.ZERO)==1)
      {
        result = result.divide(P(primorialIndex));
        primorialIndex = primorialIndex.subtract(BigInteger.ONE);
      }
      // my P(n) function omits 2 so we'll add it back into the primorial here
      result = result.divide(new BigInteger("2"));
      if(addCorrection)
      {
        double nDouble = n.doubleValue();
        double correction = Math.log10(nDouble)*0.058652;
        nDouble *= correction;
        double nDoubleSquared = nDouble*nDouble;
        long nDoubleSquaredLong = Math.round(nDoubleSquared);
        result = result.subtract(new BigInteger(String.valueOf(nDoubleSquaredLong)));
      }
      return result;
    }

    public static BigInteger numberTripletsBetweenPnAndPnSquared(BigInteger n, boolean addCorrection)
    {
      if(n.compareTo(new BigInteger("3"))==-1)
        return BigInteger.ZERO; //error
      BigInteger Pn = P(n);
      BigInteger PnSquared = Pn.multiply(Pn);
      BigInteger primorialIndex = n.subtract(BigInteger.ONE);
      BigInteger result = tripletCandidates(n.subtract(BigInteger.ONE)).multiply((PnSquared.subtract(Pn)));
      // divide through by the primorial(n-1)
      while(primorialIndex.compareTo(BigInteger.ZERO)==1)
      {
        result = result.divide(P(primorialIndex));
        primorialIndex = primorialIndex.subtract(BigInteger.ONE);
      }
      // my P(n) function omits 2 so we'll add it back into the primorial here
      result = result.divide(new BigInteger("2"));
      if(addCorrection)
      {
        double nDouble = n.doubleValue();
        double correction = Math.log10(nDouble)*0.02881;
        nDouble *= correction;
        double nDoubleSquared = nDouble*nDouble;
        long nDoubleSquaredLong = Math.round(nDoubleSquared);
        result = result.subtract(new BigInteger(String.valueOf(nDoubleSquaredLong)));
      }
      return result;
    }

    public static BigInteger numberQuadrupletsBetweenPnAndPnSquared(BigInteger n, boolean addCorrection)
    {
      if(n.compareTo(new BigInteger("3"))==-1)
        return BigInteger.ZERO; //error
      BigInteger Pn = P(n);
      BigInteger PnSquared = Pn.multiply(Pn);
      BigInteger primorialIndex = n.subtract(BigInteger.ONE);
      BigInteger result = quadrupletCandidates(n.subtract(BigInteger.ONE)).multiply((PnSquared.subtract(Pn)));
      // divide through by the primorial(n-1)
      while(primorialIndex.compareTo(BigInteger.ZERO)==1)
      {
        result = result.divide(P(primorialIndex));
        primorialIndex = primorialIndex.subtract(BigInteger.ONE);
      }
      // my P(n) function omits 2 so we'll add it back into the primorial here
      result = result.divide(new BigInteger("2"));
      if(addCorrection)
      {
        double nDouble = n.doubleValue();
        double correction = Math.log10(nDouble)*0.00718;
        nDouble *= correction;
        double nDoubleSquared = nDouble*nDouble;
        long nDoubleSquaredLong = Math.round(nDoubleSquared);
        result = result.subtract(new BigInteger(String.valueOf(nDoubleSquaredLong)));
      }
      return result;
    }
    public static BigInteger numPrimePnToPnSquared(BigInteger n)
    {
      if(n.compareTo(new BigInteger("2"))==-1)
        return BigInteger.ZERO; //error
      BigInteger Pn = P(n);
      BigInteger PnSquared = Pn.multiply(Pn);
      BigInteger numPrime = BigInteger.ZERO;
      for (BigInteger i=Pn.add(new BigInteger("2")); i.compareTo(PnSquared)==-1; i = i.add(new BigInteger("2")))
        if ( isPrime( i ) ) numPrime = numPrime.add(BigInteger.ONE);
      return numPrime;
    }
    public static BigInteger numTwinPnToPnSquared(BigInteger n)
    {
      if(n.compareTo(new BigInteger("2"))==-1)
        return BigInteger.ZERO; //error
      BigInteger Pn = P(n);
      BigInteger PnSquared = Pn.multiply(Pn);
      BigInteger numTwin = BigInteger.ZERO;
      for (BigInteger i=Pn.add(new BigInteger("2")); i.compareTo(PnSquared)==-1; i = i.add(new BigInteger("2")))
        if ( isPrime( i ) && isPrime(i.add(new BigInteger("2"))) ) numTwin = numTwin.add(BigInteger.ONE);
      return numTwin;
    }

    public static BigInteger numTripletPnToPnSquared(BigInteger n)
    {
      if(n.compareTo(new BigInteger("2"))==-1)
        return BigInteger.ZERO; //error
      BigInteger Pn = P(n);
      BigInteger PnSquared = Pn.multiply(Pn);
      BigInteger numTriplet = BigInteger.ZERO;
      for (BigInteger i=Pn.add(new BigInteger("2")); i.compareTo(PnSquared)==-1; i = i.add(new BigInteger("2")))
        if ( isPrime( i ) && isPrime(i.add(new BigInteger("2"))) && isPrime(i.add( new BigInteger("6"))) ) numTriplet = numTriplet.add(BigInteger.ONE);
      return numTriplet;
    }

    public static BigInteger numQuadrupletPnToPnSquared(BigInteger n)
    {
      if(n.compareTo(new BigInteger("2"))==-1)
        return BigInteger.ZERO; //error
      BigInteger Pn = P(n);
      BigInteger PnSquared = Pn.multiply(Pn);
      BigInteger numQuadruplet = BigInteger.ZERO;
      for (BigInteger i=Pn.add(new BigInteger("2")); i.compareTo(PnSquared)==-1; i = i.add(new BigInteger("2")))
        if ( isPrime( i ) && isPrime(i.add(new BigInteger("2"))) && isPrime(i.add( new BigInteger("6"))) && isPrime(i.add( new BigInteger("8"))) ) numQuadruplet = numQuadruplet.add(BigInteger.ONE);
      return numQuadruplet;
    }

    public static void main(String[] args) {
      // examining the #Primes[P(n)->P(n)^2] formula
      System.out.println( "---------------------------------------------------------" );
      System.out.println( "EXAMINING THE #Primes[P(n)->P(n)^2] AND #Primes_Corrected[P(n)->P(n)^2] FORMULAE" );
      for ( int x = 2; x < 400; x++ ) {
        BigInteger numPrimes = numberPrimesBetweenPnAndPnSquared( new BigInteger(String.valueOf(x)), false );
        BigInteger numPrimesCorrected = numberPrimesBetweenPnAndPnSquared( new BigInteger(String.valueOf(x)), true );
        BigInteger actualPrimes = numPrimePnToPnSquared(new BigInteger(String.valueOf(x)));
        System.out.println( "#Primes[P(" + x + ")->P(" + x + ")^2] = " + numPrimes
            + "; #Primes_Corrected[P(" + x + ")->P(" + x + ")^2] = " + numPrimesCorrected
            + "; actual number primes in this interval = " + actualPrimes
            + "; (numPrimesCorrected-actualPrimes) = " + (numPrimesCorrected.longValue()-actualPrimes.longValue()));
      }
      // examining the #Twins[P(n)->P(n)^2] formula
      System.out.println( "---------------------------------------------------------" );
      System.out.println( "EXAMINING THE #Twins[P(n)->P(n)^2] AND #Twins_Corrected[P(n)->P(n)^2] FORMULAE" );
      for ( int x = 2; x < 400; x++ ) {
        BigInteger numTwins = numberTwinsBetweenPnAndPnSquared( new BigInteger(String.valueOf(x)), false );
        BigInteger numTwinsCorrected = numberTwinsBetweenPnAndPnSquared( new BigInteger(String.valueOf(x)), true );
        BigInteger actualTwins = numTwinPnToPnSquared(new BigInteger(String.valueOf(x)));
        System.out.println( "#Twins[P(" + x + ")->P(" + x + ")^2] = " + numTwins
            + "; #Twins_Corrected[P(" + x + ")->P(" + x + ")^2] = " + numTwinsCorrected
            + "; actual number twins in this interval = " + actualTwins
            + "; (numTwinsCorrected-actualTwins) = " + (numTwinsCorrected.longValue()-actualTwins.longValue()));
      }
      // examining the #Triplets[P(n)->P(n)^2] formula
      System.out.println( "---------------------------------------------------------" );
      System.out.println( "EXAMINING THE #Triplets[P(n)->P(n)^2] AND #Triplets_Corrected[P(n)->P(n)^2] FORMULAE" );
      for ( int x = 3; x < 400; x++ ) {
        BigInteger numTriplets = numberTripletsBetweenPnAndPnSquared( new BigInteger(String.valueOf(x)), false );
        BigInteger numTripletsCorrected = numberTripletsBetweenPnAndPnSquared( new BigInteger(String.valueOf(x)), true );
        BigInteger actualTriplets = numTripletPnToPnSquared(new BigInteger(String.valueOf(x)));
        System.out.println( "#Triplets[P(" + x + ")->P(" + x + ")^2] = " + numTriplets
            + "; #Triplets_Corrected[P(" + x + ")->P(" + x + ")^2] = " + numTripletsCorrected
            + "; actual number triplets in this interval = " + actualTriplets
            + "; (numTripletsCorrected-actualTriplets) = " + (numTripletsCorrected.longValue()-actualTriplets.longValue()));
      }

      // examining the #Quadruplets[P(n)->P(n)^2] formula
      System.out.println( "---------------------------------------------------------" );
      System.out.println( "EXAMINING THE #Quadruplets[P(n)->P(n)^2] AND #Quadruplets_Corrected[P(n)->P(n)^2] FORMULAE" );
      for ( int x = 3; x < 400; x++ ) {
        BigInteger numQuadruplets = numberQuadrupletsBetweenPnAndPnSquared( new BigInteger(String.valueOf(x)), false );
        BigInteger numQuadrupletsCorrected = numberQuadrupletsBetweenPnAndPnSquared( new BigInteger(String.valueOf(x)), true );
        BigInteger actualQuadruplets = numQuadrupletPnToPnSquared(new BigInteger(String.valueOf(x)));
        System.out.println( "#Quadruplets[P(" + x + ")->P(" + x + ")^2] = " + numQuadruplets
            + "; #Quadruplets_Corrected[P(" + x + ")->P(" + x + ")^2] = " + numQuadrupletsCorrected
            + "; actual number quadruplets in this interval = " + actualQuadruplets
            + "; (numQuadrupletsCorrected-actualQuadruplets) = " + (numQuadrupletsCorrected.longValue()-actualQuadruplets.longValue()));
      }
    }
}