From 6ff0a68380917dffe5c348fba54dbbf37b44bbc9 Mon Sep 17 00:00:00 2001 From: shahrin fatima Date: Sun, 5 Apr 2026 18:29:07 +0530 Subject: [PATCH] Implemented arithmetic feature extensions --- __pycache__/arithmetic.cpython-311.pyc | Bin 0 -> 3544 bytes __pycache__/calculator.cpython-311.pyc | Bin 0 -> 1580 bytes __pycache__/expression_parser.cpython-311.pyc | Bin 0 -> 4038 bytes arithmetic.py | 52 +++++++++ calculator.py | 11 +- expression_parser.py | 99 ++++++++++++++++++ test_calculator.py | 56 ++++++---- 7 files changed, 195 insertions(+), 23 deletions(-) create mode 100644 __pycache__/arithmetic.cpython-311.pyc create mode 100644 __pycache__/calculator.cpython-311.pyc create mode 100644 __pycache__/expression_parser.cpython-311.pyc create mode 100644 arithmetic.py create mode 100644 expression_parser.py diff --git a/__pycache__/arithmetic.cpython-311.pyc b/__pycache__/arithmetic.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..65de746685c0059aabc03dfa4cc8aa6d0e517cbc GIT binary patch literal 3544 zcmd5;-%A`v9N)R!J5SeJV;ZSv+IlIp^t@kQLV{puD~XVo2t_O+rLx|fy5_vq*}Y36 zrX11-1p_IxsKr18lX^7yUvA44!h%nI>YH$HdFuB&yIyb4AJvDn&M}{x*`3+>em~z| z^J^&7CP2GA_`7yf5`;f-a@c(Cp!N+6HUuhAk`pEb>ia>MAXEhOQ3+6_en5!^0R6ND zFhGNVEwmLdNZSBgsSMag+W}=70&J%WV2F}YA=>d59yO97?uq#4hb}^|eGjl9EDL7C z+hyS;%zmpsdBA05ADFnqZKtjG?#?TX`Byg`8^YY;u9=bJ>iU8XcazQ-7LLnP$$; zXH6}WTzF)Mrgb*2nv*PR6mlj*+aM1{9H1n;>ACvMc%I%WZWn)vZN{G5cw#(FuNBve zYlG{9tAnp^>@AjWy}DJA(luBwWDX68lK^y1=L8HkJY=4xyyqS9(zXO&@;(_A7&wto z_HoH=|1^+bmVGu+Z8Bw`IFSfzg+E(VDP@;o6og*822c|A*$g;1%Z*bH0#c z?}K#8$zr1AxQDF6!0RE+A>j$FCa-u*9t+Ea5;r6}8a;^myh5)K6W!Zw%dl?=*z>M| z^dOEf^qr+kceSEsXnH;}^(eBGWqMg;sF2x=uJy-b!eqnD()-Ym2k{b;#DPH3C6)WH#;}K+Um08 z-m2VNk$R6B`eHB$k$xhB%6**{a{3{EEDS^(B`Yw`SFsP_EjrEF`HEUfUsdU=1pCgy z*Sp9`kWZSm=Q-(w`1LR753`K*f|jVQaWoT+?WK3dcgL-8!jh9!Ia!gCj^(ic9JUr1 z@Ys0?gVSRNq5}%)1HO};BRWhx!)A$fE7+v!zoMdugd)uss9wVzl9ULY&T|` zIZPV7HF9@8mbmXkB`2$(bJ&ux$V`VGv`C}+pTL|UdeDpSWn^&^z=L`?SFf;Z64n#u8K|mnwB^iGQ5+T9y*}d4+6hVFr(6g~B#nz*fVWKt#c{CD@i+C!uQ2HOjz(DW6Hsuo=~@M1U| zzIqgWVrasM8msE6$mCX3Gm}-L^ZE6ho>#dMO*aiH1(AQQrOjw^L20?sc454*X)%@a z(m?BY9Oz#3sSlt+&isRW6R(TAz6&6`dk@7*5`prhUo#%ZDji)UL0gZ7pOFbMUxK;|Z<*3fW zBpM?CP@6vA9(^;pJ9!?wR~?R?1Y@VcSk)J6)~ueyX7hs(R>qEM2R8e}L)v30)6Q6u zWQ3S)(xXI-Zb>F2-C3NPsIjri*K%<{-d*Et!|-fLucURlPBz#xvQ8sXqBOLWgDK4L zaBBvq!Vy`Zqn2l3BdrxoNS?TjG&4*QVFJGjmW%=x)vk4zGt(U@+l1^^IMg`|s8W^~|)uPHz13+!y+B@zY{;I1bNAaQZYjUG+_0!m2U2 z4{rloc6%T|-g;-*!Tl-r)iui!5s&3ol$@H?6vgr?N;c1nI_`an^0KJvjT0I8niv2L zB8V`Aa0g)+!5${mZx4~c64-z#GB`k``TNo5tIXGA>beCnIM9V;c&`a=*#+_61iPJV zvxx|^0PMkcDAPfZR|aNs8VM&6%) I9N3=y1^U%w3IG5A literal 0 HcmV?d00001 diff --git a/__pycache__/expression_parser.cpython-311.pyc b/__pycache__/expression_parser.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d9e41b0b81acbfba703b373d3934df6e69b7d590 GIT binary patch literal 4038 zcmb_fO>Emn7M>wR{ZW)9+fr=QZe*wST5@e|S~%?{adv}^T?DC9V4D`LtJGjbI$fGsHx)TmD6sp6vVI+> z4w}*MX*l!#=8^Av!>Y=jWMCb>4Q*VyF!Q(41*hCVNa01QKeddt^cON{%gVzDsic117+N?0IadlipY@dTkaRUED2ky}h866Q>s_GhA-w)hpGY(l+i za*GL7B`_l^i8$3#Z#EcE@yG80*+d4)!~gy!VTt(?4Bw~5t+s-6D!13JH|8bQK<%8c z3ERag4w>lINK6`!P5wS6MT}S^7BM~;3y;SpBGHk{#s}iWt+NyJm&c8vFl#!9oJxps z*<^@pIu@0rBqkCN1Dkv^MKrpBQ1w#u9Ze=$RJ$f#BT8~n)Rkp1dQSZ)nNUSZi^j!7 zJe?4Al`N!)dR>m|(YI$7{@9p}QeCp9DQa>dB@)mznflmt>gtl5RBp+n53CHrrHui} zpfcaJaeD1^j?Mk4$cIXND06Oy7uHV}f@i+775SMGKU1*HJO*CW7GVx1tO)-MbQ4`g zD;O@uK<7{vU;ciD)mxs&^~PAj2HPG-5;SnW5oc>$1H9-7>;sK>9Fb}^NblalFLOef#%hrYWw6pphd~yIWTmQmUpeDmO_4CWheB zs*pPXYeH9Mw(Rj{W_JBYDyWYe|B;eaj)L3k*_qr_ksm7YLltzyHD2~~Z_KUDNJ{*5=+-pmDxo}(qt(Sp@sJsV$(t;PIDGhOJJF8hL;!W|(e|3mq^a&KvC zspyN8e361LQsGc<|Ffvt-RCv9-{N)Wp4wKsA!r!9p2-hyo&UP$;lRsVD>_4gsi-&+)+MV zy1P_#4wsz61?O;uMZC}Aso0UHJ2OWph_)3fda|(!)w=-vwL-O5qy|=)gNl?+%iCTv zn06q2Liw_wrp{mg@05e`bqdnHUVvVMbfmBIzSa)nD=hFmQNPYNMLSDnyT@Sprorf* zCO?$0ONX>?J0u^g3r+n7(=28NYuMUjw!$5>CxhGP`G3K4Z&NdPe&)~~bv5PMJ!-e| zEdBtPzG&dq!fzb}$qQhL3{XPP7h$J`eCca|Y;WU>{=o+mp6#MGv=aX-UiL|K84Geq*IL^VPIcgYKgFnbi zKR5IvB@Oud{MB6g-pQ?#MSirzk5-V+HS`N0=L>AQ?zleX@A9i>_gFvg&mX@xur*Nh zjFvp31*=oktl?gpKML6{<=luS}` z9EjFlDtj6)OY`%9yas$R%KHY84B8O_S?TtN>mO!jcf5VMV9^^Yc|(~uc7(y))uJ$3 z5=Jw#fU|?4{E6ILZtibW+2HNz_372=2io?DZ$A0zN|{=glcjr^&^?oa)Lq|p?F0os1Ta}geJ3~d%QIe*#B zR~$AkQ$av7?ur+=g|f?2?_FgmOoGD!>p(JWr56eQvd35Ey=9>r1{K~ez#@>0tKwtu gEQZ3}q|PDST|o^pi-Q;{V}tzGj!%K`K4R_g|F*m;cmMzZ literal 0 HcmV?d00001 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()