();// 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