package jscl;
import jscl.math.Expression;
import jscl.math.Generic;
import jscl.math.NotIntegerException;
import jscl.math.function.Constants;
import jscl.math.function.ConstantsRegistry;
import jscl.math.function.Function;
import jscl.math.function.FunctionsRegistry;
import jscl.math.function.IConstant;
import jscl.math.function.PostfixFunctionsRegistry;
import jscl.math.function.*;
import jscl.math.operator.Operator;
import jscl.math.operator.Percent;
import jscl.math.operator.Rand;
import jscl.math.operator.matrix.OperatorsRegistry;
import jscl.text.ParseException;
import org.solovyev.common.NumberFormatter;
import org.solovyev.common.math.MathRegistry;
import org.solovyev.common.msg.MessageRegistry;
import org.solovyev.common.msg.Messages;
import static midpcalc.Real.NumberFormat.FSE_ENG;
import static midpcalc.Real.NumberFormat.FSE_NONE;
import static midpcalc.Real.NumberFormat.FSE_SCI;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.math.BigInteger;
import java.util.List;
import static midpcalc.Real.NumberFormat.*;
public class JsclMathEngine implements MathEngine {
public static final AngleUnit DEFAULT_ANGLE_UNITS = AngleUnit.deg;
public static final NumeralBase DEFAULT_NUMERAL_BASE = NumeralBase.dec;
public static final char GROUPING_SEPARATOR_DEFAULT = ' ';
@Nonnull
private static JsclMathEngine instance = new JsclMathEngine();
@Nonnull
private final ConstantsRegistry constantsRegistry = new ConstantsRegistry();
@Nonnull
private final ThreadLocal<NumberFormatter> numberFormatter = new ThreadLocal<NumberFormatter>() {
@Override
protected NumberFormatter initialValue() {
return new NumberFormatter();
}
};
private char groupingSeparator = NumberFormatter.NO_GROUPING;
private int notation = FSE_NONE;
private int precision = NumberFormatter.MAX_PRECISION;
@Nonnull
private AngleUnit angleUnits = DEFAULT_ANGLE_UNITS;
@Nonnull
private NumeralBase numeralBase = DEFAULT_NUMERAL_BASE;
@Nonnull
private MessageRegistry messageRegistry = Messages.synchronizedMessageRegistry(new FixedCapacityListMessageRegistry(10));
public JsclMathEngine() {
}
@Nonnull
public static JsclMathEngine getInstance() {
return instance;
}
@Nonnull
public String evaluate(@Nonnull String expression) throws ParseException {
return evaluateGeneric(expression).toString();
}
@Nonnull
public String simplify(@Nonnull String expression) throws ParseException {
return simplifyGeneric(expression).toString();
}
@Nonnull
public String elementary(@Nonnull String expression) throws ParseException {
return elementaryGeneric(expression).toString();
}
@Nonnull
public Generic evaluateGeneric(@Nonnull String expression) throws ParseException {
if (expression.contains(Percent.NAME) || expression.contains(Rand.NAME)) {
return Expression.valueOf(expression).numeric();
} else {
return Expression.valueOf(expression).expand().numeric();
}
}
@Nonnull
public Generic simplifyGeneric(@Nonnull String expression) throws ParseException {
if (expression.contains(Percent.NAME) || expression.contains(Rand.NAME)) {
return Expression.valueOf(expression);
} else {
return Expression.valueOf(expression).expand().simplify();
}
}
@Nonnull
public Generic elementaryGeneric(@Nonnull String expression) throws ParseException {
return Expression.valueOf(expression).elementary();
}
@Nonnull
public MathRegistry<Function> getFunctionsRegistry() {
return FunctionsRegistry.getInstance();
}
@Nonnull
public MathRegistry<Operator> getOperatorsRegistry() {
return OperatorsRegistry.getInstance();
}
@Nonnull
public MathRegistry<Operator> getPostfixFunctionsRegistry() {
return PostfixFunctionsRegistry.getInstance();
}
@Nonnull
public AngleUnit getAngleUnits() {
return angleUnits;
}
public void setAngleUnits(@Nonnull AngleUnit angleUnits) {
this.angleUnits = angleUnits;
}
@Nonnull
public NumeralBase getNumeralBase() {
return numeralBase;
}
public void setNumeralBase(@Nonnull NumeralBase numeralBase) {
this.numeralBase = numeralBase;
}
@Nonnull
public MathRegistry<IConstant> getConstantsRegistry() {
return constantsRegistry;
}
@Nonnull
public String format(double value) {
return format(value, numeralBase);
}
@Nonnull
public String format(double value, @Nonnull NumeralBase nb) {
if (Double.isInfinite(value)) {
return formatInfinity(value);
}
if (Double.isNaN(value)) {
// return "NaN"
return String.valueOf(value);
}
if (nb == NumeralBase.dec) {
if (value == 0d) {
return "0";
}
// detect if current number is precisely equals to constant in constants' registry (NOTE: ONLY FOR SYSTEM CONSTANTS)
final IConstant constant = findConstant(value);
if (constant != null) {
return constant.getName();
}
}
return prepareNumberFormatter(nb).format(value, nb.radix).toString();
}
private NumberFormatter prepareNumberFormatter(@Nonnull NumeralBase nb) {
final NumberFormatter nf = numberFormatter.get();
nf.setGroupingSeparator(hasGroupingSeparator() ? getGroupingSeparator(nb) : NumberFormatter.NO_GROUPING);
nf.setPrecision(precision);
switch (notation) {
case FSE_ENG:
nf.useEngineeringFormat(NumberFormatter.DEFAULT_MAGNITUDE);
break;
case FSE_SCI:
nf.useScientificFormat(NumberFormatter.DEFAULT_MAGNITUDE);
break;
default:
nf.useSimpleFormat();
break;
}
return nf;
}
@Override
public String format(@Nonnull BigInteger value) {
return format(value, numeralBase);
}
@Nonnull
public String format(@Nonnull BigInteger value, @Nonnull NumeralBase nb) {
if (nb == NumeralBase.dec) {
if (BigInteger.ZERO.equals(value)) {
return "0";
}
}
return prepareNumberFormatter(nb).format(value, nb.radix).toString();
}
@Nullable
private IConstant findConstant(double value) {
final IConstant constant = findConstant(constantsRegistry.getSystemEntities(), value);
if (constant != null) {
return constant;
}
final IConstant piInv = constantsRegistry.get(Constants.PI_INV.getName());
if (piInv != null) {
final Double piInvValue = piInv.getDoubleValue();
if (piInvValue != null && piInvValue == value) {
return piInv;
}
}
return null;
}
private String formatInfinity(@Nonnull Double value) {
// return predefined constant for infinity
if (value >= 0) {
return Constants.INF.getName();
} else {
return Constants.INF.expressionValue().negate().toString();
}
}
@Nullable
private IConstant findConstant(@Nonnull List<IConstant> constants, @Nonnull Double value) {
for (int i = 0; i < constants.size(); i++) {
final IConstant constant = constants.get(i);
if (!value.equals(constant.getDoubleValue())) {
continue;
}
final String name = constant.getName();
if (name.equals(Constants.PI_INV.getName()) || name.equals(Constants.ANS)) {
continue;
}
if (!name.equals(Constants.PI.getName()) || getAngleUnits() == AngleUnit.rad) {
return constant;
}
}
return null;
}
@Nonnull
public MessageRegistry getMessageRegistry() {
return messageRegistry;
}
public void setMessageRegistry(@Nonnull MessageRegistry messageRegistry) {
this.messageRegistry = messageRegistry;
}
@Nonnull
@Override
public String format(@Nonnull String value, @Nonnull NumeralBase nb) {
if (!hasGroupingSeparator()) {
return value;
}
final int dot = value.indexOf('.');
if (dot >= 0) {
final String intPart = dot != 0 ? insertSeparators(value.substring(0, dot), nb) : "";
return intPart + value.substring(dot);
}
final int e = nb == NumeralBase.hex ? -1 : value.indexOf('E');
if (e >= 0) {
final String intPart = e != 0 ? insertSeparators(value.substring(0, e), nb) : "";
return intPart + value.substring(e);
}
return insertSeparators(value, nb);
}
@Nonnull
public String insertSeparators(@Nonnull String value, @Nonnull NumeralBase nb) {
final char separator = getGroupingSeparator(nb);
final StringBuilder result = new StringBuilder(value.length() + nb.getGroupingSize());
for (int i = value.length() - 1; i >= 0; i--) {
result.append(value.charAt(i));
if (i != 0 && (value.length() - i) % nb.getGroupingSize() == 0) {
result.append(separator);
}
}
return result.reverse().toString();
}
private boolean hasGroupingSeparator() {
return groupingSeparator != NumberFormatter.NO_GROUPING;
}
private char getGroupingSeparator(@Nonnull NumeralBase nb) {
return nb == NumeralBase.dec ? groupingSeparator : ' ';
}
public void setPrecision(int precision) {
this.precision = precision;
}
public void setNotation(int notation) {
if (notation != FSE_SCI && notation != FSE_ENG && notation != FSE_NONE) {
throw new IllegalArgumentException("Unsupported notation: " + notation);
}
this.notation = notation;
}
public char getGroupingSeparator() {
return this.groupingSeparator;
}
public void setGroupingSeparator(char separator) {
this.groupingSeparator = separator;
}
}