Dalvikk / MathExprCalculator

Recursive mathematical expressions parser and calculator

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Mathematical expressions calculator

Easily modifiable recursively mathematical expressions parser and calculator

Example

> java -jar math.jar -debug
first * second mod lol
DEBUG: (first) * ((second) mod (lol))
Enter mode:
i
Enter first value:
5
Enter second value:
11
Enter lol value:
3
10

> java -jar math.jar
(first + last)/2 * n
Enter mode:
d
Enter first value:
1
Enter last value:
100
Enter n value:
100
5050.0

About

Supported:

  • any variable starting with a letter and containing a letter or number
  • Integer values
  • Overflow checks during evaluating
  • Calculations in various modes
  • Any infix binary and prefix unary operators

You can also easily modify the program:

  • Change the priority of operations
  • Add any prefix unary and infix binary operations
  • Add new calculation mode

See the instruction below

Mode Description
i Calculations in Int
ci Calculations in Int with overflow detection
bi Calculations in Big Integer
d Calculations in Double
bd Calculations in BigDecimal
p Calculations in group of Int modulo n

Operation in ascending priority:

  • ADD, SUB
  • MUL, DIV
  • MOD
  • All unary and 0-ary operations:
    CONST, VAR, LB, RB, NEGATE, SQUARE, ABS ...

Run

  1. Install JDK if for some reason you haven't done it yet
  2. Install Kotlin
  3. Clone this repository:

git clone https://github.com/Dalvikk/MathExprCalculator

  1. Compile and run:

cd MathExprCalculator
kotlinc src -include-runtime -d math.jar
java -jar math.jar [-debug]

-debug flag prints an expression with brackets for each operation, which allows you to see how the program parses and sets priorities

Error processing

All exceptions messages are user-friendly and contain the maximum amount of information with highlighted error pos

Input errors
> java -jar math.jar
hello + bye
Enter mode:
lol
Incorrect mode: lol. Available mods: [bi, i, ci, d, p]. Please, retry:
p
Enter mod:
mod
mod isn't a Int. Please, retry:
5
Enter hello value:
string
Enter bye value:
decimal
NumberFormatException while parsing: string isn't a Int
===========
hello + bye
^==========

ParseExceptions

ParseException
> java -jar math.jar
1 + (x *  / 9) + 3
Const, variable, '(' or unary operation expected:
==================
1 + (x *  / 9) + 3
==========^=======

