Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added __pycache__/arithmetic.cpython-311.pyc
Binary file not shown.
Binary file added __pycache__/calculator.cpython-311.pyc
Binary file not shown.
Binary file added __pycache__/expression_parser.cpython-311.pyc
Binary file not shown.
52 changes: 52 additions & 0 deletions arithmetic.py
Original file line number Diff line number Diff line change
@@ -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)))

11 changes: 7 additions & 4 deletions calculator.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)}")
99 changes: 99 additions & 0 deletions expression_parser.py
Original file line number Diff line number Diff line change
@@ -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)

56 changes: 37 additions & 19 deletions test_calculator.py
Original file line number Diff line number Diff line change
@@ -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()