Added test class for EquationEvaluator

This commit is contained in:
tastybento 2024-01-13 08:50:37 -08:00
parent 2b373f62d9
commit 76e0bad88a
2 changed files with 132 additions and 95 deletions

View File

@ -3,119 +3,119 @@ package world.bentobox.level.calculators;
import java.text.ParseException; import java.text.ParseException;
/** /**
* @author tastybento * Utility class to evaluate equations
*/ */
public class EquationEvaluator { public class EquationEvaluator {
private static class Parser { private static class Parser {
private final String input; private final String input;
private int pos = -1; private int pos = -1;
private int currentChar; private int currentChar;
public Parser(String input) { @SuppressWarnings("unused")
this.input = input; private Parser() {
moveToNextChar(); throw new IllegalStateException("Utility class");
} }
private void moveToNextChar() { public Parser(String input) {
currentChar = (++pos < input.length()) ? input.charAt(pos) : -1; this.input = input;
} moveToNextChar();
}
private boolean tryToEat(int charToEat) { private void moveToNextChar() {
while (currentChar == ' ') currentChar = (++pos < input.length()) ? input.charAt(pos) : -1;
moveToNextChar(); }
if (currentChar == charToEat) {
moveToNextChar();
return true;
}
return false;
}
public double evaluate() throws ParseException { private boolean tryToEat(int charToEat) {
double result = parseExpression(); while (currentChar == ' ') {
if (pos < input.length()) { moveToNextChar();
throw new ParseException("Unexpected character: " + (char) currentChar, pos); }
} if (currentChar == charToEat) {
return result; moveToNextChar();
} return true;
}
return false;
}
private double parseExpression() throws ParseException { public double evaluate() throws ParseException {
double result = parseTerm(); double result = parseExpression();
while (true) { if (pos < input.length()) {
if (tryToEat('+')) throw new ParseException("Unexpected character: " + (char) currentChar, pos);
result += parseTerm(); }
else if (tryToEat('-')) return result;
result -= parseTerm(); }
else
return result;
}
}
private double parseFactor() throws ParseException { private double parseExpression() throws ParseException {
if (tryToEat('+')) double result = parseTerm();
return parseFactor(); // unary plus while (true) {
if (tryToEat('-')) if (tryToEat('+')) {
return -parseFactor(); // unary minus result += parseTerm();
} else if (tryToEat('-')) {
result -= parseTerm();
} else {
return result;
}
}
}
double x; private double parseFactor() throws ParseException {
int startPos = this.pos; if (tryToEat('+')) {
if (tryToEat('(')) { // parentheses return parseFactor(); // unary plus
x = parseExpression(); }
tryToEat(')'); if (tryToEat('-')) {
} else if ((currentChar >= '0' && currentChar <= '9') || currentChar == '.') { // numbers return -parseFactor(); // unary minus
while ((currentChar >= '0' && currentChar <= '9') || currentChar == '.') }
moveToNextChar(); double x;
x = Double.parseDouble(input.substring(startPos, this.pos)); int startPos = this.pos;
} else if (currentChar >= 'a' && currentChar <= 'z') { // functions if (tryToEat('(')) { // parentheses
while (currentChar >= 'a' && currentChar <= 'z') x = parseExpression();
moveToNextChar(); tryToEat(')');
String func = input.substring(startPos, this.pos); } else if ((currentChar >= '0' && currentChar <= '9') || currentChar == '.') { // numbers
x = parseFactor(); while ((currentChar >= '0' && currentChar <= '9') || currentChar == '.') {
switch (func) { moveToNextChar();
case "sqrt": }
x = Math.sqrt(x); x = Double.parseDouble(input.substring(startPos, this.pos));
break; } else if (currentChar >= 'a' && currentChar <= 'z') { // functions
case "sin": while (currentChar >= 'a' && currentChar <= 'z') {
x = Math.sin(Math.toRadians(x)); moveToNextChar();
break; }
case "cos": String func = input.substring(startPos, this.pos);
x = Math.cos(Math.toRadians(x)); x = parseFactor();
break; x = switch (func) {
case "tan": case "sqrt" -> Math.sqrt(x);
x = Math.tan(Math.toRadians(x)); case "sin" -> Math.sin(Math.toRadians(x));
break; case "cos" -> Math.cos(Math.toRadians(x));
case "log": case "tan" -> Math.tan(Math.toRadians(x));
x = Math.log(x); case "log" -> Math.log(x);
break; default -> throw new ParseException("Unknown function: " + func, startPos);
default: };
throw new ParseException("Unknown function: " + func, startPos); } else {
} throw new ParseException("Unexpected: " + (char) currentChar, startPos);
} else { }
throw new ParseException("Unexpected: " + (char) currentChar, startPos);
}
if (tryToEat('^')) if (tryToEat('^')) {
x = Math.pow(x, parseFactor()); // exponentiation x = Math.pow(x, parseFactor()); // exponentiation
}
return x; return x;
} }
private double parseTerm() throws ParseException { private double parseTerm() throws ParseException {
double x = parseFactor(); double x = parseFactor();
for (;;) { for (;;) {
if (tryToEat('*')) if (tryToEat('*'))
x *= parseFactor(); // multiplication x *= parseFactor(); // multiplication
else if (tryToEat('/')) else if (tryToEat('/'))
x /= parseFactor(); // division x /= parseFactor(); // division
else else
return x; return x;
} }
} }
} }
public static double eval(final String equation) throws ParseException { public static double eval(final String equation) throws ParseException {
return new Parser(equation).evaluate(); return new Parser(equation).evaluate();
} }
} }

View File

@ -0,0 +1,37 @@
package world.bentobox.level.calculators;
import static org.junit.Assert.assertEquals;
import java.text.ParseException;
import org.junit.Test;
/**
* Test the equation evaluation
*/
public class EquationEvaluatorTest {
/**
* Test method for {@link world.bentobox.level.calculators.EquationEvaluator#eval(java.lang.String)}.
* @throws ParseException
*/
@Test
public void testEval() throws ParseException {
assertEquals(4D, EquationEvaluator.eval("2+2"), 0D);
assertEquals(0D, EquationEvaluator.eval("2-2"), 0D);
assertEquals(1D, EquationEvaluator.eval("2/2"), 0D);
assertEquals(4D, EquationEvaluator.eval("2*2"), 0D);
assertEquals(8D, EquationEvaluator.eval("2+2+2+2"), 0D);
assertEquals(5D, EquationEvaluator.eval("2.5+2.5"), 0D);
assertEquals(1.414, EquationEvaluator.eval("sqrt(2)"), 0.001D);
assertEquals(3.414, EquationEvaluator.eval("2 + sqrt(2)"), 0.001D);
assertEquals(0D, EquationEvaluator.eval("sin(0)"), 0.1D);
assertEquals(1D, EquationEvaluator.eval("cos(0)"), 0.1D);
assertEquals(0D, EquationEvaluator.eval("tan(0)"), 0.1D);
assertEquals(0D, EquationEvaluator.eval("log(1)"), 0.1D);
assertEquals(27D, EquationEvaluator.eval("3^3"), 0.D);
assertEquals(84.70332D, EquationEvaluator.eval("3^3 + 2 + 2.65 * (3 / 4) - sin(45) * log(10) + 55.344"),
0.0001D);
}
}