├── .gitignore ├── .gitattributes ├── MathParser ├── src │ └── com │ │ └── aghajari │ │ ├── math │ │ ├── exception │ │ │ ├── MathInvalidParameterException.java │ │ │ ├── BalancedParenthesesException.java │ │ │ ├── MathFunctionInvalidArgumentsException.java │ │ │ ├── MathFunctionNotFoundException.java │ │ │ ├── MathVariableNotFoundException.java │ │ │ └── MathParserException.java │ │ ├── custom │ │ │ ├── FunctionWrapper.java │ │ │ ├── Derivative.java │ │ │ ├── LogFunction.java │ │ │ ├── RadicalFunction.java │ │ │ ├── LimitFunction.java │ │ │ └── Integration.java │ │ ├── MathFunction.java │ │ ├── Utils.java │ │ ├── Functions.java │ │ └── MathParser.java │ │ └── Main.java └── MathParser.iml ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /MathParser/src/com/aghajari/math/exception/MathInvalidParameterException.java: -------------------------------------------------------------------------------- 1 | package com.aghajari.math.exception; 2 | 3 | public class MathInvalidParameterException extends MathParserException { 4 | 5 | public MathInvalidParameterException(String message) { 6 | super(null, -1, message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /MathParser/src/com/aghajari/math/exception/BalancedParenthesesException.java: -------------------------------------------------------------------------------- 1 | package com.aghajari.math.exception; 2 | 3 | public class BalancedParenthesesException extends MathParserException { 4 | 5 | public BalancedParenthesesException(String src, int index) { 6 | super(src, index, "unexpected parentheses" + (index != -1 ? " at " + index : "")); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /MathParser/MathParser.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /MathParser/src/com/aghajari/math/exception/MathFunctionInvalidArgumentsException.java: -------------------------------------------------------------------------------- 1 | package com.aghajari.math.exception; 2 | 3 | import com.aghajari.math.MathFunction; 4 | 5 | public class MathFunctionInvalidArgumentsException extends MathParserException { 6 | 7 | private final MathFunction function; 8 | 9 | public MathFunctionInvalidArgumentsException(String src, int index, MathFunction function, int count) { 10 | super(src, index, function.name() + "() Expected " + function.getParameterCount() + " arguments but found " + count); 11 | this.function = function; 12 | } 13 | 14 | public MathFunction getFunction() { 15 | return function; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MathParser/src/com/aghajari/math/exception/MathFunctionNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.aghajari.math.exception; 2 | 3 | import com.aghajari.math.MathFunction; 4 | 5 | public class MathFunctionNotFoundException extends MathParserException { 6 | 7 | private final String function; 8 | 9 | public MathFunctionNotFoundException(String src, int index, String function) { 10 | super(src, index, function == null ? "couldn't find function" : function + "() not found"); 11 | this.function = function; 12 | } 13 | 14 | public MathFunctionNotFoundException(String src, int index, String function, String message) { 15 | super(src, index, "couldn't find function: " + message); 16 | this.function = function; 17 | } 18 | 19 | public String getFunction() { 20 | return function; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /MathParser/src/com/aghajari/math/custom/FunctionWrapper.java: -------------------------------------------------------------------------------- 1 | package com.aghajari.math.custom; 2 | 3 | import com.aghajari.math.MathParser; 4 | import com.aghajari.math.exception.MathParserException; 5 | 6 | public final class FunctionWrapper { 7 | final MathParser parser; 8 | final String exp; 9 | final MathParser.MathVariable var; 10 | double cache = Double.NaN, answer, old; 11 | 12 | public FunctionWrapper(MathParser parser, String exp, MathParser.MathVariable var) { 13 | this.parser = parser; 14 | this.exp = exp; 15 | this.var = var; 16 | } 17 | 18 | public double apply(double a) throws MathParserException { 19 | if (!Double.isNaN(cache) && cache == old) 20 | return answer; 21 | old = a; 22 | var.updateAnswer(a); 23 | return answer = parser.parse(exp); 24 | } 25 | } -------------------------------------------------------------------------------- /MathParser/src/com/aghajari/math/exception/MathVariableNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.aghajari.math.exception; 2 | 3 | public class MathVariableNotFoundException extends MathParserException { 4 | 5 | final String variableName, guess; 6 | 7 | public MathVariableNotFoundException(String src, int index, String variableName) { 8 | super(src, index, variableName + " not found!"); 9 | this.variableName = variableName; 10 | guess = null; 11 | } 12 | 13 | public MathVariableNotFoundException(String src, int index, String variableName, String guess) { 14 | super(src, index, variableName + " not found, did you mean " + guess + "?"); 15 | this.variableName = variableName; 16 | this.guess = guess; 17 | } 18 | 19 | public String getVariableName() { 20 | return variableName; 21 | } 22 | 23 | public String getGuess() { 24 | return guess; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MathParser/src/com/aghajari/math/custom/Derivative.java: -------------------------------------------------------------------------------- 1 | package com.aghajari.math.custom; 2 | 3 | import com.aghajari.math.exception.MathParserException; 4 | 5 | // https://github.com/allusai/calculus-solver/blob/master/Calculus.java 6 | public class Derivative { 7 | /* 8 | *These constants can modified to change the accuracy of approximation 9 | *A smaller epsilon/step size uses more memory but yields a more 10 | *accurate approximation of the derivative/integral respectively 11 | */ 12 | private final static double EPSILON = 0.0000001; 13 | 14 | 15 | /** 16 | * Calculates the derivative around a certain point using 17 | * a numerical approximation. 18 | * 19 | * @param x the x-coordinate at which to approximate the derivative 20 | * @return double the derivative at the specified point 21 | */ 22 | public static double getDerivative(FunctionWrapper function, double x) throws MathParserException { 23 | //The y-coordinates of the points close to the specified x-coordinates 24 | double yOne = function.apply(x - EPSILON); 25 | double yTwo = function.apply(x + EPSILON); 26 | 27 | return (yTwo - yOne) / (2 * EPSILON); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /MathParser/src/com/aghajari/math/custom/LogFunction.java: -------------------------------------------------------------------------------- 1 | package com.aghajari.math.custom; 2 | 3 | import com.aghajari.math.Functions; 4 | import com.aghajari.math.MathFunction; 5 | import com.aghajari.math.MathParser; 6 | import com.aghajari.math.Utils; 7 | 8 | 9 | /** 10 | * A {@link MathFunction} for {@link Math#log(double)} 11 | * log2(x) 12 | * log3(x) 13 | * ... 14 | * log[BASE](x) 15 | */ 16 | public class LogFunction implements MathFunction { 17 | int base; 18 | 19 | @Override 20 | public String name() { 21 | return "log"; 22 | } 23 | 24 | @Override 25 | public boolean compareNames(String name) { 26 | if (name.trim().toLowerCase().startsWith("log")) { 27 | name = name.substring(3); 28 | if (Utils.isUnsignedInteger(name)) { 29 | base = Integer.parseInt(name); 30 | return true; 31 | } 32 | } 33 | return false; 34 | } 35 | 36 | @Override 37 | public double calculate(Object... parameters) { 38 | return Functions.log((double) parameters[0], base); 39 | } 40 | 41 | @Override 42 | public int getParameterCount() { 43 | return 1; 44 | } 45 | 46 | @Override 47 | public boolean isSpecialParameter(int index) { 48 | return false; 49 | } 50 | 51 | @Override 52 | public void attachToParser(MathParser parser) { 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /MathParser/src/com/aghajari/math/exception/MathParserException.java: -------------------------------------------------------------------------------- 1 | package com.aghajari.math.exception; 2 | 3 | import com.aghajari.math.Utils; 4 | 5 | public class MathParserException extends Exception { 6 | 7 | final String source, localMessage; 8 | final int index; 9 | 10 | public MathParserException(String src, int index, String message) { 11 | super(message + generateMessages(src, index)); 12 | this.localMessage = message; 13 | this.source = src; 14 | this.index = index; 15 | } 16 | 17 | public MathParserException(String source, String message, Throwable cause) { 18 | super(message, cause); 19 | this.localMessage = message; 20 | this.source = source; 21 | this.index = -1; 22 | } 23 | 24 | public int getIndex() { 25 | return index; 26 | } 27 | 28 | public String getSource() { 29 | return source; 30 | } 31 | 32 | public String getLocalMessage() { 33 | return localMessage; 34 | } 35 | 36 | public String getCursor() { 37 | return getCursor(index); 38 | } 39 | 40 | private static String getCursor(int index) { 41 | return Utils.repeat(' ', index - 1) + "^"; 42 | } 43 | 44 | private static String generateMessages(String src, int index) { 45 | if (index == -1 || (src == null || src.isEmpty())) 46 | return ""; 47 | 48 | return "\n\t" + src + "\n\t" + getCursor(index); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /MathParser/src/com/aghajari/math/custom/RadicalFunction.java: -------------------------------------------------------------------------------- 1 | package com.aghajari.math.custom; 2 | 3 | import com.aghajari.math.Functions; 4 | import com.aghajari.math.MathFunction; 5 | import com.aghajari.math.MathParser; 6 | import com.aghajari.math.Utils; 7 | 8 | /** 9 | * A {@link MathFunction} for radical, {@link Math#sqrt)} & {@link Math#cbrt(double)} 10 | * radical2(x) OR √2(x) 11 | * radical3(x) OR √3(x) 12 | * ... 13 | * radical[BASE](x) OR √[Root](x) 14 | */ 15 | public class RadicalFunction implements MathFunction { 16 | int root; 17 | 18 | @Override 19 | public String name() { 20 | return "radical"; 21 | } 22 | 23 | @Override 24 | public boolean compareNames(String name) { 25 | if (name.trim().toLowerCase().startsWith("radical")) { 26 | name = name.substring(7); 27 | } else if (name.startsWith("√")) 28 | name = name.substring("√".length()); 29 | else 30 | return false; 31 | 32 | if (Utils.isUnsignedInteger(name)) { 33 | root = Integer.parseInt(name); 34 | return true; 35 | } 36 | return false; 37 | } 38 | 39 | @Override 40 | public double calculate(Object... parameters) { 41 | return Functions.radical((double) parameters[0], root); 42 | } 43 | 44 | @Override 45 | public int getParameterCount() { 46 | return 1; 47 | } 48 | 49 | @Override 50 | public boolean isSpecialParameter(int index) { 51 | return false; 52 | } 53 | 54 | @Override 55 | public void attachToParser(MathParser parser) { 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /MathParser/src/com/aghajari/Main.java: -------------------------------------------------------------------------------- 1 | package com.aghajari; 2 | 3 | import com.aghajari.math.MathParser; 4 | import com.aghajari.math.Utils; 5 | import com.aghajari.math.exception.MathParserException; 6 | import com.aghajari.math.exception.MathVariableNotFoundException; 7 | 8 | import java.util.Scanner; 9 | 10 | public class Main { 11 | 12 | public static void main(String[] args) throws MathParserException { 13 | Scanner scanner = new Scanner(System.in); 14 | MathParser parser = MathParser.create(); 15 | 16 | for (; ; ) { 17 | String line = scanner.nextLine().trim(); 18 | if (line.isEmpty()) 19 | continue; 20 | 21 | if (isExp(line)) { 22 | parser.addExpression(line); 23 | 24 | } else { 25 | try { 26 | System.out.println("\n" + parser.parse(line)); 27 | 28 | } catch (MathParserException e) { 29 | if (e instanceof MathVariableNotFoundException) { 30 | System.out.println(e.getMessage()); 31 | continue; 32 | } 33 | e.printStackTrace(); 34 | return; 35 | } 36 | break; 37 | } 38 | } 39 | 40 | if (!parser.getVariables().isEmpty()) { 41 | System.out.println("Variables:"); 42 | for (MathParser.MathVariable variable : parser.getVariables()) 43 | System.out.println(variable.getName() + " = " + variable.getExpression() + " = " + variable.getAnswer()); 44 | } 45 | } 46 | 47 | public static boolean isExp(String line) { 48 | if (line.contains("=")) { 49 | if (line.contains("if")) { 50 | return isExp(line.substring(0, line.indexOf("if"))); 51 | } else { 52 | String l2 = line.substring(0, line.indexOf('=')); 53 | return Utils.isIdentifier(Utils.realTrim(l2)) || l2.contains("("); 54 | } 55 | } else 56 | return false; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MathParser/src/com/aghajari/math/custom/LimitFunction.java: -------------------------------------------------------------------------------- 1 | package com.aghajari.math.custom; 2 | 3 | import com.aghajari.math.MathParser; 4 | import com.aghajari.math.exception.MathParserException; 5 | 6 | /** 7 | * TO-DO: Write a new function for limit. 8 | */ 9 | public final class LimitFunction { 10 | 11 | private LimitFunction() { 12 | } 13 | 14 | public static double limit(MathParser parser, MathParser.MathVariable var, String exp, double approach) throws MathParserException { 15 | FunctionWrapper func = new FunctionWrapper(parser, exp, var); 16 | double below = limitFromBelow(func, approach); 17 | double above = limitFromAbove(func, approach); 18 | //System.out.println(below + " : " + above); 19 | 20 | return (below == above) ? below : Double.NaN; 21 | } 22 | 23 | public static double limitFromBelow(FunctionWrapper function, double approach) throws MathParserException { 24 | for (double d = approach - 10; d <= approach; d = approach 25 | - ((approach - d) / 10)) { 26 | if (function.apply(d) == Double.POSITIVE_INFINITY) { 27 | return Double.POSITIVE_INFINITY; 28 | } else if (function.apply(d) == Double.NEGATIVE_INFINITY) { 29 | return Double.NEGATIVE_INFINITY; 30 | } else if (Double.isNaN(function.apply(d))) { 31 | return function.apply(approach + ((approach - d) * 10)); 32 | } else { 33 | if (d == approach) { 34 | return function.apply(d); 35 | } else if (approach - d < 0.00000000001) 36 | d = approach; 37 | 38 | } 39 | } 40 | return Double.NaN; 41 | } 42 | 43 | public static double limitFromAbove(FunctionWrapper function, double approach) throws MathParserException { 44 | for (double d = approach + 10; d >= approach; d = approach 45 | - ((approach - d) / 10)) { 46 | if (function.apply(d) == Double.POSITIVE_INFINITY) { 47 | return Double.POSITIVE_INFINITY; 48 | } else if (function.apply(d) == Double.NEGATIVE_INFINITY) { 49 | return Double.NEGATIVE_INFINITY; 50 | } else if (Double.isNaN(function.apply(d))) { 51 | return function.apply(approach + ((approach - d) * 10)); 52 | } else { 53 | if (d == approach) { 54 | return function.apply(d); 55 | } else if (d - approach < 0.00000000001) 56 | d = approach; 57 | 58 | } 59 | } 60 | return Double.NaN; 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MathParser 2 | **MathParser** is a simple but powerful open-source math tool that parses and evaluates algebraic expressions written in pure java. 3 | 4 | This project is designed for University first project of Advanced-Programming at the Department of Computer Engineering, Amirkabir University of Technology. 5 | 6 | ## Syntax 7 | 8 | 1. Create an instance of `MathParser` 9 | ```java 10 | MathParser parser = MathParser.create(); 11 | ``` 12 | 13 | 2. Parse an expression 14 | ```java 15 | System.out.println(parser.parse("2 + 2")); // 4.0 16 | System.out.println(parser.parse("5^2 * (2 + 3 * 4) + 5!/4")); // 380.0 17 | ``` 18 | 19 | ### Add Expression (Function or Variable) 20 | ```java 21 | parser.addExpression("f(x, y) = 2(x + y)"); // addFunction 22 | parser.addExpression("x0 = 1 + 2 ^ 2"); // addVariable 23 | parser.addExpression("y0 = 2x0"); // addVariable 24 | System.out.println(parser.parse("1 + 2f(x0, y0)/3")); // 21.0 25 | ``` 26 | 27 | ### Built-in functions 28 | MathParser identifies all static methods in [Math](https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html) and [Built-in Functions](https://github.com/Aghajari/MathParser/blob/main/MathParser/src/com/aghajari/math/Functions.java). Functions and Variables are case-insensitive. 29 | 30 | ```java 31 | System.out.println(parser.parse("sin(3pi/2) + tan(45°)")); 32 | ``` 33 | 34 | Built-in Functions includes integral, derivative, limit and sigma 35 | ```java 36 | System.out.println(parser.parse("2 ∫(x, (x^3)/(x+1), 5, 10)")); // 517.121062 37 | System.out.println(parser.parse("derivative(x, x^3, 2)")); // 12.0 38 | System.out.println(parser.parse("lim(x->2, x^(x + 2)) / 2")); // 8.0 39 | System.out.println(parser.parse("Σ(i, 2i^2, 1, 5)")); // 220.0 40 | ``` 41 | 42 | Supports factorial, binary, hexadecimal, octal: 43 | ```java 44 | System.out.println(parser.parse("5!/4")); // 30.0 45 | System.out.println(parser.parse("(0b100)!")); // 4! = 24.0 46 | System.out.println(parser.parse("log2((0xFF) + 1)")); // log2(256) = 8.0 47 | System.out.println(parser.parse("(0o777)")); // 511.0 48 | ``` 49 | 50 | Supports IF conditions: 51 | ```java 52 | System.out.println(parser.parse("2 + if(2^5 >= 5!, 1, 0)")); // 2.0 53 | 54 | parser.addExpression("gcd(x, y) = if(y = 0, x, gcd(y, x % y))"); // GCD Recursive 55 | System.out.println(parser.parse("gcd(8, 20)")); // 4.0 56 | ``` 57 | GCD Recursive was only an example, gcd is a built-in function so you don't need to add it as an expression. 58 | ```java 59 | System.out.println(parser.parse("gcd(8, 20, 100, 150)")); // 2.0 60 | ``` 61 | Some functions such as `sum`, `max`, `min` and `gcd` get array. 62 | 63 | ### Import new Functions Easy A! 64 | 65 | 1. Create a class and static methods as your functions: 66 | ```java 67 | public class MyFunctions { 68 | 69 | public static double test(double a, double b) { 70 | return a * b / 2; 71 | } 72 | 73 | public static double minus(Double... a) { 74 | double out = a[0]; 75 | for (int i = 1; i < a.length; i++) 76 | out -= a[i]; 77 | return out; 78 | } 79 | 80 | } 81 | ``` 82 | 2. Add the class to the parser 83 | ```java 84 | parser.addFunctions(MyFunctions.class); 85 | ``` 86 | 3. Done! 87 | ```java 88 | System.out.println(parser.parse("test(10, 5)")); // 25.0 89 | System.out.println(parser.parse("minus(100, 25, 5, 2)")); // 68.0 90 | ``` 91 | 92 | License 93 | ======= 94 | 95 | Copyright 2022 Amir Hossein Aghajari 96 | Licensed under the Apache License, Version 2.0 (the "License"); 97 | you may not use this file except in compliance with the License. 98 | You may obtain a copy of the License at 99 | 100 | http://www.apache.org/licenses/LICENSE-2.0 101 | 102 | Unless required by applicable law or agreed to in writing, software 103 | distributed under the License is distributed on an "AS IS" BASIS, 104 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 105 | See the License for the specific language governing permissions and 106 | limitations under the License. 107 | 108 | 109 |

110 |
111 | LCoders | AmirHosseinAghajari 112 |
Amir Hossein AghajariEmailGitHub 113 |
114 | -------------------------------------------------------------------------------- /MathParser/src/com/aghajari/math/MathFunction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 - Amir Hossein Aghajari 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | 19 | package com.aghajari.math; 20 | 21 | import com.aghajari.math.exception.MathFunctionInvalidArgumentsException; 22 | import com.aghajari.math.exception.MathParserException; 23 | 24 | import java.lang.reflect.InvocationTargetException; 25 | import java.lang.reflect.Method; 26 | import java.util.ArrayList; 27 | import java.util.Arrays; 28 | import java.util.List; 29 | 30 | public interface MathFunction { 31 | 32 | /** 33 | * @return the name of function 34 | */ 35 | String name(); 36 | 37 | /** 38 | * @return True if name mentions this function 39 | */ 40 | boolean compareNames(String name); 41 | 42 | /** 43 | * Calculates and returns the value 44 | * Parameters are usually double values 45 | */ 46 | double calculate(Object... parameters) throws MathParserException; 47 | 48 | int getParameterCount(); 49 | 50 | /** 51 | * @return True if the parameter at specified index won't be a double value (String otherwise) 52 | */ 53 | boolean isSpecialParameter(int index); 54 | 55 | /** 56 | * Calls when this function just attached to a parser or it's parent variable called to calculate 57 | */ 58 | void attachToParser(MathParser parser); 59 | 60 | static MathFunction wrap(final Method method) { 61 | return wrap(method, method.getName()); 62 | } 63 | 64 | static MathFunction wrap(final Method method, final String name) { 65 | return new MathFunction() { 66 | 67 | MathParser parser = null; 68 | 69 | @Override 70 | public String name() { 71 | return name; 72 | } 73 | 74 | @Override 75 | public boolean compareNames(String name) { 76 | return name().trim().equalsIgnoreCase(name.trim()); 77 | } 78 | 79 | @Override 80 | public double calculate(Object... parameters) throws MathParserException { 81 | try { 82 | List pars = new ArrayList<>(); 83 | if (method.getParameterTypes()[0] == MathParser.class) 84 | pars.add(parser); 85 | if (getParameterCount() == -1) { 86 | pars.add(parameters); 87 | } else { 88 | pars.addAll(Arrays.asList(parameters)); 89 | } 90 | return (double) method.invoke(null, pars.toArray()); 91 | } catch (IllegalAccessException | InvocationTargetException e) { 92 | if (e.getCause() instanceof MathParserException) 93 | throw (MathParserException) e.getCause(); 94 | } 95 | return (double) parameters[0]; 96 | } 97 | 98 | @Override 99 | public int getParameterCount() { 100 | int count = method.getParameterCount(); 101 | int first = 0; 102 | if (count >= 1 && method.getParameterTypes()[0] == MathParser.class) { 103 | count--; 104 | first++; 105 | } 106 | if (count == 1 && method.getParameterTypes()[first].isArray()) 107 | return -1; 108 | 109 | return count; 110 | } 111 | 112 | @Override 113 | public boolean isSpecialParameter(int index) { 114 | int first = 0; 115 | if (method.getParameterCount() >= 1 116 | && method.getParameterTypes()[0] == MathParser.class) 117 | first++; 118 | 119 | if (method.getParameterCount() <= index + first) 120 | return false; 121 | 122 | return method.getParameterTypes()[index + first] == String.class; 123 | } 124 | 125 | @Override 126 | public void attachToParser(MathParser parser) { 127 | this.parser = parser; 128 | } 129 | }; 130 | } 131 | 132 | static MathFunction wrap(String exp) { 133 | exp = Utils.realTrim(exp); 134 | String[] var = {exp.substring(0, exp.indexOf('=')), exp.substring(exp.indexOf('=') + 1)}; 135 | String name = var[0].substring(0, var[0].indexOf('(')); 136 | String vars = var[0].substring(var[0].indexOf('(') + 1, var[0].indexOf(')')); 137 | return wrap(name, vars.split(","), var[1]); 138 | } 139 | 140 | static MathFunction wrap(final String functionName, final String[] variables, final String exp) { 141 | final String name = functionName.trim(); 142 | 143 | return new MathFunction() { 144 | 145 | MathParser parser = null; 146 | MathParser.MathVariable[] mVariables = null; 147 | 148 | @Override 149 | public String name() { 150 | return name; 151 | } 152 | 153 | @Override 154 | public boolean compareNames(String name) { 155 | return name().trim().equalsIgnoreCase(name.trim()); 156 | } 157 | 158 | @Override 159 | public double calculate(Object... parameters) throws MathParserException { 160 | if (parameters.length != mVariables.length) 161 | throw new MathFunctionInvalidArgumentsException(null, -1, this, parameters.length); 162 | 163 | for (int i = 0; i < parameters.length; i++) 164 | mVariables[i].updateAnswer(parameters[i]); 165 | 166 | return parser.parse(exp); 167 | } 168 | 169 | @Override 170 | public int getParameterCount() { 171 | return variables.length; 172 | } 173 | 174 | @Override 175 | public boolean isSpecialParameter(int index) { 176 | return false; 177 | } 178 | 179 | @Override 180 | public void attachToParser(MathParser parser) { 181 | this.parser = parser.clone(); 182 | mVariables = new MathParser.MathVariable[variables.length]; 183 | int i = 0; 184 | for (String var : variables) { 185 | this.parser.addVariable(var.trim(), 0, 0); 186 | mVariables[i++] = this.parser.getVariable(var.trim()); 187 | } 188 | } 189 | }; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /MathParser/src/com/aghajari/math/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 - Amir Hossein Aghajari 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | 19 | package com.aghajari.math; 20 | 21 | import com.aghajari.math.exception.BalancedParenthesesException; 22 | 23 | import java.util.regex.Pattern; 24 | 25 | public class Utils { 26 | 27 | /* used to check if parentheses are balanced */ 28 | //public final static Pattern balancedParentheses = Pattern.compile("\\((?:[^)(]+|\\((?:[^)(]+|\\([^)(]*\\))*\\))*\\)"); 29 | /* used to find the innermost parentheses */ 30 | public final static Pattern innermostParentheses = Pattern.compile("(\\([^\\(]*?\\))"); 31 | /* used to split function arguments by comma */ 32 | public final static Pattern splitParameters = Pattern.compile(",(?=(?:[^()]*\\([^()]*\\))*[^\\()]*$)"); 33 | /* used to split if condition to two comparable part */ 34 | public final static Pattern splitIf = Pattern.compile("(.*?)(!=|<>|>=|<=|==|>|=|<)(.*?$)"); 35 | /* used to simplify double type values */ 36 | public final static Pattern doubleType = Pattern.compile("\\(([\\d.]+([eE])[\\d+-]+)\\)"); 37 | /* used to simplify binary values */ 38 | public final static Pattern binary = Pattern.compile("\\(0b[01]+\\)"); 39 | /* used to simplify octal values */ 40 | public final static Pattern octal = Pattern.compile("\\(0o[0-7]+\\)"); 41 | /* used to simplify hexadecimal values */ 42 | public final static Pattern hexadecimal = Pattern.compile("\\(0x[0-9a-fA-F]+\\)"); 43 | 44 | /** 45 | * @param src the expression to check 46 | * @throws BalancedParenthesesException If parentheses aren't balanced 47 | */ 48 | public static void validateBalancedParentheses(String src) throws BalancedParenthesesException { 49 | /*String dest = src.replaceAll(balancedParentheses.pattern(), ""); 50 | if (dest.contains(")")) 51 | throw new BalancedParenthesesException(src, src.indexOf(dest.substring(dest.indexOf(")"))) + 1); 52 | else if (dest.contains("(")) 53 | throw new BalancedParenthesesException(src, src.indexOf(dest.substring(dest.indexOf("("))) + 1);*/ 54 | 55 | if (Utils.realTrim(src).contains("()")) 56 | throw new BalancedParenthesesException(null, -1); 57 | 58 | int opened = 0; 59 | for (int i = 0; i < src.length(); ++i) 60 | if (src.charAt(i) == '(') 61 | opened++; 62 | else if (src.charAt(i) == ')') { 63 | opened--; 64 | if (opened < 0) 65 | throw new BalancedParenthesesException(src, i + 1); 66 | } 67 | 68 | if (opened != 0) 69 | throw new BalancedParenthesesException(src, src.length()); 70 | } 71 | 72 | /** 73 | * @see jdk.internal.joptsimple.internal.Strings#repeat(char, int) 74 | */ 75 | public static String repeat(char ch, int count) { 76 | StringBuilder buffer = new StringBuilder(); 77 | 78 | for (int i = 0; i < count; ++i) 79 | buffer.append(ch); 80 | 81 | return buffer.toString(); 82 | } 83 | 84 | public static char findCharBefore(String src, int start) { 85 | try { 86 | src = src.substring(0, start).trim(); 87 | return src.isEmpty() ? '\0' : src.charAt(src.length() - 1); 88 | } catch (Exception ignore) { 89 | return '\0'; 90 | } 91 | } 92 | 93 | public static char findCharAfter(String src, int start) { 94 | try { 95 | src = src.substring(start).trim(); 96 | return src.isEmpty() ? '\0' : src.charAt(0); 97 | } catch (Exception ignore) { 98 | ignore.printStackTrace(); 99 | 100 | return '\0'; 101 | } 102 | } 103 | 104 | static int findBestIndex(String src, boolean before) { 105 | int index = before ? src.lastIndexOf(' ') : src.indexOf(' '); 106 | if (index == -1) 107 | index = before ? 0 : src.length(); 108 | else if (before) 109 | index++; 110 | 111 | for (char c : MathParser.special) { 112 | int id = before ? src.lastIndexOf(c) : src.indexOf(c); 113 | if (id != -1) 114 | index = before ? Math.max(index, id + 1) : Math.min(index, id); 115 | } 116 | 117 | return index; 118 | } 119 | 120 | /** 121 | * Calculates the similarity (a number within 0 and 1) between two strings. 122 | * https://stackoverflow.com/a/16018452/9187189 123 | */ 124 | public static double similarity(String s1, String s2) { 125 | String longer = s1, shorter = s2; 126 | if (s1.length() < s2.length()) { // longer should always have greater length 127 | longer = s2; 128 | shorter = s1; 129 | } 130 | int longerLength = longer.length(); 131 | if (longerLength == 0) { 132 | return 1.0; /* both strings are zero length */ 133 | } 134 | return (longerLength - getLevenshteinDistance(longer, shorter)) / (double) longerLength; 135 | 136 | } 137 | 138 | /** 139 | * java.org.apache.commons.lang3.StringUtils#getLevenshteinDistance(CharSequence, CharSequence) 140 | */ 141 | private static int getLevenshteinDistance(CharSequence s, CharSequence t) { 142 | int n = s.length(); 143 | int m = t.length(); 144 | 145 | if (n == 0) { 146 | return m; 147 | } 148 | if (m == 0) { 149 | return n; 150 | } 151 | 152 | if (n > m) { 153 | // swap the input strings to consume less memory 154 | final CharSequence tmp = s; 155 | s = t; 156 | t = tmp; 157 | n = m; 158 | m = t.length(); 159 | } 160 | 161 | final int[] p = new int[n + 1]; 162 | // indexes into strings s and t 163 | int i; // iterates through s 164 | int j; // iterates through t 165 | int upper_left; 166 | int upper; 167 | 168 | char t_j; // jth character of t 169 | int cost; 170 | 171 | for (i = 0; i <= n; i++) { 172 | p[i] = i; 173 | } 174 | 175 | for (j = 1; j <= m; j++) { 176 | upper_left = p[0]; 177 | t_j = t.charAt(j - 1); 178 | p[0] = j; 179 | 180 | for (i = 1; i <= n; i++) { 181 | upper = p[i]; 182 | cost = s.charAt(i - 1) == t_j ? 0 : 1; 183 | // minimum of cell to the left+1, to the top+1, diagonally left and up +cost 184 | p[i] = Math.min(Math.min(p[i - 1] + 1, p[i] + 1), upper_left + cost); 185 | upper_left = upper; 186 | } 187 | } 188 | 189 | return p[n]; 190 | } 191 | 192 | public static boolean isUnsignedInteger(String s) { 193 | if (s.isEmpty()) return false; 194 | for (int i = 0; i < s.length(); i++) { 195 | if (!Character.isDigit(s.charAt(i))) 196 | return false; 197 | } 198 | return true; 199 | } 200 | 201 | public static String realTrim(String src) { 202 | return src.trim().replaceAll("\\s+", ""); 203 | } 204 | 205 | public static boolean isIdentifier(String text) { 206 | if (text == null || text.isEmpty()) 207 | return false; 208 | if (!Character.isLetter(text.charAt(0)) && text.charAt(0) != '_') 209 | return false; 210 | for (int ix = 1; ix < text.length(); ++ix) 211 | if (!Character.isLetterOrDigit(text.charAt(ix)) && text.charAt(ix) != '_') 212 | return false; 213 | return true; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MathParser/src/com/aghajari/math/Functions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 - Amir Hossein Aghajari 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | 19 | package com.aghajari.math; 20 | 21 | import com.aghajari.math.custom.*; 22 | import com.aghajari.math.exception.MathFunctionInvalidArgumentsException; 23 | import com.aghajari.math.exception.MathInvalidParameterException; 24 | import com.aghajari.math.exception.MathParserException; 25 | 26 | import java.lang.reflect.Method; 27 | import java.util.ArrayList; 28 | import java.util.Arrays; 29 | import java.util.Collection; 30 | import java.util.List; 31 | import java.util.regex.Matcher; 32 | 33 | public class Functions { 34 | 35 | /** 36 | * All static methods in {@link Math} and {@link Functions} 37 | * that returns double and matches arguments will wrap a {@link MathFunction} 38 | * by {@link MathFunction#wrap(Method)} and import here so 39 | * the MathParser can recognize the functions as a built-in function. 40 | */ 41 | private static final List functions = new ArrayList<>(); 42 | 43 | static { 44 | functions.add(new LogFunction()); 45 | functions.add(new RadicalFunction()); 46 | try { 47 | functions.add(MathFunction.wrap(Functions.class.getMethod("integral", MathParser.class, 48 | String.class, String.class, double.class, double.class), "∫")); 49 | functions.add(MathFunction.wrap(Functions.class.getMethod("integral", MathParser.class, 50 | String.class, String.class, double.class, double.class, double.class), "∫")); 51 | functions.add(MathFunction.wrap(Functions.class.getMethod("radical", 52 | double.class), "√")); 53 | functions.add(MathFunction.wrap(Functions.class.getMethod("sigma", MathParser.class, String.class, 54 | String.class, double.class, double.class), "Σ")); 55 | functions.add(MathFunction.wrap(Functions.class.getMethod("sigma", MathParser.class, String.class, 56 | String.class, double.class, double.class, double.class), "Σ")); 57 | } catch (NoSuchMethodException e) { 58 | e.printStackTrace(); 59 | } 60 | 61 | ArrayList methods = new ArrayList<>(); 62 | methods.addAll(Arrays.asList(Functions.class.getMethods())); 63 | methods.addAll(Arrays.asList(Math.class.getMethods())); 64 | addFunctions(methods, functions); 65 | } 66 | 67 | public static List getFunctions() { 68 | return functions; 69 | } 70 | 71 | static void addFunctions(Collection methods, List functions) { 72 | Math: 73 | for (Method method : methods) { 74 | if (method.getReturnType() == double.class) { 75 | if (method.getParameterCount() != 1 || 76 | (method.getParameterTypes()[0] != Object[].class && method.getParameterTypes()[0] != Double[].class)) { 77 | int index = 0; 78 | for (Class cls : method.getParameterTypes()) { 79 | if (cls != double.class && cls != String.class && !(index == 0 && cls == MathParser.class)) 80 | continue Math; 81 | index++; 82 | } 83 | } 84 | functions.add(MathFunction.wrap(method)); 85 | } 86 | } 87 | } 88 | 89 | static MathFunction getFunction(String src, int index, String name, int count, List innerFunctions) throws MathFunctionInvalidArgumentsException { 90 | MathFunction function = null; 91 | if (innerFunctions != null) 92 | for (MathFunction func : innerFunctions) 93 | if (func.compareNames(name)) { 94 | function = func; 95 | if (func.getParameterCount() == count || func.getParameterCount() == -1) 96 | return func; 97 | } 98 | 99 | for (MathFunction func : functions) 100 | if (func.compareNames(name)) { 101 | function = func; 102 | if (func.getParameterCount() == count || func.getParameterCount() == -1) 103 | return func; 104 | } 105 | 106 | if (function != null) 107 | throw new MathFunctionInvalidArgumentsException(src, index, function, count); 108 | return null; 109 | } 110 | 111 | /* Built-in functions */ 112 | 113 | public static double log(double a, double b) { 114 | return Math.log(a) / Math.log(b); 115 | } 116 | 117 | public static double ln(double a) { 118 | return Math.log(a); 119 | } 120 | 121 | public static double radical(double a) { 122 | return Math.sqrt(a); 123 | } 124 | 125 | public static double radical(double a, double b) { 126 | if (b <= 2) 127 | return Math.sqrt(a); 128 | else if (b == 3) 129 | return Math.cbrt(a); 130 | else 131 | return Math.pow(a, 1.0 / b); 132 | } 133 | 134 | public static double max(Double... a) { 135 | double out = a[0]; 136 | for (double b : a) 137 | out = Math.max(out, b); 138 | return out; 139 | } 140 | 141 | public static double min(Double... a) { 142 | double out = a[0]; 143 | for (double b : a) 144 | out = Math.min(out, b); 145 | return out; 146 | } 147 | 148 | public static double sum(Double... a) { 149 | double out = 0; 150 | for (double b : a) 151 | out += b; 152 | return out; 153 | } 154 | 155 | public static double average(Double... a) { 156 | return avg(a); 157 | } 158 | 159 | public static double avg(Double... a) { 160 | return sum(a) / a.length; 161 | } 162 | 163 | public static double sigma(MathParser parser, String variableName, String exp, double from, double to) throws MathParserException { 164 | return sigma(parser, variableName, exp, from, to, 1.0); 165 | } 166 | 167 | public static double sigma(MathParser parser, String variableName, String exp, double from, double to, double step) throws MathParserException { 168 | if (!Utils.isIdentifier(variableName)) 169 | throw new MathInvalidParameterException("sigma(): invalid variable name (" + variableName + ")"); 170 | if (step == 0) 171 | throw new MathInvalidParameterException("sigma(): step can not be 0"); 172 | 173 | MathParser newParser = parser.clone(); 174 | newParser.addVariable(variableName, from, 0); 175 | MathParser.MathVariable variable = newParser.getVariable(variableName); 176 | Double[] ans = new Double[1]; 177 | double out = 0; 178 | if (step < 0) { 179 | double tmp = from; 180 | from = to; 181 | to = tmp; 182 | step *= -1; 183 | } 184 | 185 | for (double i = from; i <= to; i += step) { 186 | ans[0] = i; 187 | variable.answer = ans; 188 | out += newParser.parse(exp); 189 | } 190 | return out; 191 | } 192 | 193 | public static double lim(MathParser parser, String variable, String exp) throws MathParserException { 194 | return limit(parser, variable, exp); 195 | } 196 | 197 | public static double limit(MathParser parser, String variable, String exp) throws MathParserException { 198 | MathParser newParser = parser.clone(); 199 | variable = variable.replace("->", "="); 200 | if (!variable.contains("=")) 201 | throw new MathInvalidParameterException("limit(): invalid variable (" + variable + "), must be something like x->2"); 202 | 203 | String[] var = variable.split("="); 204 | String variableName = var[0]; 205 | if (!Utils.isIdentifier(variableName)) 206 | throw new MathInvalidParameterException("limit(): invalid variable name (" + variableName + ")"); 207 | 208 | double a; 209 | var[1] = Utils.realTrim(var[1]); 210 | if (var[1].equalsIgnoreCase("+inf") || var[1].equalsIgnoreCase("inf")) 211 | a = Double.POSITIVE_INFINITY; 212 | else if (var[1].equalsIgnoreCase("-inf")) 213 | a = Double.NEGATIVE_INFINITY; 214 | else 215 | a = newParser.parse(var[1]); 216 | 217 | //newParser.setRoundEnabled(false); 218 | newParser.addVariable(variableName, 0, 0); 219 | return LimitFunction.limit(newParser, newParser.getVariable(variableName), exp, a); 220 | } 221 | 222 | public static double derivative(MathParser parser, String variableName, String exp, double x) throws MathParserException { 223 | if (!Utils.isIdentifier(variableName)) 224 | throw new MathInvalidParameterException("derivative(): invalid variable name (" + variableName + ")"); 225 | 226 | MathParser newParser = parser.clone(); 227 | newParser.setRoundEnabled(false); 228 | newParser.addVariable(variableName, 0, 0); 229 | MathParser.MathVariable variable = newParser.getVariable(variableName); 230 | return Derivative.getDerivative(new FunctionWrapper(newParser, exp, variable), x); 231 | } 232 | 233 | public static double intg(MathParser parser, String variableName, String exp, double lowerLimit, double upperLimit) throws MathParserException { 234 | return integral(parser, variableName, exp, lowerLimit, upperLimit, 20); 235 | } 236 | 237 | public static double integral(MathParser parser, String variableName, String exp, double lowerLimit, double upperLimit) throws MathParserException { 238 | return integral(parser, variableName, exp, lowerLimit, upperLimit, 20); 239 | } 240 | 241 | public static double integral(MathParser parser, String variableName, String exp, double lowerLimit, double upperLimit, double glPoints) throws MathParserException { 242 | if (!Utils.isIdentifier(variableName)) 243 | throw new MathInvalidParameterException("integral(): invalid variable name (" + variableName + ")"); 244 | 245 | MathParser newParser = parser.clone(); 246 | newParser.setRoundEnabled(false); 247 | newParser.addVariable(variableName, 0, 0); 248 | MathParser.MathVariable variable = newParser.getVariable(variableName); 249 | Integration integration = new Integration(new FunctionWrapper(newParser, exp, variable), lowerLimit, upperLimit); 250 | integration.gaussQuad((int) Math.abs(glPoints)); 251 | return integration.getIntegralSum(); 252 | } 253 | 254 | public static double gcd(Double... x) { 255 | double result = 0; 256 | for (double value : x) 257 | result = gcd(value, result); 258 | return result; 259 | } 260 | 261 | private static double gcd(double a, double b) { 262 | double x = Math.abs(a); 263 | double y = Math.abs(b); 264 | while (y != 0) { 265 | double z = x % y; 266 | x = y; 267 | y = z; 268 | } 269 | return x; 270 | } 271 | 272 | public static double factorial(double x) { 273 | int number = (int) x; 274 | long result = 1; 275 | for (int factor = 2; factor <= number; factor++) { 276 | result *= factor; 277 | } 278 | return result; 279 | } 280 | 281 | // ignore Math.log as base e 282 | public static double log(double a) { 283 | return Math.log10(a); 284 | } 285 | 286 | public static double mod(double a, double b) { 287 | return a % b; 288 | } 289 | 290 | public static double nor(double a, double b) { 291 | return not(or(a, b)); 292 | } 293 | 294 | public static double not(double a) { 295 | return ~((long) a); 296 | } 297 | 298 | public static double or(double a, double b) { 299 | return (long) a | (long) b; 300 | } 301 | 302 | public static double and(double a, double b) { 303 | return (long) a & (long) b; 304 | } 305 | 306 | public static double xor(double a, double b) { 307 | return (long) a ^ (long) b; 308 | } 309 | 310 | public static double shiftLeft(double a, double b) { 311 | return (long) a << (long) b; 312 | } 313 | 314 | public static double shiftRight(double a, double b) { 315 | return (long) a >> (long) b; 316 | } 317 | 318 | public static double unsignedShiftRight(double a, double b) { 319 | return (long) a >>> (long) b; 320 | } 321 | 322 | public static double sign(double a) { 323 | return Double.compare(a, 0); 324 | } 325 | 326 | public static double IF(MathParser parser, String condition, String a, String b) throws MathParserException { 327 | condition = Utils.realTrim(condition); 328 | Matcher matcher = Utils.splitIf.matcher(condition); 329 | double ca, cb = 0; 330 | String type = "!="; 331 | 332 | if (matcher.find()) { 333 | ca = parser.parse(matcher.group(1).trim()); 334 | cb = parser.parse(matcher.group(3).trim()); 335 | type = matcher.group(2).trim(); 336 | } else 337 | ca = parser.parse(condition); 338 | 339 | boolean c; 340 | switch (type) { 341 | case "==": 342 | case "=": 343 | c = ca == cb; 344 | break; 345 | case ">=": 346 | c = ca >= cb; 347 | break; 348 | case "<=": 349 | c = ca <= cb; 350 | break; 351 | case ">": 352 | c = ca > cb; 353 | break; 354 | case "<": 355 | c = ca < cb; 356 | break; 357 | case "<>": 358 | case "!=": 359 | default: 360 | c = ca != cb; 361 | break; 362 | } 363 | return parser.parse(c ? a : b); 364 | } 365 | 366 | 367 | public static double cot(double x) { 368 | return 1.0 / Math.tan(x); 369 | } 370 | 371 | public static double arccos(double x) { 372 | return Math.acos(x); 373 | } 374 | 375 | public static double acosh(double x) { 376 | return arccosh(x); 377 | } 378 | 379 | public static double arccosh(double x) { 380 | return Math.log(x + (Math.sqrt(x * x - 1))); 381 | } 382 | 383 | public static double arcsin(double x) { 384 | return Math.asin(x); 385 | } 386 | 387 | public static double asinh(double x) { 388 | return arcsinh(x); 389 | } 390 | 391 | public static double arcsinh(double x) { 392 | return Math.log(x + (Math.sqrt(x * x + 1))); 393 | } 394 | 395 | public static double sec(double x) { 396 | return 1.0 / Math.cos(x); 397 | } 398 | 399 | public static double asec(double x) { 400 | return arcsec(x); 401 | } 402 | 403 | public static double arcsec(double x) { 404 | return Math.acos(1.0 / x); 405 | } 406 | 407 | public static double sech(double x) { 408 | return 1.0 / Math.cosh(x); 409 | } 410 | 411 | public static double asech(double x) { 412 | return arcsech(x); 413 | } 414 | 415 | public static double arcsech(double x) { 416 | return arccosh(1.0 / x); 417 | } 418 | 419 | public static double csc(double x) { 420 | return 1.0 / Math.sin(x); 421 | } 422 | 423 | public static double acsc(double x) { 424 | return arccsc(x); 425 | } 426 | 427 | public static double arccsc(double x) { 428 | return Math.asin(1.0 / x); 429 | } 430 | 431 | public static double csch(double x) { 432 | return 1.0 / Math.sinh(x); 433 | } 434 | 435 | public static double acsch(double x) { 436 | return arccsch(x); 437 | } 438 | 439 | public static double arccsch(double x) { 440 | return arcsinh(1.0 / x); 441 | } 442 | 443 | public static double arctan(double x) { 444 | return Math.atan(x); 445 | } 446 | 447 | public static double atanh(double x) { 448 | return arctanh(x); 449 | } 450 | 451 | public static double arctanh(double x) { 452 | return 0.5 * Math.log((1 + x) / (1 - x)); 453 | } 454 | 455 | public static double coth(double x) { 456 | return 1.0 / Math.tanh(x); 457 | } 458 | 459 | public static double acot(double x) { 460 | return arccot(x); 461 | } 462 | 463 | public static double arccot(double x) { 464 | return arctan(1.0 / x); 465 | } 466 | 467 | public static double acoth(double x) { 468 | return arccoth(x); 469 | } 470 | 471 | public static double arccoth(double x) { 472 | return arctanh(1.0 / x); 473 | } 474 | 475 | public static double c(double x, double y) { 476 | return factorial(x) / (factorial(y) * factorial(x - y)); 477 | } 478 | } 479 | -------------------------------------------------------------------------------- /MathParser/src/com/aghajari/math/custom/Integration.java: -------------------------------------------------------------------------------- 1 | package com.aghajari.math.custom; 2 | 3 | import com.aghajari.math.exception.MathParserException; 4 | 5 | import java.util.ArrayList; 6 | 7 | // https://github.com/gbenroscience/ParserNG/blob/master/src/main/java/math/numericalmethods/Integration.java 8 | public class Integration { 9 | 10 | private FunctionWrapper function = null; // Function to be integrated 11 | private boolean setFunction = false; // = true when Function set 12 | private double lowerLimit = Double.NaN; // Lower integration limit 13 | private double upperLimit = Double.NaN; // Upper integration limit 14 | private boolean setLimits = false; // = true when limits set 15 | 16 | private int glPoints = 0; // Number of points in the Gauss-Legendre integration 17 | private boolean setGLpoints = false; // = true when glPoints set 18 | private int nIntervals = 0; // Number of intervals in the rectangular rule integrations 19 | private boolean setIntervals = false; // = true when nIntervals set 20 | 21 | private double integralSum = 0.0D; // Sum returned by the numerical integration method 22 | private boolean setIntegration = false; // = true when integration performed 23 | 24 | // ArrayLists to hold Gauss-Legendre Coefficients saving repeated calculation 25 | private static ArrayList gaussQuadIndex = new ArrayList(); // Gauss-Legendre indices 26 | private static ArrayList gaussQuadDistArrayList = new ArrayList(); // Gauss-Legendre distances 27 | private static ArrayList gaussQuadWeightArrayList = new ArrayList();// Gauss-Legendre weights 28 | 29 | // Iterative trapezium rule 30 | private double requiredAccuracy = 0.0D; // required accuracy at which iterative trapezium is terminated 31 | private double trapeziumAccuracy = 0.0D; // actual accuracy at which iterative trapezium is terminated as instance variable 32 | private static double trapAccuracy = 0.0D; // actual accuracy at which iterative trapezium is terminated as class variable 33 | private int maxIntervals = 0; // maximum number of intervals allowed in iterative trapezium 34 | private int trapeziumIntervals = 1; // number of intervals in trapezium at which accuracy was satisfied as instance variable 35 | private static int trapIntervals = 1; // number of intervals in trapezium at which accuracy was satisfied as class variable 36 | 37 | // CONSTRUCTORS 38 | 39 | // Constructor taking function to be integrated 40 | public Integration(FunctionWrapper intFunc) { 41 | this.function = intFunc; 42 | this.setFunction = true; 43 | } 44 | 45 | // Constructor taking function to be integrated and the limits 46 | public Integration(FunctionWrapper intFunc, double lowerLimit, double upperLimit) { 47 | this.function = intFunc; 48 | this.setFunction = true; 49 | this.lowerLimit = lowerLimit; 50 | this.upperLimit = upperLimit; 51 | this.setLimits = true; 52 | } 53 | 54 | // SET METHODS 55 | 56 | // Set function to be integrated 57 | public void setFunction(FunctionWrapper intFunc) { 58 | this.function = intFunc; 59 | this.setFunction = true; 60 | } 61 | 62 | // Set limits 63 | public void setLimits(double lowerLimit, double upperLimit) { 64 | this.lowerLimit = lowerLimit; 65 | this.upperLimit = upperLimit; 66 | this.setLimits = true; 67 | } 68 | 69 | // Set lower limit 70 | public void setLowerLimit(double lowerLimit) { 71 | this.lowerLimit = lowerLimit; 72 | if (!Double.isNaN(this.upperLimit)) this.setLimits = true; 73 | } 74 | 75 | // Set upper limit 76 | public void setUpperLimit(double upperLimit) { 77 | this.upperLimit = upperLimit; 78 | if (!Double.isNaN(this.lowerLimit)) this.setLimits = true; 79 | } 80 | 81 | // Set number of points in the Gaussian Legendre integration 82 | public void setGLpoints(int nPoints) { 83 | this.glPoints = nPoints; 84 | this.setGLpoints = true; 85 | } 86 | 87 | // Set number of intervals in trapezoidal, forward or backward rectangular integration 88 | public void setNintervals(int nIntervals) { 89 | this.nIntervals = nIntervals; 90 | this.setIntervals = true; 91 | } 92 | 93 | // GET METHODS 94 | 95 | // Get the sum returned by the numerical integration 96 | public double getIntegralSum() { 97 | if (!this.setIntegration) throw new IllegalArgumentException("No integration has been performed"); 98 | return this.integralSum; 99 | } 100 | 101 | // GAUSSIAN-LEGENDRE QUADRATURE 102 | 103 | // Numerical integration using n point Gaussian-Legendre quadrature (instance method) 104 | // All parameters preset 105 | public double gaussQuad() throws MathParserException { 106 | if (!this.setGLpoints) throw new IllegalArgumentException("Number of points not set"); 107 | if (!this.setLimits) throw new IllegalArgumentException("One limit or both limits not set"); 108 | if (!this.setFunction) throw new IllegalArgumentException("No integral function has been set"); 109 | 110 | double[] gaussQuadDist = new double[glPoints]; 111 | double[] gaussQuadWeight = new double[glPoints]; 112 | double sum = 0.0D; 113 | double xplus = 0.5D * (upperLimit + lowerLimit); 114 | double xminus = 0.5D * (upperLimit - lowerLimit); 115 | double dx = 0.0D; 116 | boolean test = true; 117 | int k = -1, kn = -1; 118 | 119 | // Get Gauss-Legendre coefficients, i.e. the weights and scaled distances 120 | // Check if coefficients have been already calculated on an earlier call 121 | if (!gaussQuadIndex.isEmpty()) { 122 | for (k = 0; k < gaussQuadIndex.size(); k++) { 123 | Integer ki = gaussQuadIndex.get(k); 124 | if (ki == this.glPoints) { 125 | test = false; 126 | kn = k; 127 | } 128 | } 129 | } 130 | 131 | if (test) { 132 | // Calculate and store coefficients 133 | Integration.gaussQuadCoeff(gaussQuadDist, gaussQuadWeight, glPoints); 134 | Integration.gaussQuadIndex.add(glPoints); 135 | Integration.gaussQuadDistArrayList.add(gaussQuadDist); 136 | Integration.gaussQuadWeightArrayList.add(gaussQuadWeight); 137 | } else { 138 | // Recover coefficients 139 | gaussQuadDist = gaussQuadDistArrayList.get(kn); 140 | gaussQuadWeight = gaussQuadWeightArrayList.get(kn); 141 | } 142 | 143 | // Perform summation 144 | for (int i = 0; i < glPoints; i++) { 145 | dx = xminus * gaussQuadDist[i]; 146 | sum += gaussQuadWeight[i] * this.function.apply(xplus + dx); 147 | } 148 | this.integralSum = sum * xminus; // rescale 149 | this.setIntegration = true; // integration performed 150 | return this.integralSum; // return value 151 | } 152 | 153 | // Numerical integration using n point Gaussian-Legendre quadrature (instance method) 154 | // All parametes except the number of points in the Gauss-Legendre integration preset 155 | public double gaussQuad(int glPoints) throws MathParserException { 156 | this.glPoints = glPoints; 157 | this.setGLpoints = true; 158 | return this.gaussQuad(); 159 | } 160 | 161 | // Numerical integration using n point Gaussian-Legendre quadrature (static method) 162 | // All parametes provided 163 | public static double gaussQuad(FunctionWrapper intFunc, double lowerLimit, double upperLimit, int glPoints) throws MathParserException { 164 | Integration intgrtn = new Integration(intFunc, lowerLimit, upperLimit); 165 | return intgrtn.gaussQuad(glPoints); 166 | } 167 | 168 | // Returns the distance (gaussQuadDist) and weight coefficients (gaussQuadCoeff) 169 | // for an n point Gauss-Legendre Quadrature. 170 | // The Gauss-Legendre distances, gaussQuadDist, are scaled to -1 to 1 171 | // See Numerical Recipes for details 172 | public static void gaussQuadCoeff(double[] gaussQuadDist, double[] gaussQuadWeight, int n) { 173 | 174 | double z = 0.0D, z1 = 0.0D; 175 | double pp = 0.0D, p1 = 0.0D, p2 = 0.0D, p3 = 0.0D; 176 | 177 | double eps = 3e-11; // set required precision 178 | double x1 = -1.0D; // lower limit 179 | double x2 = 1.0D; // upper limit 180 | 181 | // Calculate roots 182 | // Roots are symmetrical - only half calculated 183 | int m = (n + 1) / 2; 184 | double xm = 0.5D * (x2 + x1); 185 | double xl = 0.5D * (x2 - x1); 186 | 187 | // Loop for each root 188 | for (int i = 1; i <= m; i++) { 189 | // Approximation of ith root 190 | z = Math.cos(Math.PI * (i - 0.25D) / (n + 0.5D)); 191 | 192 | // Refinement on above using Newton's method 193 | do { 194 | p1 = 1.0D; 195 | p2 = 0.0D; 196 | 197 | // Legendre polynomial (p1, evaluated at z, p2 is polynomial of 198 | // one order lower) recurrence relationsip 199 | for (int j = 1; j <= n; j++) { 200 | p3 = p2; 201 | p2 = p1; 202 | p1 = ((2.0D * j - 1.0D) * z * p2 - (j - 1.0D) * p3) / j; 203 | } 204 | pp = n * (z * p1 - p2) / (z * z - 1.0D); // Derivative of p1 205 | z1 = z; 206 | z = z1 - p1 / pp; // Newton's method 207 | } while (Math.abs(z - z1) > eps); 208 | 209 | gaussQuadDist[i - 1] = xm - xl * z; // Scale root to desired interval 210 | gaussQuadDist[n - i] = xm + xl * z; // Symmetric counterpart 211 | gaussQuadWeight[i - 1] = 2.0 * xl / ((1.0 - z * z) * pp * pp); // Compute weight 212 | gaussQuadWeight[n - i] = gaussQuadWeight[i - 1]; // Symmetric counterpart 213 | } 214 | } 215 | 216 | // TRAPEZIUM METHODS 217 | 218 | // Numerical integration using the trapeziodal rule (instance method) 219 | // all parameters preset 220 | public double trapezium() throws MathParserException { 221 | if (!this.setIntervals) throw new IllegalArgumentException("Number of intervals not set"); 222 | if (!this.setLimits) throw new IllegalArgumentException("One limit or both limits not set"); 223 | if (!this.setFunction) throw new IllegalArgumentException("No integral function has been set"); 224 | 225 | double y1 = 0.0D; 226 | double interval = (this.upperLimit - this.lowerLimit) / this.nIntervals; 227 | double x0 = this.lowerLimit; 228 | double x1 = this.lowerLimit + interval; 229 | double y0 = this.function.apply(x0); 230 | this.integralSum = 0.0D; 231 | 232 | for (int i = 0; i < nIntervals; i++) { 233 | // adjust last interval for rounding errors 234 | if (x1 > this.upperLimit) { 235 | x1 = this.upperLimit; 236 | interval -= (x1 - this.upperLimit); 237 | } 238 | 239 | // perform summation 240 | y1 = this.function.apply(x1); 241 | this.integralSum += 0.5D * (y0 + y1) * interval; 242 | x0 = x1; 243 | y0 = y1; 244 | x1 += interval; 245 | } 246 | this.setIntegration = true; 247 | return this.integralSum; 248 | } 249 | 250 | // Numerical integration using the trapeziodal rule (instance method) 251 | // all parameters except the number of intervals preset 252 | public double trapezium(int nIntervals) throws MathParserException { 253 | this.nIntervals = nIntervals; 254 | this.setIntervals = true; 255 | return this.trapezium(); 256 | } 257 | 258 | // Numerical integration using the trapeziodal rule (static method) 259 | // all parameters to be provided 260 | public static double trapezium(FunctionWrapper intFunc, double lowerLimit, double upperLimit, int nIntervals) throws MathParserException { 261 | Integration intgrtn = new Integration(intFunc, lowerLimit, upperLimit); 262 | return intgrtn.trapezium(nIntervals); 263 | } 264 | 265 | // Numerical integration using an iteration on the number of intervals in the trapeziodal rule 266 | // until two successive results differ by less than a predetermined accuracy times the penultimate result 267 | public double trapezium(double accuracy, int maxIntervals) throws MathParserException { 268 | this.requiredAccuracy = accuracy; 269 | this.maxIntervals = maxIntervals; 270 | this.trapeziumIntervals = 1; 271 | 272 | double summ = trapezium(this.function, this.lowerLimit, this.upperLimit, 1); 273 | double oldSumm = summ; 274 | int i = 2; 275 | for (i = 2; i <= this.maxIntervals; i++) { 276 | summ = trapezium(this.function, this.lowerLimit, this.upperLimit, i); 277 | this.trapeziumAccuracy = Math.abs((summ - oldSumm) / oldSumm); 278 | if (this.trapeziumAccuracy <= this.requiredAccuracy) break; 279 | oldSumm = summ; 280 | } 281 | 282 | if (i > this.maxIntervals) { 283 | System.out.println("accuracy criterion was not met in Integration.trapezium - current sum was returned as result."); 284 | this.trapeziumIntervals = this.maxIntervals; 285 | } else { 286 | this.trapeziumIntervals = i; 287 | } 288 | Integration.trapIntervals = this.trapeziumIntervals; 289 | Integration.trapAccuracy = this.trapeziumAccuracy; 290 | return summ; 291 | } 292 | 293 | // Numerical integration using an iteration on the number of intervals in the trapeziodal rule (static method) 294 | // until two successive results differ by less than a predtermined accuracy times the penultimate result 295 | // All parameters to be provided 296 | public static double trapezium(FunctionWrapper intFunc, double lowerLimit, double upperLimit, double accuracy, int maxIntervals) throws MathParserException { 297 | Integration intgrtn = new Integration(intFunc, lowerLimit, upperLimit); 298 | return intgrtn.trapezium(accuracy, maxIntervals); 299 | } 300 | 301 | // Get the number of intervals at which accuracy was last met in trapezium if using the instance trapezium call 302 | public int getTrapeziumIntervals() { 303 | return this.trapeziumIntervals; 304 | } 305 | 306 | // Get the number of intervals at which accuracy was last met in trapezium if using static trapezium call 307 | public static int getTrapIntervals() { 308 | return Integration.trapIntervals; 309 | } 310 | 311 | // Get the actual accuracy acheived when the iterative trapezium calls were terminated, using the instance method 312 | public double getTrapeziumAccuracy() { 313 | return this.trapeziumAccuracy; 314 | } 315 | 316 | // Get the actual accuracy acheived when the iterative trapezium calls were terminated, using the static method 317 | public static double getTrapAccuracy() { 318 | return Integration.trapAccuracy; 319 | } 320 | 321 | // BACKWARD RECTANGULAR METHODS 322 | 323 | // Numerical integration using the backward rectangular rule (instance method) 324 | // All parameters preset 325 | public double backward() throws MathParserException { 326 | if (!this.setIntervals) throw new IllegalArgumentException("Number of intervals not set"); 327 | if (!this.setLimits) throw new IllegalArgumentException("One limit or both limits not set"); 328 | if (!this.setFunction) throw new IllegalArgumentException("No integral function has been set"); 329 | 330 | double interval = (this.upperLimit - this.lowerLimit) / this.nIntervals; 331 | double x = this.lowerLimit + interval; 332 | double y; 333 | this.integralSum = 0.0D; 334 | 335 | for (int i = 0; i < this.nIntervals; i++) { 336 | // adjust last interval for rounding errors 337 | if (x > this.upperLimit) { 338 | x = this.upperLimit; 339 | interval -= (x - this.upperLimit); 340 | } 341 | 342 | // perform summation 343 | y = this.function.apply(x); 344 | this.integralSum += y * interval; 345 | x += interval; 346 | } 347 | 348 | this.setIntegration = true; 349 | return this.integralSum; 350 | } 351 | 352 | // Numerical integration using the backward rectangular rule (instance method) 353 | // all parameters except number of intervals preset 354 | public double backward(int nIntervals) throws MathParserException { 355 | this.nIntervals = nIntervals; 356 | this.setIntervals = true; 357 | return this.backward(); 358 | } 359 | 360 | // Numerical integration using the backward rectangular rule (static method) 361 | // all parameters must be provided 362 | public static double backward(FunctionWrapper intFunc, double lowerLimit, double upperLimit, int nIntervals) throws MathParserException { 363 | Integration intgrtn = new Integration(intFunc, lowerLimit, upperLimit); 364 | return intgrtn.backward(nIntervals); 365 | } 366 | 367 | // FORWARD RECTANGULAR METHODS 368 | 369 | // Numerical integration using the forward rectangular rule 370 | // all parameters preset 371 | public double forward() throws MathParserException { 372 | 373 | double interval = (this.upperLimit - this.lowerLimit) / this.nIntervals; 374 | double x = this.lowerLimit; 375 | double y; 376 | this.integralSum = 0.0D; 377 | 378 | for (int i = 0; i < this.nIntervals; i++) { 379 | // adjust last interval for rounding errors 380 | if (x > this.upperLimit) { 381 | x = this.upperLimit; 382 | interval -= (x - this.upperLimit); 383 | } 384 | 385 | // perform summation 386 | y = this.function.apply(x); 387 | this.integralSum += y * interval; 388 | x += interval; 389 | } 390 | this.setIntegration = true; 391 | return this.integralSum; 392 | } 393 | 394 | // Numerical integration using the forward rectangular rule 395 | // all parameters except number of intervals preset 396 | public double forward(int nIntervals) throws MathParserException { 397 | this.nIntervals = nIntervals; 398 | this.setIntervals = true; 399 | return this.forward(); 400 | } 401 | 402 | // Numerical integration using the forward rectangular rule (static method) 403 | // all parameters provided 404 | public static double forward(FunctionWrapper integralFunc, double lowerLimit, double upperLimit, int nIntervals) throws MathParserException { 405 | Integration intgrtn = new Integration(integralFunc, lowerLimit, upperLimit); 406 | return intgrtn.forward(nIntervals); 407 | } 408 | 409 | 410 | } -------------------------------------------------------------------------------- /MathParser/src/com/aghajari/math/MathParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 - Amir Hossein Aghajari 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | 19 | package com.aghajari.math; 20 | 21 | import com.aghajari.math.exception.*; 22 | 23 | import java.lang.reflect.Method; 24 | import java.math.BigDecimal; 25 | import java.math.RoundingMode; 26 | import java.util.*; 27 | import java.util.concurrent.atomic.AtomicInteger; 28 | import java.util.regex.Matcher; 29 | 30 | /** 31 | * A simple but powerful math parser for java. 32 | *

  33 |  *     MathParser parser = new MathParser();                            // start
  34 |  *     parser.addExpression("f(x, y) = 2(x + y)");                      // addFunction
  35 |  *     parser.addExpression("x0 = 1 + 2 ^ 2");                          // addVariable
  36 |  *     parser.addExpression("y0 = 2x0");                                // addVariable
  37 |  *     System.out.println(parser.parse("1 + 2f(x0, y0)/3"));            // 21.0
  38 |  * 
39 | *

40 | * Supports all {@link Math} functions: 41 | *


  42 |  *     System.out.println(parser.parse("cos(45°) ^ (2 * sin(pi/2))"));  // 0.5
  43 |  * 
44 | *

45 | * Supports integral, derivative, limit and sigma: 46 | *


  47 |  *     System.out.println(parser.parse("2 ∫(x, (x^3)/(x+1), 5, 10)"));  // 517.121062
  48 |  *     System.out.println(parser.parse("derivative(x, x^3, 2)"));       // 12.0
  49 |  *     System.out.println(parser.parse("lim(x->2, x^(x + 2)) / 2"));    // 8.0
  50 |  *     System.out.println(parser.parse("Σ(i, 2i^2, 1, 5)"));            // 220.0
  51 |  * 
52 | *

53 | * Supports factorial, binary, hexadecimal and octal: 54 | *


  55 |  *     System.out.println(parser.parse("5!/4"));                        // 30.0
  56 |  *     System.out.println(parser.parse("(0b100)!"));                    // 4! = 24.0
  57 |  *     System.out.println(parser.parse("log2((0xFF) + 1)"));            // log2(256) = 8.0
  58 |  *     System.out.println(parser.parse("(0o777)"));                     // 511.0
  59 |  * 
60 | * Supports IF conditions: 61 | *

  62 |  *     System.out.println(parser.parse("2 + if(2^5 >= 5!, 1, 0)"));     // 2.0
  63 |  *     parser.addExpression("gcd(x, y) = if(y == 0, x, gcd(y, x%y))");  // GCD Recursive
  64 |  *     System.out.println(parser.parse("gcd(8, 20)"));                  // 4.0
  65 |  * 
66 | * Supports array arguments: 67 | *

  68 |  *     System.out.println(parser.parse("sum(10, 20, 30, 40)"));         // 100.0
  69 |  *     System.out.println(parser.parse("gcd(8, 20, 150)"));             // 2.0
  70 |  * 
71 | *

72 | * Let's see how does MathParser work with an example: 73 | * exp = cos(x) ^ 2 + (1 + x * sin(x)) / 2 74 | *

  75 |  * - let tmp1 be cos(x) -> exp = tmp1 ^ 2 + (1 + x * sin(x)) / 2
  76 |  *  + tmp1 is ready                                                     // tmp1 = cos(x)
  77 |  * - let tmp2 be sin(x) -> exp = tmp1 ^ 2 + (1 + x * tmp2) / 2
  78 |  *  + tmp2 is ready                                                     // tmp2 = sin(x)
  79 |  * - let tmp3 be (1 + x * tmp2) -> exp = tmp1 ^ 2 + tmp3 / 2
  80 |  *  + tmp3 = 1 + x * tmp2
  81 |  *  + order tmp3 operations -> tmp3 = 1 + (x * tmp2)
  82 |  *      - let tmp4 be (x * tmp2) -> tmp3 = 1 + tmp4
  83 |  *          + tmp4 is ready                                             // tmp4 = x * tmp2
  84 |  *  + tmp3 = 1 + tmp4
  85 |  *  + tmp3 is ready                                                     // tmp3 = 1 + tmp4
  86 |  * - exp = tmp1 ^ 2 + tmp3 / 2
  87 |  *  + order exp operations -> exp = (tmp1 ^ 2) + tmp3 / 2
  88 |  *      - let tmp5 be (tmp1 ^ 2) -> exp = tmp5 + tmp3 / 2
  89 |  *          + tmp5 is ready                                             // tmp5 = tmp1 ^ 2
  90 |  *  + exp = tmp5 + tmp3 / 2
  91 |  *  + order exp operations -> exp = tmp5 + (tmp3 / 2)
  92 |  *      - let tmp6 be (tmp3 / 2) -> exp = tmp5 + tmp6
  93 |  *          + tmp6 is ready                                             // tmp6 = tmp3 / 2
  94 |  *  + exp = tmp5 + tmp6
  95 |  *  + exp is ready
  96 |  * 
97 | *

98 | * Here is the list of inner variables after simplification: 99 | * tmp1 = cos(x) 100 | * tmp2 = sin(x) 101 | * tmp4 = x * tmp2 102 | * tmp3 = 1 + tmp4 103 | * tmp5 = tmp1 ^ 2 104 | * tmp6 = tmp3 / 2 105 | * exp = tmp5 + tmp6 106 | *

107 | * As you can see, all variables contain only a very small part of 108 | * the original expression and all the operations in variables 109 | * have the same priority, So makes the calculation very easy. 110 | * {@link SimpleParser}, In order of the above list, starts calculating 111 | * the variables separately to reach the exp which is the final answer. 112 | * 113 | * @author AmirHossein Aghajari 114 | * @version 1.0.0 115 | */ 116 | public class MathParser implements Cloneable { 117 | 118 | /** 119 | * {@link #setRoundEnabled(boolean)} 120 | */ 121 | private boolean roundEnabled = true; 122 | private int roundScale = 6; 123 | 124 | private final ArrayList variables = new ArrayList<>(); 125 | private final ArrayList functions = new ArrayList<>(); 126 | private final ArrayList innerVariables = new ArrayList<>(); 127 | private final AtomicInteger tmpGenerator = new AtomicInteger(0); 128 | 129 | /* The order of operations */ 130 | static final char[] order = {'%', '^', '*', '/', '+', '-'}; 131 | 132 | /* The priority of operations connected to order[] */ 133 | static final int[] orderPriority = {3, 2, 1, 1, 0, 0}; 134 | 135 | /* Special characters will end name of variables or functions */ 136 | static final char[] special = {'%', '^', '*', '/', '+', '-', ',', '(', ')', '!', '=', '<', '>'}; 137 | 138 | /* Basic math operations that parser supports */ 139 | static final HashMap operations = new HashMap<>(); 140 | 141 | static { 142 | operations.put('^', Math::pow); 143 | operations.put('*', (a, b) -> a * b); 144 | operations.put('/', (a, b) -> a / b); 145 | operations.put('+', Double::sum); 146 | operations.put('-', (a, b) -> a - b); 147 | operations.put('%', (a, b) -> a % b); 148 | } 149 | 150 | private MathParser() { 151 | } 152 | 153 | public static MathParser create() { 154 | return new MathParser(); 155 | } 156 | 157 | /** 158 | * Parses and calculates the expression 159 | * 160 | * @param expression the expression to parse and calculate 161 | * @throws MathParserException If something went wrong 162 | * @throws BalancedParenthesesException If parentheses aren't balanced 163 | * @throws MathInvalidParameterException If parameter of the function is invalid 164 | * @throws MathFunctionInvalidArgumentsException If the number of arguments is unexpected 165 | * @throws MathFunctionNotFoundException If couldn't find the function 166 | * @throws MathVariableNotFoundException If couldn't find the variable 167 | */ 168 | public double parse(String expression) throws MathParserException { 169 | String org = expression; 170 | validate(expression); 171 | try { 172 | initDefaultVariables(); 173 | expression = firstSimplify(expression); 174 | calculateVariables(); 175 | 176 | return round(calculate(expression, org)); 177 | } catch (Exception e) { 178 | if (e instanceof MathParserException) 179 | throw e; 180 | else if (e.getCause() instanceof MathParserException) 181 | throw (MathParserException) e.getCause(); 182 | else 183 | throw new MathParserException(org, e.getMessage(), e); 184 | } 185 | } 186 | 187 | /** 188 | * validate syntax 189 | */ 190 | private void validate(String src) throws MathParserException { 191 | Utils.validateBalancedParentheses(src); 192 | } 193 | 194 | /** 195 | * Simplify syntax for common functions 196 | */ 197 | private String firstSimplify(String expression) { 198 | expression = Utils.realTrim(expression); 199 | expression = fixDegrees(expression); 200 | expression = fixFactorial(expression); 201 | expression = fixDoubleType(expression); 202 | expression = fixBinary(expression); 203 | expression = fixHexadecimal(expression); 204 | expression = fixOctal(expression); 205 | return expression; 206 | } 207 | 208 | /** 209 | * Makes degrees readable for Math trigonometry functions 210 | * x° => toRadians(x) 211 | */ 212 | private String fixDegrees(String src) { 213 | char deg = '°'; 214 | // 24deg | 24degrees => 24° 215 | // 24rad | 24 radian | 24radians => 24 216 | if (getVariable("degrees") == null) 217 | src = src.replaceAll("(?<=\\d)degrees(?=[^\\w]|$)", String.valueOf(deg)); 218 | if (getVariable("deg") == null) 219 | src = src.replaceAll("(?<=\\d)deg(?=[^\\w]|$)", String.valueOf(deg)); 220 | if (getVariable("radians") == null) 221 | src = src.replaceAll("(?<=\\d)radians(?=[^\\w]|$)", ""); 222 | if (getVariable("radian") == null) 223 | src = src.replaceAll("(?<=\\d)radian(?=[^\\w]|$)", ""); 224 | if (getVariable("rad") == null) 225 | src = src.replaceAll("(?<=\\d)rad(?=[^\\w]|$)", ""); 226 | 227 | return fix(src, "toRadians", deg); 228 | } 229 | 230 | /** 231 | * Makes factorial readable 232 | * x! => factorial(x) 233 | */ 234 | private String fixFactorial(String src) { 235 | return fix(src, "factorial", '!'); 236 | } 237 | 238 | private String fix(String src, String function, char c) { 239 | int index; 240 | while ((index = src.indexOf(c)) != -1) { 241 | boolean applyToFirst = true, ph = false; 242 | int count = 0; 243 | 244 | for (int i = index - 1; i >= 0; i--) { 245 | if (i == index - 1 && src.charAt(i) == ')') { 246 | ph = true; 247 | count++; 248 | continue; 249 | } 250 | if (ph) { 251 | if (src.charAt(i) == ')') { 252 | count++; 253 | } else if (src.charAt(i) == '(') { 254 | count--; 255 | } 256 | if (count != 0) 257 | continue; 258 | ph = false; 259 | } 260 | if (!isSpecialSign(src.charAt(i))) 261 | continue; 262 | 263 | String sign = isSpecialSign(src.charAt(i)) ? "" : "*"; 264 | i++; 265 | src = src.substring(0, i) + sign + function + "(" + src.substring(i, index) + ")" + src.substring(index + 1); 266 | applyToFirst = false; 267 | break; 268 | } 269 | if (applyToFirst) 270 | src = function + "(" + src.substring(0, index) + ")" + src.substring(index + 1); 271 | } 272 | return src; 273 | } 274 | 275 | /** 276 | * (2e+2) -> (200.0) 277 | */ 278 | private String fixDoubleType(String src) { 279 | Matcher matcher = Utils.doubleType.matcher(src); 280 | if (matcher.find()) { 281 | String a = matcher.group(1); 282 | String b = a; 283 | String e = matcher.group(2); 284 | boolean ignoreE = a.endsWith("-") || a.endsWith("+"); 285 | if (!ignoreE) { 286 | try { 287 | b = String.valueOf(Double.parseDouble(a)); 288 | } catch (Exception ignore) { 289 | ignoreE = true; 290 | } 291 | } 292 | if (ignoreE) { 293 | b = a.substring(0, a.indexOf(e)) + " " + a.substring(a.indexOf(e)); 294 | } 295 | src = src.substring(0, matcher.start() + 1) + b + src.substring(matcher.end() - 1); 296 | return fixDoubleType(src.trim()); 297 | } 298 | return src.trim(); 299 | } 300 | 301 | /** 302 | * (0b010) -> (2) 303 | */ 304 | private String fixBinary(String src) { 305 | Matcher matcher = Utils.binary.matcher(src); 306 | if (matcher.find()) { 307 | String a = matcher.group(0); 308 | long value = Long.parseLong(a.substring(3, a.length() - 1), 2); 309 | src = src.substring(0, matcher.start() + 1) + value + src.substring(matcher.end() - 1); 310 | return fixBinary(src); 311 | } 312 | return src; 313 | } 314 | 315 | /** 316 | * (0x0FF) -> (255) 317 | */ 318 | private String fixHexadecimal(String src) { 319 | Matcher matcher = Utils.hexadecimal.matcher(src); 320 | if (matcher.find()) { 321 | String a = matcher.group(0); 322 | long value = Long.parseLong(a.substring(3, a.length() - 1), 16); 323 | src = src.substring(0, matcher.start() + 1) + value + src.substring(matcher.end() - 1); 324 | return fixHexadecimal(src); 325 | } 326 | return src; 327 | } 328 | 329 | /** 330 | * (0o027) -> (23) 331 | */ 332 | private String fixOctal(String src) { 333 | Matcher matcher = Utils.octal.matcher(src); 334 | if (matcher.find()) { 335 | String a = matcher.group(0); 336 | long value = Long.parseLong(a.substring(3, a.length() - 1), 8); 337 | src = src.substring(0, matcher.start() + 1) + value + src.substring(matcher.end() - 1); 338 | return fixOctal(src); 339 | } 340 | return src; 341 | } 342 | 343 | /** 344 | * Adds default constants {pi, e} 345 | */ 346 | private void initDefaultVariables() { 347 | addConst("e", Math.E); 348 | addConst("Π", Math.PI); 349 | addConst("π", Math.PI); 350 | addConst("pi", Math.PI); 351 | } 352 | 353 | /** 354 | * Calculate the answer of variables 355 | */ 356 | private void calculateVariables() throws MathParserException { 357 | for (MathVariable variable : variables) { 358 | if (!variable.hasFound) { 359 | validate(variable.expression); 360 | variable.answer = new Double[]{round(calculate(firstSimplify(variable.expression), variable.original))}; 361 | variable.hasFound = true; 362 | //System.out.println(variable.name + " = " + variable.getAnswer()); 363 | } 364 | } 365 | } 366 | 367 | /** 368 | * Rounds the value if {@link #roundEnabled} is true. 369 | * 370 | * @see #setRoundEnabled(boolean) 371 | * @see #isRoundEnabled() 372 | */ 373 | private double round(double a) { 374 | if (!roundEnabled || Double.isInfinite(a) || Double.isNaN(a)) 375 | return a; 376 | return BigDecimal.valueOf(a).setScale(roundScale, RoundingMode.HALF_UP).doubleValue(); 377 | } 378 | 379 | /** 380 | * Calculates and returns the value of an expression 381 | * 382 | * @param exp the simplified expression 383 | * @param main the original expression 384 | */ 385 | private double calculate(String exp, String main) throws MathParserException { 386 | return calculate(exp, main, false); 387 | } 388 | 389 | /** 390 | * Calculates and returns the value of an expression. 391 | *

392 | * This function is not going to check the priorities of {@link #operations}, 393 | * but tries to simplify the expression as much as possible and calculate 394 | * the parentheses from the innermost order and put them in {@link #innerVariables}, 395 | * If the parentheses are related to function calls, adds a {@link MathVariable#function} 396 | * to the temp variable to identify the function. So eventually a simple expression without 397 | * parentheses and functions will be created. 398 | *

399 | * For example: 400 | * 2 + (2x + abs(x)) / 2 401 | *

402 | * - let tmp1 be abs(x) -> 2 + (2x + tmp1) / 2 403 | * - let tmp2 be (2x + tmp1) -> 2 + tmp2 / 2 404 | * - calls {@link #orderAgain} to order operations 405 | * 406 | * @param src the simplified expression 407 | * @param main the user-entered expression 408 | * @param fromExpValue {@link ExpValue} is an expression that contained an unknown variable in 409 | * the first step that was calculated. If fromExpValue is true, it means that 410 | * it is trying to calculate it again in the hope that the unknown variable is 411 | * found. If it is found, returns the final answer, otherwise will throw 412 | * {@link MathVariableNotFoundException}. Basically, functions that will add a 413 | * dynamic variable, such as sigma and integral, need this property to update 414 | * and apply the variable in the second step. 415 | */ 416 | private double calculate(String src, String main, boolean fromExpValue) throws MathParserException { 417 | src = Utils.realTrim(src); 418 | if (src.startsWith("(") && src.endsWith(")") && !src.substring(1).contains("(")) 419 | src = src.substring(1, src.length() - 1); 420 | 421 | while (src.contains("(") || src.contains(")")) { 422 | Matcher matcher = Utils.innermostParentheses.matcher(src); 423 | if (matcher.find()) { 424 | String name = generateTmpName(), exp = matcher.group(0).trim(); 425 | exp = exp.substring(1, exp.length() - 1); 426 | MathVariable var; 427 | 428 | Matcher matcher2 = Utils.splitParameters.matcher(exp); 429 | ArrayList answers = new ArrayList<>(); 430 | int startParameter = 0; 431 | while (matcher2.find()) { 432 | answers.add(exp.substring(startParameter, startParameter = matcher2.end() - 1)); 433 | startParameter++; 434 | } 435 | if (answers.isEmpty()) 436 | answers.add(exp); 437 | else 438 | answers.add(exp.substring(startParameter)); 439 | 440 | MathFunction function = null; 441 | int start = matcher.start(); 442 | String signBefore = (start == 0 || isSpecialSign(Utils.findCharBefore(src, matcher.start()))) ? "" : "*"; 443 | String signAfter = (matcher.end() == src.length() || isSpecialSign(Utils.findCharAfter(src, matcher.end()))) ? "" : "*"; 444 | 445 | if (start > 0) { 446 | String before = src.substring(0, start); 447 | String wordBefore = before.substring(Utils.findBestIndex(before, true)).trim(); 448 | while (wordBefore.length() > 0 && Character.isDigit(wordBefore.charAt(0))) 449 | wordBefore = wordBefore.substring(1); 450 | 451 | if (wordBefore.length() > 0) { 452 | function = Functions.getFunction(main, main.indexOf(wordBefore), 453 | wordBefore, answers.size(), functions); 454 | if (function != null) { 455 | signBefore = ""; 456 | start -= wordBefore.length(); 457 | } else if (answers.size() > 1) 458 | throw new MathFunctionNotFoundException(main, main.indexOf(wordBefore), wordBefore); 459 | } 460 | } 461 | if (answers.size() > 1 && function == null) 462 | throw new MathFunctionNotFoundException(main, -1, null, matcher.group(0).trim()); 463 | 464 | List answers2 = new ArrayList<>(); 465 | if (function == null) { 466 | if (!fromExpValue) 467 | try { 468 | answers2.add(calculate(answers.get(0), main)); 469 | } catch (Exception e) { 470 | answers2.add(new ExpValue(answers.get(0), main)); 471 | } 472 | else 473 | answers2.add(calculate(answers.get(0), main)); 474 | } else { 475 | for (int i = 0; i < answers.size(); i++) { 476 | if (function.isSpecialParameter(i)) 477 | answers2.add(Utils.realTrim(answers.get(i))); 478 | else { 479 | if (!fromExpValue) 480 | try { 481 | answers2.add(calculate(answers.get(i), main)); 482 | } catch (Exception e) { 483 | answers2.add(new ExpValue(answers.get(i), main)); 484 | } 485 | else 486 | answers2.add(calculate(answers.get(i), main)); 487 | } 488 | } 489 | } 490 | var = new MathVariable(name, answers2, exp, main, function); 491 | if (function != null) 492 | function.attachToParser(this); 493 | innerVariables.add(var); 494 | 495 | src = src.substring(0, start) + signBefore + name + signAfter + src.substring(matcher.end()); 496 | } else 497 | break; 498 | } 499 | 500 | return orderAgain(src, main); 501 | } 502 | 503 | /** 504 | * {@link #calculate(String, String, boolean)} has simplified the expression 505 | * as much as it's possible, It is time to set priorities of {@link #operations}. 506 | * This function first checks if all operations are in the same priority. 507 | * If yes, the phrase is the simplest case possible and the final calculations 508 | * should be done by the {@link SimpleParser}. If not, it parentheses the higher 509 | * priority operation and sends it back to the {@link #calculate} 510 | * for simplification. This is done so that in the end a linear expression remains which 511 | * all operations have the same priority. 512 | *

513 | * For example: 514 | * 2 + tmp2 / 2 515 | *

516 | *

 517 |      * + parentheses the division operation -> 2 + (tmp2 / 2)
 518 |      * + sends it back to {@link #calculate}
 519 |      *  - let tmp3 be (tmp2 / 2) -> 2 + tmp3
 520 |      * + final calculation by {@link SimpleParser}
 521 |      * 
522 | *

523 | * This cycle will also apply to all variables 524 | */ 525 | private double orderAgain(String src, String main) throws MathParserException { 526 | boolean allInSamePriority = true; 527 | int highestPriority = -1; 528 | for (int i = 0; i < order.length; i++) { 529 | if (src.contains(String.valueOf(order[i]))) { 530 | if (highestPriority != -1 && orderPriority[i] != highestPriority) { 531 | allInSamePriority = false; 532 | break; // the first ones always have higher priority 533 | } 534 | highestPriority = Math.max(highestPriority, orderPriority[i]); 535 | } 536 | } 537 | 538 | if (!allInSamePriority) { 539 | int ind = -1; 540 | char op = '+'; 541 | for (int i = 0; i < order.length; i++) { 542 | if (orderPriority[i] == highestPriority) { 543 | int ind2 = src.indexOf(order[i]); 544 | if (ind2 != -1 && (ind == -1 || ind > ind2)) { 545 | ind = ind2; 546 | op = order[i]; 547 | } 548 | } 549 | } 550 | 551 | if (ind != -1) { 552 | int index; 553 | String wordAfter = src.substring(ind + 1, ind + 1 + Utils.findBestIndex(src.substring(ind + 1), false)); 554 | String wordBefore = src.substring(index = Utils.findBestIndex(src.substring(0, ind), true), ind); 555 | 556 | src = src.substring(0, index) + "(" + wordBefore + op + wordAfter + ")" 557 | + src.substring(ind + wordAfter.length() + 1); 558 | } 559 | } 560 | 561 | if (src.contains("(") || src.contains(")")) 562 | return calculate(src, main); 563 | 564 | return new SimpleParser(src, main).parse(); 565 | } 566 | 567 | /** 568 | * Generates a new unique name for temp variables in format of __tmp{index} 569 | */ 570 | private String generateTmpName() { 571 | String name; 572 | do { 573 | name = "__tmp" + tmpGenerator.incrementAndGet(); 574 | } while (getVariable(name) != null); 575 | return name; 576 | } 577 | 578 | /** 579 | * Adds support of an expression to this {@link MathParser}, 580 | * the expression must contains {@code =}, 581 | * There are two types of phrases. Variable and Function 582 | * Variables are in the form of {@code name = expression} 583 | * Functions are in the form of {@code name(x, y, ..) = expression} 584 | *

585 | * For example: 586 | *


 587 |      *      MathParser parser = MathParser.create();
 588 |      *      parser.addExpression("f(x, y) = x ^ y");
 589 |      *      parser.addExpression("x0 = 2");
 590 |      *      parser.addExpression("y0 = 4");
 591 |      *      System.out.println(parser.parse("f(x0, y0)")); // 2^4 = 16.0
 592 |      * 
593 | */ 594 | public void addExpression(String exp) { 595 | String[] var = {exp.substring(0, exp.indexOf('=')), exp.substring(exp.indexOf('=') + 1)}; 596 | if (var[0].contains("(")) 597 | addFunction(MathFunction.wrap(exp)); 598 | else 599 | addVariable(var[0].trim(), var[1].trim()); 600 | } 601 | 602 | /** 603 | * Adds a new variable to this {@link MathParser} 604 | * 605 | * @param name variable name 606 | * @param expression expression of variable 607 | * @see #addVariable(String, double) 608 | * @see #addExpression(String) 609 | */ 610 | public void addVariable(String name, String expression) { 611 | removeVariable(name); 612 | variables.add(new MathVariable(name, expression)); 613 | } 614 | 615 | /** 616 | * Adds a new variable at specific index to this {@link MathParser} 617 | * 618 | * @param name variable name 619 | * @param expression expression of variable 620 | * @param index index of variable in {@link #getVariables()} list 621 | * @see #addVariable(String, double, int) 622 | * @see #addExpression(String) 623 | */ 624 | public void addVariable(String name, String expression, int index) { 625 | removeVariable(name); 626 | variables.add(index, new MathVariable(name, expression)); 627 | } 628 | 629 | /** 630 | * Adds a new variable to this {@link MathParser} 631 | * 632 | * @param name variable name 633 | * @param value value of variable 634 | * @see #addVariable(String, String) 635 | */ 636 | public void addVariable(String name, double value) { 637 | removeVariable(name); 638 | variables.add(new MathVariable(name, value)); 639 | } 640 | 641 | /** 642 | * Adds a new variable at specific index to this {@link MathParser} 643 | * 644 | * @param name variable name 645 | * @param value value of variable 646 | * @param index index of variable in {@link #getVariables()} list 647 | * @see #addVariable(String, String, int) 648 | */ 649 | public void addVariable(String name, double value, int index) { 650 | removeVariable(name); 651 | variables.add(index, new MathVariable(name, value)); 652 | } 653 | 654 | /** 655 | * Adds a new function to this {@link MathParser} 656 | * 657 | * @see #addExpression(String) 658 | */ 659 | public void addFunction(MathFunction function) { 660 | functions.add(function); 661 | } 662 | 663 | /** 664 | * Adds set of functions to this {@link MathParser} 665 | * based on {@link Method} 666 | * Only the methods that return double are supported. 667 | * The method can get {@link MathParser} only as the first argument. 668 | * The method can get String only if {@link MathFunction#isSpecialParameter(int)} 669 | * returns true for the index of argument. 670 | * The method can get array of double in form of Object... 671 | * The method must be static and have at least one argument 672 | * 673 | * @see #addExpression(String) 674 | */ 675 | public void addFunctions(Collection methods) { 676 | Functions.addFunctions(methods, functions); 677 | } 678 | 679 | /** 680 | * @see #addFunctions(Collection) 681 | */ 682 | public void addFunctions(Class cls) { 683 | Functions.addFunctions(Arrays.asList(cls.getMethods()), functions); 684 | } 685 | 686 | /** 687 | * Removes the specified variable from {@link #getVariables()} 688 | */ 689 | public void removeVariable(String name) { 690 | name = name.trim().toLowerCase(); 691 | for (MathVariable variable : variables) 692 | if (variable.name.equals(name)) { 693 | variables.remove(variable); 694 | return; 695 | } 696 | } 697 | 698 | /** 699 | * Clears all imported variables 700 | */ 701 | public void clearVariables() { 702 | variables.clear(); 703 | } 704 | 705 | /** 706 | * Clears all imported functions 707 | */ 708 | public void clearFunctions() { 709 | functions.clear(); 710 | } 711 | 712 | /** 713 | * Returns {@code true} if {@link #getVariables()} contains the specified variable. 714 | */ 715 | public boolean containsVariable(String name) { 716 | name = name.trim().toLowerCase(); 717 | for (MathVariable variable : variables) 718 | if (variable.name.equals(name)) 719 | return true; 720 | return false; 721 | } 722 | 723 | /** 724 | * Returns the list of imported variables 725 | */ 726 | public ArrayList getVariables() { 727 | return variables; 728 | } 729 | 730 | /** 731 | * Returns the list of imported functions 732 | */ 733 | public ArrayList getFunctions() { 734 | return functions; 735 | } 736 | 737 | /** 738 | * Returns the list of const and temp variables 739 | */ 740 | ArrayList getInnerVariables() { 741 | return innerVariables; 742 | } 743 | 744 | /** 745 | * Adds a const variable to {@link #innerVariables}; 746 | */ 747 | private void addConst(String name, double value) { 748 | // variables can update the default constants 749 | //removeVariable(name); 750 | innerVariables.add(new MathVariable(name, value)); 751 | } 752 | 753 | /** 754 | * Returns the instance of {@link MathVariable} if the variable exists 755 | * on {@link #getVariables()} or {@link #getInnerVariables()}, Null otherwise. 756 | */ 757 | MathVariable getVariable(String name) { 758 | name = name.trim().toLowerCase(); 759 | for (MathVariable variable : variables) 760 | if (variable.name.equals(name)) 761 | return variable; 762 | 763 | for (MathVariable variable : innerVariables) 764 | if (variable.name.equals(name)) 765 | return variable; 766 | return null; 767 | } 768 | 769 | /** 770 | * Because of the way floating-point numbers are stored as binary, 771 | * it is better to round them a bit 772 | *

773 | * Some functions like {@link Functions#derivative(MathParser, String, String, double)}, 774 | * won't work with rounded value so you have to disable rounding option for this function calls. 775 | * (The function itself will do this) 776 | *

777 | * The round option is enabled by default 778 | */ 779 | public void setRoundEnabled(boolean roundEnabled) { 780 | this.roundEnabled = roundEnabled; 781 | } 782 | 783 | /** 784 | * Checks whether the rounding option is enabled for this {@link MathParser} or not 785 | * 786 | * @return True if rounding option is enabled, False otherwise 787 | */ 788 | public boolean isRoundEnabled() { 789 | return roundEnabled; 790 | } 791 | 792 | /** 793 | * Sets scale of decimal 794 | * 795 | * @see #setRoundEnabled(boolean) 796 | * @see #getRoundScale() 797 | */ 798 | public void setRoundScale(int roundScale) { 799 | this.roundScale = roundScale; 800 | } 801 | 802 | /** 803 | * @return the scale of decimal 804 | * @see #setRoundScale(int) 805 | * @see #setRoundEnabled(boolean) 806 | */ 807 | public int getRoundScale() { 808 | return roundScale; 809 | } 810 | 811 | /** 812 | * @return True if src exists on supported operations, False otherwise 813 | */ 814 | static boolean isMathSign(String src) { 815 | src = src.trim(); 816 | if (src.isEmpty()) 817 | return false; 818 | return isMathSign(src.charAt(0)); 819 | } 820 | 821 | /** 822 | * @return True if src exists on supported operations, False otherwise 823 | */ 824 | static boolean isMathSign(char src) { 825 | return operations.containsKey(src); 826 | } 827 | 828 | /** 829 | * @return True if src is an special characters, False otherwise 830 | */ 831 | private static boolean isSpecialSign(char src) { 832 | for (char c : special) 833 | if (c == src) 834 | return true; 835 | return false; 836 | } 837 | 838 | /** 839 | * Creates a new copy of this {@link MathParser} 840 | * 841 | * @return a shallow copy of this {@link MathParser} 842 | * @noinspection MethodDoesntCallSuperMethod 843 | */ 844 | public MathParser clone() { 845 | MathParser newParser = MathParser.create(); 846 | newParser.getVariables().addAll(variables); 847 | newParser.getInnerVariables().addAll(innerVariables); 848 | newParser.getFunctions().addAll(functions); 849 | newParser.tmpGenerator.set(tmpGenerator.get()); 850 | newParser.roundEnabled = roundEnabled; 851 | newParser.roundScale = roundScale; 852 | return newParser; 853 | } 854 | 855 | /** 856 | * Resets temp variables 857 | * 858 | * @param deep True to clear all imported functions and variables, False otherwise 859 | */ 860 | public void reset(boolean deep) { 861 | if (deep) { 862 | variables.clear(); 863 | functions.clear(); 864 | } 865 | innerVariables.clear(); 866 | tmpGenerator.set(0); 867 | } 868 | 869 | /** 870 | * @see #operations 871 | */ 872 | private interface MathOperation { 873 | double apply(double a, double b); 874 | } 875 | 876 | public static class MathVariable { 877 | final String name, expression, original; 878 | Object[] answer = {0.0}; 879 | boolean hasFound = false; 880 | MathFunction function = null; 881 | 882 | MathVariable(String name, String expression) { 883 | this.name = name.trim().toLowerCase(); 884 | this.expression = expression.trim().toLowerCase(); 885 | this.original = this.name + " = " + this.expression; 886 | } 887 | 888 | MathVariable(String name, double value) { 889 | this.name = name.trim().toLowerCase(); 890 | this.expression = String.valueOf(value); 891 | this.original = this.name + " = " + this.expression; 892 | this.answer = new Double[]{value}; 893 | this.hasFound = true; 894 | } 895 | 896 | MathVariable(String name, List value, String expression, String original, MathFunction function) { 897 | this.name = name.trim().toLowerCase(); 898 | this.expression = expression; 899 | this.original = original; 900 | this.answer = value.toArray(); 901 | this.hasFound = true; 902 | this.function = function; 903 | } 904 | 905 | public double getAnswer() throws MathParserException { 906 | if (function == null) 907 | return (double) answer[0]; 908 | else { 909 | return function.calculate(answer); 910 | } 911 | } 912 | 913 | public void updateAnswer(Object... answers) { 914 | this.answer = answers; 915 | } 916 | 917 | public double getAnswer(MathParser parser) throws MathParserException { 918 | if (function == null) 919 | return answer[0] instanceof ExpValue ? ((ExpValue) answer[0]).calculate(parser) : (double) answer[0]; 920 | else { 921 | function.attachToParser(parser); 922 | boolean allAreDouble = true; 923 | List ans = new ArrayList<>(); 924 | for (Object o : answer) { 925 | if (o instanceof ExpValue) 926 | ans.add(((ExpValue) o).calculate(parser)); 927 | else { 928 | ans.add(o); 929 | if (!(o instanceof Double)) 930 | allAreDouble = false; 931 | } 932 | } 933 | if (allAreDouble) 934 | //noinspection SuspiciousToArrayCall,RedundantCast 935 | return function.calculate((Object[]) ans.toArray(new Double[0])); 936 | else 937 | return function.calculate(ans.toArray()); 938 | } 939 | } 940 | 941 | public String getName() { 942 | return name; 943 | } 944 | 945 | public String getExpression() { 946 | return expression; 947 | } 948 | 949 | public void setFunction(MathFunction function) { 950 | this.function = function; 951 | } 952 | 953 | public MathFunction getFunction() { 954 | return function; 955 | } 956 | 957 | @Override 958 | public String toString() { 959 | return getName() + " = " + 960 | (function != null ? function.name() + "(" : "") 961 | + getExpression() + 962 | (function != null ? ")" : ""); 963 | } 964 | } 965 | 966 | /** 967 | * A new ExpValue means parser once tried to calculate {@link ExpValue#expression} 968 | * but something went wrong! (usually a variable is missed), So 969 | * Parser tries to store the expression in a {@link ExpValue} and calculate it again 970 | * when needed by {@link MathParser#calculate} and will set fromExpValue true, 971 | * if something went wrong again it will throw an exception this time so 972 | * it won't go on a loop, and if everything was ok, it will use the calculated value. 973 | *

974 | * Some functions like integral or sigma will add a dynamic variable during parsing, 975 | * this type of variables will be unknown for parser at the first time. 976 | * that's why we use {@link ExpValue}, to make sure there won't be any dynamic variable. 977 | */ 978 | private static class ExpValue { 979 | final String expression, original; 980 | 981 | ExpValue(String expression, String original) { 982 | this.expression = expression; 983 | this.original = original; 984 | } 985 | 986 | public double calculate(MathParser parser) throws MathParserException { 987 | return parser.calculate(expression, original, true); 988 | } 989 | } 990 | 991 | /** 992 | * A simple parser to parse a linear expression, 993 | * Doesn't need to check priority of operations 994 | */ 995 | class SimpleParser { 996 | final String original; 997 | String src; 998 | MathOperation currentOperation = operations.get('+'); 999 | Double a = 0.0, b = null; 1000 | 1001 | SimpleParser(String src, String original) { 1002 | this.src = src; 1003 | this.original = original; 1004 | } 1005 | 1006 | double parse() throws MathParserException { 1007 | trim(); 1008 | 1009 | //System.out.println(src); 1010 | if (b != null) { 1011 | a = currentOperation.apply(a, b); 1012 | b = null; 1013 | } 1014 | 1015 | if (src.isEmpty()) 1016 | return a; 1017 | 1018 | if (isMathSign(src)) 1019 | currentOperation = operations.get(get()); 1020 | else { 1021 | String word = nextWord(); 1022 | try { 1023 | b = Double.parseDouble(word); 1024 | } catch (Exception e) { 1025 | b = tryParseWord(word); 1026 | } 1027 | } 1028 | return parse(); 1029 | } 1030 | 1031 | /** 1032 | * so there is a string which isn't a number, 1033 | * this function will try to parse this word and check for variables 1034 | * "xy" => can be two variables (x or y) or just a variable named xy, 1035 | * figures out what would xy mean. (int this case, being one variable 1036 | * is the higher priority) 1037 | */ 1038 | private double tryParseWord(String word) throws MathParserException { 1039 | MathVariable variable = getVariable(word); 1040 | if (variable == null) { 1041 | if (Character.isDigit(word.charAt(0))) { 1042 | StringBuilder numberFirst = new StringBuilder(); 1043 | for (int i = 0; i < word.length(); i++) { 1044 | char c = word.charAt(i); 1045 | if (Character.isDigit(c) || c == '.') 1046 | numberFirst.append(c); 1047 | else 1048 | break; 1049 | } 1050 | 1051 | double coefficient = Double.parseDouble(numberFirst.toString()); 1052 | word = word.substring(numberFirst.length()); 1053 | variable = getVariable(word); 1054 | if (variable == null) 1055 | return trySplitVariables(word, coefficient); 1056 | else 1057 | return coefficient * variable.getAnswer(MathParser.this); 1058 | } else { 1059 | return trySplitVariables(word, 1.0); 1060 | } 1061 | } else 1062 | return variable.getAnswer(MathParser.this); 1063 | } 1064 | 1065 | private double trySplitVariables(String word, double coefficient) throws MathParserException { 1066 | StringBuilder name = new StringBuilder(); 1067 | int indexOfStart = 0; 1068 | for (int i = 0; i < word.length(); i++) { 1069 | char c = word.charAt(i); 1070 | if (i + 1 < word.length() && Character.isDigit(word.charAt(i + 1))) { 1071 | name.append(c); 1072 | continue; 1073 | } 1074 | 1075 | MathVariable variable = getVariable(name.toString() + c); 1076 | if (variable == null) { 1077 | name.append(c); 1078 | } else { 1079 | indexOfStart = i + 1; 1080 | name = new StringBuilder(); 1081 | coefficient *= variable.getAnswer(MathParser.this); 1082 | } 1083 | } 1084 | if (name.length() > 0) 1085 | couldNotFindVariables(original.indexOf(word) + 1 + indexOfStart, name.toString()); 1086 | return coefficient; 1087 | } 1088 | 1089 | /** 1090 | * Couldn't find the variable but let's try to guess what could it be, 1091 | * may help to debug the expression 1092 | */ 1093 | private void couldNotFindVariables(int index, String nameOut) throws MathParserException { 1094 | double guess = 0; 1095 | String guessName = ""; 1096 | for (MathVariable variable : variables) { 1097 | if (!variable.hasFound) 1098 | continue; 1099 | 1100 | double sim = Utils.similarity(nameOut, variable.name); 1101 | if (sim > guess) { 1102 | guessName = variable.name; 1103 | guess = sim; 1104 | } 1105 | } 1106 | if (guess == 0) 1107 | throw new MathVariableNotFoundException(original, index, nameOut); 1108 | else 1109 | throw new MathVariableNotFoundException(original, index, nameOut, guessName); 1110 | } 1111 | 1112 | private void trim() { 1113 | src = src.trim(); 1114 | } 1115 | 1116 | private char get() { 1117 | char c = src.charAt(0); 1118 | src = src.substring(1); 1119 | return c; 1120 | } 1121 | 1122 | private String nextWord() { 1123 | try { 1124 | int index = Utils.findBestIndex(src, false); 1125 | String word = src.substring(0, index).trim(); 1126 | src = src.substring(index); 1127 | return word; 1128 | } catch (Exception ignore) { 1129 | return ""; 1130 | } 1131 | } 1132 | } 1133 | } 1134 | --------------------------------------------------------------------------------