diff --git a/__pycache__/arithmetic.cpython-311.pyc b/__pycache__/arithmetic.cpython-311.pyc new file mode 100644 index 0000000..65de746 Binary files /dev/null and b/__pycache__/arithmetic.cpython-311.pyc differ diff --git a/__pycache__/calculator.cpython-311.pyc b/__pycache__/calculator.cpython-311.pyc new file mode 100644 index 0000000..0fdf130 Binary files /dev/null and b/__pycache__/calculator.cpython-311.pyc differ diff --git a/__pycache__/expression_parser.cpython-311.pyc b/__pycache__/expression_parser.cpython-311.pyc new file mode 100644 index 0000000..d9e41b0 Binary files /dev/null and b/__pycache__/expression_parser.cpython-311.pyc differ diff --git a/arithmetic.py b/arithmetic.py new file mode 100644 index 0000000..2a3c3c1 --- /dev/null +++ b/arithmetic.py @@ -0,0 +1,52 @@ +# arithmetic.py + +import math + +def format_result(x): + return str(int(x)) if x == int(x) else str(x) + +def add(a, b): + return format_result(float(a) + float(b)) + +def sub(a, b): + return format_result(float(a) - float(b)) + +def mul(a, b): + return format_result(float(a) * float(b)) + +def div(a, b): + a, b = float(a), float(b) + if b == 0: + raise ValueError("Division by zero") + return format_result(a / b) + +def modulo(a, b): + a, b = float(a), float(b) + if b == 0: + raise ValueError("Modulo by zero") + return format_result(a % b) + +def power(a, b): + return format_result(float(a) ** float(b)) + +def factorial(n): + n = float(n) + if n < 0: + raise ValueError("Negative factorial") + return format_result(math.factorial(int(n))) + +def sqrt(x): + return format_result(math.sqrt(float(x))) + +def cbrt(x): + return format_result(float(x) ** (1/3)) + +def log(x): + return format_result(math.log10(float(x))) + +def floor(x): + return format_result(math.floor(float(x))) + +def ceil(x): + return format_result(math.ceil(float(x))) + diff --git a/calculator.py b/calculator.py index 88317c8..aec59a3 100644 --- a/calculator.py +++ b/calculator.py @@ -1,3 +1,5 @@ +from expression_parser import evaluate_expression + class Calculator: # mode can be 1: Fraction, 2: Bin, 3: Oct, 4: Hex, 5: Set, 6: Matrix, default = 0 mode = 0 @@ -12,7 +14,8 @@ def divide(self, a, b): if b == 0: raise ValueError("Division by zero") return a / b - - def evaluate(mode = 0): - #check the mode and based on its values execute for different modes - print('evaluate method to extend for multiple derived classes') + def evaluate(self, expr): + try: + return evaluate_expression(expr) + except Exception as e: + raise ValueError(f"Invalid expression: {str(e)}") diff --git a/expression_parser.py b/expression_parser.py new file mode 100644 index 0000000..b41e436 --- /dev/null +++ b/expression_parser.py @@ -0,0 +1,99 @@ +# expression_parser.py + +import re +from arithmetic import * + +precedence = { + '+': 1, '-': 1, + '*': 2, '/': 2, '%': 2, + '**': 3 +} + +ops = { + '+': add, + '-': sub, + '*': mul, + '/': div, + '%': modulo, + '**': power +} + +functions = { + 'sqrt': sqrt, + 'cbrt': cbrt, + 'log': log, + 'floor': floor, + 'ceil': ceil, +} + +def tokenize(expr): + expr = expr.replace(" ", "") + return re.findall(r'\d+\.?\d*|\*\*|[()+\-*/%]|[a-zA-Z]+|!', expr) + +def to_postfix(tokens): + output = [] + stack = [] + + for token in tokens: + if re.match(r'\d', token): + output.append(token) + + elif token in functions: + stack.append(token) + + elif token == '!': + output.append(token) + + elif token in ops: + while (stack and stack[-1] in ops and + precedence[token] <= precedence[stack[-1]]): + output.append(stack.pop()) + stack.append(token) + + elif token == '(': + stack.append(token) + + elif token == ')': + while stack and stack[-1] != '(': + output.append(stack.pop()) + stack.pop() + if stack and stack[-1] in functions: + output.append(stack.pop()) + + while stack: + output.append(stack.pop()) + + return output + +def evaluate_postfix(postfix): + stack = [] + + for token in postfix: + if re.match(r'\d', str(token)): + stack.append(token) + + elif token == '!': + a = stack.pop() + stack.append(factorial(a)) + + elif token in ops: + b = stack.pop() + a = stack.pop() + result = ops[token](a, b) # returns string + stack.append(result) + + elif token in functions: + a = stack.pop() + result = functions[token](a) + stack.append(result) + + return stack[0] + +def evaluate_expression(expr): + tokens = tokenize(expr) + postfix = to_postfix(tokens) + result = evaluate_postfix(postfix) + + # FINAL conversion → number (for your tests) + return float(result) if '.' in result else int(result) + diff --git a/test_calculator.py b/test_calculator.py index 7cb79e9..8d8db5e 100644 --- a/test_calculator.py +++ b/test_calculator.py @@ -1,33 +1,51 @@ import unittest from calculator import Calculator -class TestCalculator(unittest.TestCase): - # base test cases +class TestArithmetic(unittest.TestCase): + def setUp(self): self.calc = Calculator() def test_add(self): - self.assertEqual(self.calc.add(2, 3), 5) + self.assertEqual(self.calc.evaluate("2+3"), 5) + + def test_precedence(self): + self.assertEqual(self.calc.evaluate("2+3*4"), 14) + + def test_parentheses(self): + self.assertEqual(self.calc.evaluate("(2+3)*4"), 20) + + def test_power(self): + self.assertEqual(self.calc.evaluate("3**2"), 9) + + def test_modulo(self): + self.assertEqual(self.calc.evaluate("10%3"), 1) - def test_sub(self): - self.assertEqual(self.calc.subtract(2, 3), -1) + def test_sqrt(self): + self.assertEqual(self.calc.evaluate("sqrt(16)"), 4) - def test_multiply(self): - self.assertEqual(self.calc.multiply(2, 3), 6) + def test_cbrt(self): + self.assertAlmostEqual(self.calc.evaluate("cbrt(27)"), 3) - def test_divide(self): - self.assertEqual(self.calc.divide(2, 4), 0.5) + def test_factorial(self): + self.assertEqual(self.calc.evaluate("5!"), 120) - def test_divide(self): - self.assertEqual(self.calc.divide(4, -2), -2) - - def test_divide_fail(self): # this will fail - self.assertNotEqual(self.calc.divide(4, -2), 2) + def test_log(self): + self.assertAlmostEqual(self.calc.evaluate("log(100)"), 2) - def test_divide_by_zero(self): + def test_floor(self): + self.assertEqual(self.calc.evaluate("floor(2.7)"), 2) + + def test_ceil(self): + self.assertEqual(self.calc.evaluate("ceil(2.2)"), 3) + + def test_nested(self): + self.assertEqual(self.calc.evaluate("2*(3+4)"), 14) + + def test_div_zero(self): with self.assertRaises(ValueError): - self.calc.divide(5, 0) + self.calc.evaluate("5/0") + -# Optional: this allows running the script directly - if __name__ == '__main__': - unittest.main() # +if __name__ == "__main__": + unittest.main()