> java -jar math.jar
x @ * y
Expected binary operation or nothing
=======
x @ * y
==^====
MissingLeftBracketException
> java -jar math.jar
(x + y     )    )
Left bracket missing
=================
(x + y     )    )
================^
MissingRightBracketException
> java -jar math.jar
((1 + 1		)
Right bracket missing
==========
((1 + 1  )
==========^

CalculateExceptions

All calculate exceptions extend ArithmeticException

DivisionByZeroException
> java -jar math.jar
x / (abs -5 - 5)
Enter mode:
i
Enter x value:
9999
Division by zero. Left = 9999, right = 0.
================
x / (abs -5 - 5)
==^=============
ModByZeroException
> java -jar math.jar
x mod (abs -5 - 5)
Enter mode:
i
Enter x value:
0
Mod by zero. Left = 0, right = 0.
==================
x mod (abs -5 - 5)
==^===============
ArithmeticException
> java -jar math.jar
x mod y
Enter mode:
bi
Enter x value:
100
Enter y value:
-2
Mod by negative number. BigInteger's mode always returns a non-negative BigInteger.
Maybe you want add 'remainder' operation?
Left = 100, right = -2.
=======
x mod y
==^====

// Arithmetic exception caused by NumberFormatException
> java -jar math.jar
first + second - third
Enter mode:
i
Enter first value:
123
Enter second value:
456
Enter third value:
hello
NumberFormatException while parsing: hello isn't a Int
======================
first + second - third
=================^====

// Constant overflows in type
> java -jar math.jar
x + 2
Enter mode:
i
Enter x value:
1000000000000000000
NumberFormatException while parsing: 1000000000000000000 isn't a Int
=====
x + 2
^====

Modding

Operation priority changing

Suppose you are not satisfied that mod priority is higher than mul.

> java -jar math.jar -debug
10 * 14 mod 5
DEBUG: (10) * ((14) mod (5))
Enter mode:
i
40

but you want 0. Let's change it.
Go to src/expression/operations/Operation.kt
Find the operationsByPriority list:

val operationsByPriority = listOf(
    listOf(ADD, SUB),
    listOf(MUL, DIV),
    listOf(MOD),
    listOf(NEGATE, SQUARE, ABS, CONST, VAR, LB, RB)
)

Now the priority of mod is equal to its index. Let's change the priority from 3 to 2.

val operationsByPriority = listOf(
    listOf(ADD, SUB),
    listOf(MUL, DIV, MOD),
    listOf(NEGATE, SQUARE, ABS, CONST, VAR, LB, RB)
)

Now mod has the same priority as mul and div. Operations with a higher priority are shifted one value lower accordingly.
Warning! Unary and 0-ary operations must have maximum priority or things won't work as you expect

That's all. Let's compile and check this:

> java -jar math.jar -debug
10 * 14 mod 5
DEBUG: ((10) * (14)) mod (5)
Enter mode: 
i
0
New calculating mode adding

Suppose that you want a new calculation mode.
For example, let's roll back to this state when BigDecimal wasn't supported and add it.

You can see the diff in BigDecimal added commit and make sure it's easy.

Go to src/expression/calculators/
Create calculator what you want, in our case BigDecimalCalculator, and inherit it from Calculate<YOUR_TYPE>

package expression.calculators

import java.math.BigDecimal

class BigDecimalCalculator : Calculator<BigDecimal> 

Implement all methods (if you don't want or cannot support some operation in your calculator, throw NotImplementedError):

override fun add(x: BigDecimal, y: BigDecimal, wrapper: AbstractNAryOperation): BigDecimal {
    return x.add(y)
}

override fun sub(x: BigDecimal, y: BigDecimal, wrapper: AbstractNAryOperation): BigDecimal {
    return x.subtract(y)
}

override fun mul(x: BigDecimal, y: BigDecimal, wrapper: AbstractNAryOperation): BigDecimal {
    return x.multiply(y)
}

override fun not_implemented(x: BigDecimal, y: BigDecimal, wrapper: AbstractNAryOperation): BigDecimal {
    throw NotImplementedError("This method isn't implemented for BigDecimal")
    // or TODO("This method isn't implemented for BigDecimal")
}

// ...

Go to src/expression/Main.kt
Find CALCULATOR_BY_MODE map and insert your calculator to match the mode:

val CALCULATOR_BY_MODE: Map<String, Calculator<*>> = mapOf(
    Pair("bi", BigIntCalculator()),
    Pair("i", CheckedIntCalculator()),
    Pair("ci", IntCalculator()),
    Pair("d", DoubleCalculator()),
    Pair("bd", BigDecimalCalculator()), // +
    Pair("p", ModCalculator())
)

Add small improvements:

var res = result.evaluate(map, calculator)
if (res is BigDecimal) {
    res = res.setScale(50, RoundingMode.HALF_EVEN)
}
println(res)

That's all.

New infix binary operation adding [TODO]
New prefix unary operation adding [TODO]

TODO list

  • Add VarConst support (pi = 3,1415926535 by default, e = 2.71828182846, etc)
  • Add Double support to the parser (0.01, 0.0e-2, 1e+2, Infinity, -Infinity, NaN)
  • More operations
  • Add postfix unary operations (!, !!)
  • Add assigment operation and interpreter mode
    > S1 = pi * R * l
    > S2 = pi * R^2 + pi * R * l
    > l = 5
    > S1 + S2
    Enter mode:
    > d
    Enter R:
    > 2
    result = 75.36
    > ...

About

Recursive mathematical expressions parser and calculator


Languages

Language:Kotlin 100.0%