<commit_before>package org.nwapw.abacus.plugin;

import org.nwapw.abacus.function.*;
import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PreciseNumber;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * The plugin providing standard functions such as addition and subtraction to
 * the calculator.
 */
public class StandardPlugin extends Plugin {

    /**
     * The addition operator, +
     */
    public static final NumberOperator OP_ADD = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 2;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return params[0].add(params[1]);
        }
    };
    /**
     * The subtraction operator, -
     */
    public static final NumberOperator OP_SUBTRACT = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 2;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return params[0].subtract(params[1]);

        }
    };
    /**
     * The negation operator, -
     */
    public static final NumberOperator OP_NEGATE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.UNARY_PREFIX, 0) {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return params[0].negate();
        }
    };
    /**
     * The multiplication operator, *
     */
    public static final NumberOperator OP_MULTIPLY = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 2;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return params[0].multiply(params[1]);
        }
    };
    /**
     * The implementation for double-based naive numbers.
     */
    public static final NumberImplementation IMPLEMENTATION_NAIVE = new NumberImplementation(NaiveNumber.class, 0) {
        @Override
        public NumberInterface instanceForString(String string) {
            return new NaiveNumber(string);
        }

        @Override
        public NumberInterface instanceForPi() {
            return new NaiveNumber(Math.PI);
        }
    };
    /**
     * The implementation for the infinite-precision BigDecimal.
     */
    public static final NumberImplementation IMPLEMENTATION_PRECISE = new NumberImplementation(PreciseNumber.class, 0) {
        @Override
        public NumberInterface instanceForString(String string) {
            return new PreciseNumber(string);
        }

        @Override
        public NumberInterface instanceForPi() {
            NumberInterface C = FUNCTION_SQRT.apply(this, new PreciseNumber("10005")).multiply(new PreciseNumber("426880"));
            NumberInterface M = PreciseNumber.ONE;
            NumberInterface L = new PreciseNumber("13591409");
            NumberInterface X = M;
            NumberInterface sum = L;
            int termsNeeded = C.getMaxPrecision() / 13 + 1;

            NumberInterface lSummand = new PreciseNumber("545140134");
            NumberInterface xMultiplier = new PreciseNumber("262537412")
                    .multiply(new PreciseNumber("1000000000"))
                    .add(new PreciseNumber("640768000"))
                    .negate();
            for (int i = 0; i < termsNeeded; i++) {
                M = M
                        .multiply(new PreciseNumber((12 * i + 2) + ""))
                        .multiply(new PreciseNumber((12 * i + 6) + ""))
                        .multiply(new PreciseNumber((12 * i + 10) + ""))
                        .divide(new PreciseNumber(Math.pow(i + 1, 3) + ""));
                L = L.add(lSummand);
                X = X.multiply(xMultiplier);
                sum = sum.add(M.multiply(L).divide(X));
            }
            return C.divide(sum);
        }
    };
    /**
     * The division operator, /
     */
    public static final NumberOperator OP_DIVIDE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 2 && params[1].compareTo(implementation.instanceForString(Integer.toString(0))) != 0;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return params[0].divide(params[1]);
        }
    };
    /**
     * The factorial operator, !
     */
    public static final NumberOperator OP_FACTORIAL = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.UNARY_POSTFIX, 0) {
        //private HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> storedList = new HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>>();
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1
                    && params[0].fractionalPart().compareTo(implementation.instanceForString("0")) == 0
                    && params[0].signum() >= 0;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            if (params[0].signum() == 0) {
                return implementation.instanceForString("1");
            }
            NumberInterface one = implementation.instanceForString("1");
            NumberInterface factorial = params[0];
            NumberInterface multiplier = params[0];
            //It is necessary to later prevent calls of factorial on anything but non-negative integers.
            while ((multiplier = multiplier.subtract(one)).signum() == 1) {
                factorial = factorial.multiply(multiplier);
            }
            return factorial;
                /*if(!storedList.containsKey(params[0].getClass())){
                    storedList.put(params[0].getClass(), new ArrayList<NumberInterface>());
                    storedList.get(params[0].getClass()).add(NaiveNumber.ONE.promoteTo(params[0].getClass()));
                    storedList.get(params[0].getClass()).add(NaiveNumber.ONE.promoteTo(params[0].getClass()));
                }*/
        }
    };
    /**
     * The permutation operator.
     */
    public static final NumberOperator OP_NPR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 2 && params[0].fractionalPart().signum() == 0
                    && params[1].fractionalPart().signum() == 0;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            if (params[0].compareTo(params[1]) < 0 ||
                    params[0].signum() < 0 ||
                    (params[0].signum() == 0 && params[1].signum() != 0)) return implementation.instanceForString("0");
            NumberInterface total = implementation.instanceForString("1");
            NumberInterface multiplyBy = params[0];
            NumberInterface remainingMultiplications = params[1];
            NumberInterface halfway = params[0].divide(implementation.instanceForString("2"));
            if (remainingMultiplications.compareTo(halfway) > 0) {
                remainingMultiplications = params[0].subtract(remainingMultiplications);
            }
            while (remainingMultiplications.signum() > 0) {
                total = total.multiply(multiplyBy);
                remainingMultiplications = remainingMultiplications.subtract(implementation.instanceForString("1"));
                multiplyBy = multiplyBy.subtract(implementation.instanceForString("1"));
            }
            return total;
        }
    };
    /**
     * The combination operator.
     */
    public static final NumberOperator OP_NCR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 2 && params[0].fractionalPart().signum() == 0
                    && params[1].fractionalPart().signum() == 0;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return OP_NPR.apply(implementation, params).divide(OP_FACTORIAL.apply(implementation, params[1]));
        }
    };
    /**
     * The absolute value function, abs(-3) = 3
     */
    public static final NumberFunction FUNCTION_ABS = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return params[0].multiply(implementation.instanceForString(Integer.toString(params[0].signum())));
        }
    };
    /**
     * The natural log function.
     */
    public static final NumberFunction FUNCTION_LN = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1 && params[0].compareTo(implementation.instanceForString("0")) > 0;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            NumberInterface param = params[0];
            NumberInterface one = implementation.instanceForString("1");
            int powersOf2 = 0;
            while (FUNCTION_ABS.apply(implementation, param.subtract(one)).compareTo(implementation.instanceForString(".1")) >= 0) {
                if (param.subtract(one).signum() == 1) {
                    param = param.divide(implementation.instanceForString("2"));
                    powersOf2++;
                    if (param.subtract(one).signum() != 1) {
                        break;
                        //No infinite loop for you.
                    }
                } else {
                    param = param.multiply(implementation.instanceForString("2"));
                    powersOf2--;
                    if (param.subtract(one).signum() != -1) {
                        break;
                        //No infinite loop for you.
                    }
                }
            }
            return getLog2(implementation, param).multiply(implementation.instanceForString(Integer.toString(powersOf2))).add(getLogPartialSum(implementation, param));
        }

        /**
         * Returns the partial sum of the Taylor series for logx (around x=1).
         * Automatically determines the number of terms needed based on the precision of x.
         * @param x value at which the series is evaluated. 0 < x < 2. (x=2 is convergent but impractical.)
         * @return the partial sum.
         */
        private NumberInterface getLogPartialSum(NumberImplementation implementation, NumberInterface x) {

            NumberInterface maxError = x.getMaxError();
            x = x.subtract(implementation.instanceForString("1")); //Terms used are for log(x+1).
            NumberInterface currentNumerator = x, currentTerm = x, sum = x;
            int n = 1;
            while (FUNCTION_ABS.apply(implementation, currentTerm).compareTo(maxError) > 0) {
                n++;
                currentNumerator = currentNumerator.multiply(x).negate();
                currentTerm = currentNumerator.divide(implementation.instanceForString(Integer.toString(n)));
                sum = sum.add(currentTerm);
            }
            return sum;
        }

        /**
         * Returns natural log of 2 to the required precision of the class of number.
         * @param number a number of the same type as the return type. (Used for precision.)
         * @return the value of log(2) with the appropriate precision.
         */
        private NumberInterface getLog2(NumberImplementation implementation, NumberInterface number) {
            NumberInterface maxError = number.getMaxError();
            //NumberInterface errorBound = implementation.instanceForString("1");
            //We'll use the series \sigma_{n >= 1) ((1/3^n + 1/4^n) * 1/n)
            //In the following, a=1/3^n, b=1/4^n, c = 1/n.
            //a is also an error bound.
            NumberInterface a = implementation.instanceForString("1"), b = a, c = a;
            NumberInterface sum = implementation.instanceForString("0");
            NumberInterface one = implementation.instanceForString("1");
            int n = 0;
            while (a.compareTo(maxError) >= 1) {
                n++;
                a = a.divide(implementation.instanceForString("3"));
                b = b.divide(implementation.instanceForString("4"));
                c = one.divide(implementation.instanceForString(Integer.toString(n)));
                sum = sum.add(a.add(b).multiply(c));
            }
            return sum;
        }
    };
    /**
     * Gets a random number smaller or equal to the given number's integer value.
     */
    public static final NumberFunction FUNCTION_RAND_INT = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return implementation.instanceForString(Long.toString(Math.round(Math.random() * params[0].floor().intValue())));
        }
    };
    /**
     * The caret / pow operator, ^
     */
    public static final NumberOperator OP_CARET = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 2) {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            NumberInterface zero = implementation.instanceForString("0");
            return params.length == 2
                    && !(params[0].compareTo(zero) == 0
                    && params[1].compareTo(zero) == 0)
                    && !(params[0].signum() == -1 && params[1].fractionalPart().compareTo(zero) != 0);
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            NumberInterface zero = implementation.instanceForString("0");
            if (params[0].compareTo(zero) == 0)
                return zero;
            else if (params[1].compareTo(zero) == 0)
                return implementation.instanceForString("1");
            //Detect integer bases:
            if (params[0].fractionalPart().compareTo(implementation.instanceForString("0")) == 0
                    && FUNCTION_ABS.apply(implementation, params[1]).compareTo(implementation.instanceForString(Integer.toString(Integer.MAX_VALUE))) < 0
                    && FUNCTION_ABS.apply(implementation, params[1]).compareTo(implementation.instanceForString("1")) >= 0) {
                NumberInterface[] newParams = {params[0], params[1].fractionalPart()};
                return params[0].intPow(params[1].floor().intValue()).multiply(applyInternal(implementation, newParams));
            }
            return FUNCTION_EXP.apply(implementation, FUNCTION_LN.apply(implementation, FUNCTION_ABS.apply(implementation, params[0])).multiply(params[1]));
        }
    };
    /**
     * The square root function.
     */
    public static final NumberFunction FUNCTION_SQRT = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return OP_CARET.apply(implementation, params[0], implementation.instanceForString(".5"));
        }
    };
    private static final HashMap<NumberImplementation, ArrayList<NumberInterface>> FACTORIAL_LISTS = new HashMap<>();
    /**
     * The exponential function, exp(1) = e^1 = 2.71...
     */
    public static final NumberFunction FUNCTION_EXP = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            NumberInterface maxError = params[0].getMaxError();
            int n = 0;
            if (params[0].signum() < 0) {
                NumberInterface[] negatedParams = {params[0].negate()};
                return implementation.instanceForString("1").divide(applyInternal(implementation, negatedParams));
            } else {
                //We need n such that x^(n+1) * 3^ceil(x) <= maxError * (n+1)!.
                //right and left refer to lhs and rhs in the above inequality.
                NumberInterface sum = implementation.instanceForString("1");
                NumberInterface nextNumerator = params[0];
                NumberInterface left = params[0].multiply(implementation.instanceForString("3").intPow(params[0].ceiling().intValue())), right = maxError;
                do {
                    sum = sum.add(nextNumerator.divide(factorial(implementation, n + 1)));
                    n++;
                    nextNumerator = nextNumerator.multiply(params[0]);
                    left = left.multiply(params[0]);
                    NumberInterface nextN = implementation.instanceForString(Integer.toString(n + 1));
                    right = right.multiply(nextN);
                    //System.out.println(left + ", " + right);
                }
                while (left.compareTo(right) > 0);
                //System.out.println(n+1);
                return sum;
            }
        }
    };
    /**
     * The sine function (the argument is interpreted in radians).
     */
    public final NumberFunction functionSin = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            NumberInterface pi = piFor(params[0].getClass());
            NumberInterface twoPi = pi.multiply(implementation.instanceForString("2"));
            NumberInterface theta = getSmallAngle(implementation, params[0], pi);
            //System.out.println(theta);
            if (theta.compareTo(pi.multiply(implementation.instanceForString("1.5"))) >= 0) {
                theta = theta.subtract(twoPi);
            } else if (theta.compareTo(pi.divide(implementation.instanceForString("2"))) > 0) {
                theta = pi.subtract(theta);
            }
            //System.out.println(theta);
            return sinTaylor(implementation, theta);
        }
    };
    /**
     * The cosine function (the argument is in radians).
     */
    public final NumberFunction functionCos = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return functionSin.apply(implementation, piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
                    .subtract(params[0]));
        }
    };
    /**
     * The tangent function (the argument is in radians).
     */
    public final NumberFunction functionTan = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return functionSin.apply(implementation, params[0]).divide(functionCos.apply(implementation, params[0]));
        }
    };
    /**
     * The secant function (the argument is in radians).
     */
    public final NumberFunction functionSec = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return implementation.instanceForString("1").divide(functionCos.apply(implementation, params[0]));
        }
    };
    /**
     * The cosecant function (the argument is in radians).
     */
    public final NumberFunction functionCsc = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return implementation.instanceForString("1").divide(functionSin.apply(implementation, params[0]));
        }
    };
    /**
     * The cotangent function (the argument is in radians).
     */
    public final NumberFunction functionCot = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return functionCos.apply(implementation, params[0]).divide(functionSin.apply(implementation, params[0]));
        }
    };

    /**
     * The arcsine function (return type in radians).
     */
    public final NumberFunction functionArcsin = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1
                    && FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString("1")) <= 0;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            if (FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString(".8")) >= 0) {
                NumberInterface[] newParams = {FUNCTION_SQRT.apply(implementation, implementation.instanceForString("1").subtract(params[0].multiply(params[0])))};
                return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
                        .subtract(applyInternal(implementation, newParams)).multiply(implementation.instanceForString(Integer.toString(params[0].signum())));
            }
            NumberInterface currentTerm = params[0], sum = currentTerm,
                    multiplier = currentTerm.multiply(currentTerm), summandBound = sum.getMaxError().multiply(implementation.instanceForString("1").subtract(multiplier)),
                    power = currentTerm, coefficient = implementation.instanceForString("1");
            int exponent = 1;
            while (FUNCTION_ABS.apply(implementation, currentTerm).compareTo(summandBound) > 0) {
                exponent += 2;
                power = power.multiply(multiplier);
                coefficient = coefficient.multiply(implementation.instanceForString(Integer.toString(exponent - 2)))
                        .divide(implementation.instanceForString(Integer.toString(exponent - 1)));
                currentTerm = power.multiply(coefficient).divide(implementation.instanceForString(Integer.toString(exponent)));
                sum = sum.add(currentTerm);
            }
            return sum;
        }
    };

    /**
     * The arccosine function.
     */
    public final NumberFunction functionArccos = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1 && FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString("1")) <= 0;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
                    .subtract(functionArcsin.apply(implementation, params));
        }
    };

    /**
     * The arccosecant function.
     */
    public final NumberFunction functionArccsc = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1 && FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString("1")) >= 0;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            NumberInterface[] reciprocalParamArr = {implementation.instanceForString("1").divide(params[0])};
            return functionArcsin.apply(implementation, reciprocalParamArr);
        }
    };

    /**
     * The arcsecant function.
     */
    public final NumberFunction functionArcsec = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1 && FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString("1")) >= 0;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            NumberInterface[] reciprocalParamArr = {implementation.instanceForString("1").divide(params[0])};
            return functionArccos.apply(implementation, reciprocalParamArr);
        }
    };

    /**
     * The arctangent function.
     */
    public final NumberFunction functionArctan = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }


        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            if (params[0].signum() == -1) {
                NumberInterface[] negatedParams = {params[0].negate()};
                return applyInternal(implementation, negatedParams).negate();
            }
            if (params[0].compareTo(implementation.instanceForString("1")) > 0) {
                NumberInterface[] reciprocalParams = {implementation.instanceForString("1").divide(params[0])};
                return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
                        .subtract(applyInternal(implementation, reciprocalParams));
            }
            if (params[0].compareTo(implementation.instanceForString("1")) == 0) {
                return piFor(params[0].getClass()).divide(implementation.instanceForString("4"));
            }
            if (params[0].compareTo(implementation.instanceForString(".9")) >= 0) {
                NumberInterface[] newParams = {params[0].multiply(implementation.instanceForString("2"))
                        .divide(implementation.instanceForString("1").subtract(params[0].multiply(params[0])))};
                return applyInternal(implementation, newParams).divide(implementation.instanceForString("2"));
            }
            NumberInterface currentPower = params[0], currentTerm = currentPower, sum = currentTerm,
                    maxError = params[0].getMaxError(), multiplier = currentPower.multiply(currentPower).negate();
            int n = 1;
            while (FUNCTION_ABS.apply(implementation, currentTerm).compareTo(maxError) > 0) {
                n += 2;
                currentPower = currentPower.multiply(multiplier);
                currentTerm = currentPower.divide(implementation.instanceForString(Integer.toString(n)));<commit_msg>Remove newlines generated by IntelliJ's addition of @Nullable.<commit_after>package org.nwapw.abacus.plugin;

import org.nwapw.abacus.function.*;
import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PreciseNumber;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * The plugin providing standard functions such as addition and subtraction to
 * the calculator.
 */
public class StandardPlugin extends Plugin {

    /**
     * The addition operator, +
     */
    public static final NumberOperator OP_ADD = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 2;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return params[0].add(params[1]);
        }
    };
    /**
     * The subtraction operator, -
     */
    public static final NumberOperator OP_SUBTRACT = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 0) {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 2;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return params[0].subtract(params[1]);

        }
    };
    /**
     * The negation operator, -
     */
    public static final NumberOperator OP_NEGATE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.UNARY_PREFIX, 0) {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return params[0].negate();
        }
    };
    /**
     * The multiplication operator, *
     */
    public static final NumberOperator OP_MULTIPLY = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 2;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return params[0].multiply(params[1]);
        }
    };
    /**
     * The implementation for double-based naive numbers.
     */
    public static final NumberImplementation IMPLEMENTATION_NAIVE = new NumberImplementation(NaiveNumber.class, 0) {
        @Override
        public NumberInterface instanceForString(String string) {
            return new NaiveNumber(string);
        }

        @Override
        public NumberInterface instanceForPi() {
            return new NaiveNumber(Math.PI);
        }
    };
    /**
     * The implementation for the infinite-precision BigDecimal.
     */
    public static final NumberImplementation IMPLEMENTATION_PRECISE = new NumberImplementation(PreciseNumber.class, 0) {
        @Override
        public NumberInterface instanceForString(String string) {
            return new PreciseNumber(string);
        }

        @Override
        public NumberInterface instanceForPi() {
            NumberInterface C = FUNCTION_SQRT.apply(this, new PreciseNumber("10005")).multiply(new PreciseNumber("426880"));
            NumberInterface M = PreciseNumber.ONE;
            NumberInterface L = new PreciseNumber("13591409");
            NumberInterface X = M;
            NumberInterface sum = L;
            int termsNeeded = C.getMaxPrecision() / 13 + 1;

            NumberInterface lSummand = new PreciseNumber("545140134");
            NumberInterface xMultiplier = new PreciseNumber("262537412")
                    .multiply(new PreciseNumber("1000000000"))
                    .add(new PreciseNumber("640768000"))
                    .negate();
            for (int i = 0; i < termsNeeded; i++) {
                M = M
                        .multiply(new PreciseNumber((12 * i + 2) + ""))
                        .multiply(new PreciseNumber((12 * i + 6) + ""))
                        .multiply(new PreciseNumber((12 * i + 10) + ""))
                        .divide(new PreciseNumber(Math.pow(i + 1, 3) + ""));
                L = L.add(lSummand);
                X = X.multiply(xMultiplier);
                sum = sum.add(M.multiply(L).divide(X));
            }
            return C.divide(sum);
        }
    };
    /**
     * The division operator, /
     */
    public static final NumberOperator OP_DIVIDE = new NumberOperator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1) {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 2 && params[1].compareTo(implementation.instanceForString(Integer.toString(0))) != 0;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return params[0].divide(params[1]);
        }
    };
    /**
     * The factorial operator, !
     */
    public static final NumberOperator OP_FACTORIAL = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.UNARY_POSTFIX, 0) {
        //private HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> storedList = new HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>>();
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1
                    && params[0].fractionalPart().compareTo(implementation.instanceForString("0")) == 0
                    && params[0].signum() >= 0;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            if (params[0].signum() == 0) {
                return implementation.instanceForString("1");
            }
            NumberInterface one = implementation.instanceForString("1");
            NumberInterface factorial = params[0];
            NumberInterface multiplier = params[0];
            //It is necessary to later prevent calls of factorial on anything but non-negative integers.
            while ((multiplier = multiplier.subtract(one)).signum() == 1) {
                factorial = factorial.multiply(multiplier);
            }
            return factorial;
                /*if(!storedList.containsKey(params[0].getClass())){
                    storedList.put(params[0].getClass(), new ArrayList<NumberInterface>());
                    storedList.get(params[0].getClass()).add(NaiveNumber.ONE.promoteTo(params[0].getClass()));
                    storedList.get(params[0].getClass()).add(NaiveNumber.ONE.promoteTo(params[0].getClass()));
                }*/
        }
    };
    /**
     * The permutation operator.
     */
    public static final NumberOperator OP_NPR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 2 && params[0].fractionalPart().signum() == 0
                    && params[1].fractionalPart().signum() == 0;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            if (params[0].compareTo(params[1]) < 0 ||
                    params[0].signum() < 0 ||
                    (params[0].signum() == 0 && params[1].signum() != 0)) return implementation.instanceForString("0");
            NumberInterface total = implementation.instanceForString("1");
            NumberInterface multiplyBy = params[0];
            NumberInterface remainingMultiplications = params[1];
            NumberInterface halfway = params[0].divide(implementation.instanceForString("2"));
            if (remainingMultiplications.compareTo(halfway) > 0) {
                remainingMultiplications = params[0].subtract(remainingMultiplications);
            }
            while (remainingMultiplications.signum() > 0) {
                total = total.multiply(multiplyBy);
                remainingMultiplications = remainingMultiplications.subtract(implementation.instanceForString("1"));
                multiplyBy = multiplyBy.subtract(implementation.instanceForString("1"));
            }
            return total;
        }
    };
    /**
     * The combination operator.
     */
    public static final NumberOperator OP_NCR = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 0) {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 2 && params[0].fractionalPart().signum() == 0
                    && params[1].fractionalPart().signum() == 0;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return OP_NPR.apply(implementation, params).divide(OP_FACTORIAL.apply(implementation, params[1]));
        }
    };
    /**
     * The absolute value function, abs(-3) = 3
     */
    public static final NumberFunction FUNCTION_ABS = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return params[0].multiply(implementation.instanceForString(Integer.toString(params[0].signum())));
        }
    };
    /**
     * The natural log function.
     */
    public static final NumberFunction FUNCTION_LN = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1 && params[0].compareTo(implementation.instanceForString("0")) > 0;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            NumberInterface param = params[0];
            NumberInterface one = implementation.instanceForString("1");
            int powersOf2 = 0;
            while (FUNCTION_ABS.apply(implementation, param.subtract(one)).compareTo(implementation.instanceForString(".1")) >= 0) {
                if (param.subtract(one).signum() == 1) {
                    param = param.divide(implementation.instanceForString("2"));
                    powersOf2++;
                    if (param.subtract(one).signum() != 1) {
                        break;
                        //No infinite loop for you.
                    }
                } else {
                    param = param.multiply(implementation.instanceForString("2"));
                    powersOf2--;
                    if (param.subtract(one).signum() != -1) {
                        break;
                        //No infinite loop for you.
                    }
                }
            }
            return getLog2(implementation, param).multiply(implementation.instanceForString(Integer.toString(powersOf2))).add(getLogPartialSum(implementation, param));
        }

        /**
         * Returns the partial sum of the Taylor series for logx (around x=1).
         * Automatically determines the number of terms needed based on the precision of x.
         * @param x value at which the series is evaluated. 0 < x < 2. (x=2 is convergent but impractical.)
         * @return the partial sum.
         */
        private NumberInterface getLogPartialSum(NumberImplementation implementation, NumberInterface x) {

            NumberInterface maxError = x.getMaxError();
            x = x.subtract(implementation.instanceForString("1")); //Terms used are for log(x+1).
            NumberInterface currentNumerator = x, currentTerm = x, sum = x;
            int n = 1;
            while (FUNCTION_ABS.apply(implementation, currentTerm).compareTo(maxError) > 0) {
                n++;
                currentNumerator = currentNumerator.multiply(x).negate();
                currentTerm = currentNumerator.divide(implementation.instanceForString(Integer.toString(n)));
                sum = sum.add(currentTerm);
            }
            return sum;
        }

        /**
         * Returns natural log of 2 to the required precision of the class of number.
         * @param number a number of the same type as the return type. (Used for precision.)
         * @return the value of log(2) with the appropriate precision.
         */
        private NumberInterface getLog2(NumberImplementation implementation, NumberInterface number) {
            NumberInterface maxError = number.getMaxError();
            //NumberInterface errorBound = implementation.instanceForString("1");
            //We'll use the series \sigma_{n >= 1) ((1/3^n + 1/4^n) * 1/n)
            //In the following, a=1/3^n, b=1/4^n, c = 1/n.
            //a is also an error bound.
            NumberInterface a = implementation.instanceForString("1"), b = a, c = a;
            NumberInterface sum = implementation.instanceForString("0");
            NumberInterface one = implementation.instanceForString("1");
            int n = 0;
            while (a.compareTo(maxError) >= 1) {
                n++;
                a = a.divide(implementation.instanceForString("3"));
                b = b.divide(implementation.instanceForString("4"));
                c = one.divide(implementation.instanceForString(Integer.toString(n)));
                sum = sum.add(a.add(b).multiply(c));
            }
            return sum;
        }
    };
    /**
     * Gets a random number smaller or equal to the given number's integer value.
     */
    public static final NumberFunction FUNCTION_RAND_INT = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return implementation.instanceForString(Long.toString(Math.round(Math.random() * params[0].floor().intValue())));
        }
    };
    /**
     * The caret / pow operator, ^
     */
    public static final NumberOperator OP_CARET = new NumberOperator(OperatorAssociativity.RIGHT, OperatorType.BINARY_INFIX, 2) {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            NumberInterface zero = implementation.instanceForString("0");
            return params.length == 2
                    && !(params[0].compareTo(zero) == 0
                    && params[1].compareTo(zero) == 0)
                    && !(params[0].signum() == -1 && params[1].fractionalPart().compareTo(zero) != 0);
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            NumberInterface zero = implementation.instanceForString("0");
            if (params[0].compareTo(zero) == 0)
                return zero;
            else if (params[1].compareTo(zero) == 0)
                return implementation.instanceForString("1");
            //Detect integer bases:
            if (params[0].fractionalPart().compareTo(implementation.instanceForString("0")) == 0
                    && FUNCTION_ABS.apply(implementation, params[1]).compareTo(implementation.instanceForString(Integer.toString(Integer.MAX_VALUE))) < 0
                    && FUNCTION_ABS.apply(implementation, params[1]).compareTo(implementation.instanceForString("1")) >= 0) {
                NumberInterface[] newParams = {params[0], params[1].fractionalPart()};
                return params[0].intPow(params[1].floor().intValue()).multiply(applyInternal(implementation, newParams));
            }
            return FUNCTION_EXP.apply(implementation, FUNCTION_LN.apply(implementation, FUNCTION_ABS.apply(implementation, params[0])).multiply(params[1]));
        }
    };
    /**
     * The square root function.
     */
    public static final NumberFunction FUNCTION_SQRT = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return OP_CARET.apply(implementation, params[0], implementation.instanceForString(".5"));
        }
    };
    private static final HashMap<NumberImplementation, ArrayList<NumberInterface>> FACTORIAL_LISTS = new HashMap<>();
    /**
     * The exponential function, exp(1) = e^1 = 2.71...
     */
    public static final NumberFunction FUNCTION_EXP = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            NumberInterface maxError = params[0].getMaxError();
            int n = 0;
            if (params[0].signum() < 0) {
                NumberInterface[] negatedParams = {params[0].negate()};
                return implementation.instanceForString("1").divide(applyInternal(implementation, negatedParams));
            } else {
                //We need n such that x^(n+1) * 3^ceil(x) <= maxError * (n+1)!.
                //right and left refer to lhs and rhs in the above inequality.
                NumberInterface sum = implementation.instanceForString("1");
                NumberInterface nextNumerator = params[0];
                NumberInterface left = params[0].multiply(implementation.instanceForString("3").intPow(params[0].ceiling().intValue())), right = maxError;
                do {
                    sum = sum.add(nextNumerator.divide(factorial(implementation, n + 1)));
                    n++;
                    nextNumerator = nextNumerator.multiply(params[0]);
                    left = left.multiply(params[0]);
                    NumberInterface nextN = implementation.instanceForString(Integer.toString(n + 1));
                    right = right.multiply(nextN);
                    //System.out.println(left + ", " + right);
                }
                while (left.compareTo(right) > 0);
                //System.out.println(n+1);
                return sum;
            }
        }
    };
    /**
     * The sine function (the argument is interpreted in radians).
     */
    public final NumberFunction functionSin = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            NumberInterface pi = piFor(params[0].getClass());
            NumberInterface twoPi = pi.multiply(implementation.instanceForString("2"));
            NumberInterface theta = getSmallAngle(implementation, params[0], pi);
            //System.out.println(theta);
            if (theta.compareTo(pi.multiply(implementation.instanceForString("1.5"))) >= 0) {
                theta = theta.subtract(twoPi);
            } else if (theta.compareTo(pi.divide(implementation.instanceForString("2"))) > 0) {
                theta = pi.subtract(theta);
            }
            //System.out.println(theta);
            return sinTaylor(implementation, theta);
        }
    };
    /**
     * The cosine function (the argument is in radians).
     */
    public final NumberFunction functionCos = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return functionSin.apply(implementation, piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
                    .subtract(params[0]));
        }
    };
    /**
     * The tangent function (the argument is in radians).
     */
    public final NumberFunction functionTan = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return functionSin.apply(implementation, params[0]).divide(functionCos.apply(implementation, params[0]));
        }
    };
    /**
     * The secant function (the argument is in radians).
     */
    public final NumberFunction functionSec = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return implementation.instanceForString("1").divide(functionCos.apply(implementation, params[0]));
        }
    };
    /**
     * The cosecant function (the argument is in radians).
     */
    public final NumberFunction functionCsc = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return implementation.instanceForString("1").divide(functionSin.apply(implementation, params[0]));
        }
    };
    /**
     * The cotangent function (the argument is in radians).
     */
    public final NumberFunction functionCot = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return functionCos.apply(implementation, params[0]).divide(functionSin.apply(implementation, params[0]));
        }
    };

    /**
     * The arcsine function (return type in radians).
     */
    public final NumberFunction functionArcsin = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1
                    && FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString("1")) <= 0;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            if (FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString(".8")) >= 0) {
                NumberInterface[] newParams = {FUNCTION_SQRT.apply(implementation, implementation.instanceForString("1").subtract(params[0].multiply(params[0])))};
                return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
                        .subtract(applyInternal(implementation, newParams)).multiply(implementation.instanceForString(Integer.toString(params[0].signum())));
            }
            NumberInterface currentTerm = params[0], sum = currentTerm,
                    multiplier = currentTerm.multiply(currentTerm), summandBound = sum.getMaxError().multiply(implementation.instanceForString("1").subtract(multiplier)),
                    power = currentTerm, coefficient = implementation.instanceForString("1");
            int exponent = 1;
            while (FUNCTION_ABS.apply(implementation, currentTerm).compareTo(summandBound) > 0) {
                exponent += 2;
                power = power.multiply(multiplier);
                coefficient = coefficient.multiply(implementation.instanceForString(Integer.toString(exponent - 2)))
                        .divide(implementation.instanceForString(Integer.toString(exponent - 1)));
                currentTerm = power.multiply(coefficient).divide(implementation.instanceForString(Integer.toString(exponent)));
                sum = sum.add(currentTerm);
            }
            return sum;
        }
    };

    /**
     * The arccosine function.
     */
    public final NumberFunction functionArccos = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1 && FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString("1")) <= 0;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
                    .subtract(functionArcsin.apply(implementation, params));
        }
    };

    /**
     * The arccosecant function.
     */
    public final NumberFunction functionArccsc = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1 && FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString("1")) >= 0;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            NumberInterface[] reciprocalParamArr = {implementation.instanceForString("1").divide(params[0])};
            return functionArcsin.apply(implementation, reciprocalParamArr);
        }
    };

    /**
     * The arcsecant function.
     */
    public final NumberFunction functionArcsec = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1 && FUNCTION_ABS.apply(implementation, params[0]).compareTo(implementation.instanceForString("1")) >= 0;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            NumberInterface[] reciprocalParamArr = {implementation.instanceForString("1").divide(params[0])};
            return functionArccos.apply(implementation, reciprocalParamArr);
        }
    };

    /**
     * The arctangent function.
     */
    public final NumberFunction functionArctan = new NumberFunction() {
        @Override
        public boolean matchesParams(NumberImplementation implementation, NumberInterface[] params) {
            return params.length == 1;
        }

        @Override
        public NumberInterface applyInternal(NumberImplementation implementation, NumberInterface[] params) {
            if (params[0].signum() == -1) {
                NumberInterface[] negatedParams = {params[0].negate()};
                return applyInternal(implementation, negatedParams).negate();
            }
            if (params[0].compareTo(implementation.instanceForString("1")) > 0) {
                NumberInterface[] reciprocalParams = {implementation.instanceForString("1").divide(params[0])};
                return piFor(params[0].getClass()).divide(implementation.instanceForString("2"))
                        .subtract(applyInternal(implementation, reciprocalParams));
            }
            if (params[0].compareTo(implementation.instanceForString("1")) == 0) {
                return piFor(params[0].getClass()).divide(implementation.instanceForString("4"));
            }
            if (params[0].compareTo(implementation.instanceForString(".9")) >= 0) {
                NumberInterface[] newParams = {params[0].multiply(implementation.instanceForString("2"))
                        .divide(implementation.instanceForString("1").subtract(params[0].multiply(params[0])))};
                return applyInternal(implementation, newParams).divide(implementation.instanceForString("2"));
            }
            NumberInterface currentPower = params[0], currentTerm = currentPower, sum = currentTerm,
                    maxError = params[0].getMaxError(), multiplier = currentPower.multiply(currentPower).negate();
            int n = 1;
            while (FUNCTION_ABS.apply(implementation, currentTerm).compareTo(maxError) > 0) {
                n += 2;
                currentPower = currentPower.multiply(multiplier);
                currentTerm = currentPower.divide(implementation.instanceForString(Integer.toString(n)));
{'fraction_non_alphanumeric': 0.10529243215911006, 'fraction_numerical': 0.012101803472105174, 'mean_word_length': 2.5044890726520967, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 61, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '373278', 'n_tokens_mistral': 14943, 'n_tokens_neox': 14014, 'n_words': 3132}