<NME> CalculationUtils.java
<BEF> ADDFILE
<MSG> Fix #2038: Provide calculator in edit waypoint activity

<DFF> @@ -0,0 +1,127 @@
+package cgeo.geocaching.utils;
+
+public final class CalculationUtils {
+    private CalculationUtils() {
+        // utility class
+    }
+
+    /**
+     * This simple evaluation algorithm was created by user 'Boann' and released to the public domain on Stack Overflow:
+     * https://stackoverflow.com/questions/3422673/evaluating-a-math-expression-given-in-string-form
+     *
+     * I added the '%' operator myself.
+     * Note that the 'sqrt', 'cos', 'sin' and 'tan' functions don't work in this implementation as
+     * expected as the individual letters are automatically replaced with their corresponding
+     * expressions as they are interpreted as being individual variables.
+     */
+    public static double eval(final String str) {
+        return new Object() {
+            int pos = -1, ch;
+
+            void nextChar() {
+                ch = (++pos < str.length()) ? str.charAt(pos) : -1;
+            }
+
+            boolean eat(final int charToEat) {
+                while (ch == ' ') {
+                    nextChar();
+                }
+                if (ch == charToEat) {
+                    nextChar();
+                    return true;
+                }
+                return false;
+            }
+
+            double parse() {
+                nextChar();
+                final double x = parseExpression();
+                if (pos < str.length()) {
+                    throw new RuntimeException("Unexpected: " + (char) ch);
+                }
+                return x;
+            }
+
+            /**
+             * Grammar:
+             *
+             * expression = term | expression `+` term | expression `-` term
+             * term = factor | term `*` factor | term `/` factor
+             * factor = `+` factor | `-` factor | `(` expression `)`
+             *        | number | functionName factor | factor `^` factor
+             */
+            double parseExpression() {
+                double x = parseTerm();
+                for (;;) {
+                    if (eat('+')) {
+                        x += parseTerm(); // addition
+                    } else if (eat('-')) {
+                        x -= parseTerm(); // subtraction
+                    } else {
+                        return x;
+                    }
+                }
+            }
+
+            double parseTerm() {
+                double x = parseFactor();
+                for (;;) {
+                    if (eat('*')) {
+                        x *= parseFactor(); // multiplication
+                    } else if (eat('/')) {
+                        x /= parseFactor(); // division
+                    } else if (eat('%')) {
+                        x %= parseFactor(); // modulus (remainder)
+                    } else {
+                        return x;
+                    }
+                }
+            }
+
+            double parseFactor() {
+                if (eat('+')) {
+                    return parseFactor(); // unary plus
+                }
+                if (eat('-')) {
+                    return -parseFactor(); // unary minus
+                }
+
+                double x;
+                final int startPos = this.pos;
+                if (eat('(')) { // parentheses
+                    x = parseExpression();
+                    eat(')');
+                } else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
+                    while ((ch >= '0' && ch <= '9') || ch == '.') {
+                        nextChar();
+                    }
+                    x = Double.parseDouble(str.substring(startPos, this.pos));
+                } else if (ch >= 'a' && ch <= 'z') { // functions
+                    while (ch >= 'a' && ch <= 'z') {
+                        nextChar();
+                    }
+                    final String func = str.substring(startPos, this.pos);
+                    x = parseFactor();
+
+                    switch (func) {
+                        case "sqrt": x = Math.sqrt(x);                 break;
+                        case "sin":  x = Math.sin(Math.toRadians(x));  break;
+                        case "cos":  x = Math.cos(Math.toRadians(x));  break;
+                        case "tan":  x = Math.tan(Math.toRadians(x));  break;
+                        default:
+                            throw new RuntimeException("Unknown function: " + func);
+                    }
+
+                } else {
+                    throw new RuntimeException("Unexpected: " + (char) ch);
+                }
+
+                if (eat('^')) {
+                    x = Math.pow(x, parseFactor()); // exponentiation
+                }
+
+                return x;
+            }
+        }.parse();
+    }
+}

Fix #2038: Provide calculator in edit waypoint activity