diff --git a/README.md b/README.md new file mode 100644 index 0000000..24c5e68 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +A simple and efficient Scientific/Software Engineering Calculator built to perform a variety of mathematical and logical operations. This project is designed to demonstrate core programming concepts, modular design, and user interaction. diff --git a/__pycache__/calculator.cpython-313.pyc b/__pycache__/calculator.cpython-313.pyc new file mode 100644 index 0000000..718ccaf Binary files /dev/null and b/__pycache__/calculator.cpython-313.pyc differ diff --git a/__pycache__/exceptions.cpython-313.pyc b/__pycache__/exceptions.cpython-313.pyc new file mode 100644 index 0000000..30b353d Binary files /dev/null and b/__pycache__/exceptions.cpython-313.pyc differ diff --git a/__pycache__/test_calculator.cpython-313.pyc b/__pycache__/test_calculator.cpython-313.pyc new file mode 100644 index 0000000..de89208 Binary files /dev/null and b/__pycache__/test_calculator.cpython-313.pyc differ diff --git a/calculator.py b/calculator.py index 5daac9a..4d44f2c 100644 --- a/calculator.py +++ b/calculator.py @@ -1,11 +1,46 @@ +import re +from trigonometry_module.trigonometric import Trigonometric + class Calculator: + + def __init__(self): + self.trig = Trigonometric() + def add(self, a, b): return a + b + def subtract(self, a, b): return a - b + def multiply(self, a, b): return a * b + def divide(self, a, b): if b == 0: raise ValueError("Division by zero") return a / b + + def evaluate(self, expr): + expr = expr.replace(" ", "") + + functions = [ + ("asin", self.trig.asin), + ("acos", self.trig.acos), + ("atan", self.trig.atan), + ("sinh", self.trig.sinh), + ("cosh", self.trig.cosh), + ("tanh", self.trig.tanh), + ("sin", self.trig.sin), + ("cos", self.trig.cos), + ("tan", self.trig.tan), + ] + + for name, func in functions: + expr = re.sub(rf'{name}\((.*?)\)', lambda m: str(func(m.group(1))) if m.group(1) != "" else (_ for _ in ()).throw(ValueError("Empty input")), expr) + + try: + return eval(expr) + except ZeroDivisionError: + raise ValueError("Division by zero") + except Exception: + raise ValueError("Invalid expression") \ No newline at end of file diff --git a/exceptions.py b/exceptions.py new file mode 100644 index 0000000..4ed2739 --- /dev/null +++ b/exceptions.py @@ -0,0 +1,18 @@ +class CalculatorError(Exception): + """Base class for calculator-related errors.""" + pass + + +class InvalidExpressionError(CalculatorError): + """Raised when the input expression is invalid.""" + pass + + +class DivisionByZeroError(CalculatorError): + """Raised when division by zero is attempted.""" + pass + + +class DomainError(CalculatorError): + """Raised when a trig function gets an invalid domain value.""" + pass \ No newline at end of file diff --git a/trigonometry_module/__init__.py b/trigonometry_module/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/trigonometry_module/__pycache__/__init__.cpython-313.pyc b/trigonometry_module/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..362a7cd Binary files /dev/null and b/trigonometry_module/__pycache__/__init__.cpython-313.pyc differ diff --git a/trigonometry_module/__pycache__/trigonometric.cpython-313.pyc b/trigonometry_module/__pycache__/trigonometric.cpython-313.pyc new file mode 100644 index 0000000..b6114f5 Binary files /dev/null and b/trigonometry_module/__pycache__/trigonometric.cpython-313.pyc differ diff --git a/trigonometry_module/test_trigonometric.py b/trigonometry_module/test_trigonometric.py new file mode 100644 index 0000000..1fe72e0 --- /dev/null +++ b/trigonometry_module/test_trigonometric.py @@ -0,0 +1,76 @@ +import unittest +from calculator import Calculator + +class TestTrigonometric(unittest.TestCase): + + def setUp(self): + self.calc = Calculator() + + def test_sin(self): + self.assertAlmostEqual(self.calc.evaluate("sin(30)"), 0.5, places=2) + + def test_cos(self): + self.assertAlmostEqual(self.calc.evaluate("cos(60)"), 0.5, places=2) + + def test_tan(self): + self.assertAlmostEqual(self.calc.evaluate("tan(45)"), 1.0, places=2) + + def test_expression(self): + self.assertAlmostEqual(self.calc.evaluate("2 + 3*sin(30)"), 3.5, places=2) + + def test_inverse(self): + self.assertAlmostEqual(self.calc.evaluate("asin(0.5)"), 30, places=2) + + def test_hyperbolic(self): + self.assertAlmostEqual(self.calc.evaluate("sinh(0)"), 0.0, places=2) + + #more test cases: + def test_sin_zero(self): + self.assertAlmostEqual(self.calc.evaluate("sin(0)"), 0.0, places=2) + + def test_cos_zero(self): + self.assertAlmostEqual(self.calc.evaluate("cos(0)"), 1.0, places=2) + + def test_tan_zero(self): + self.assertAlmostEqual(self.calc.evaluate("tan(0)"), 0.0, places=2) + + def test_asin_one(self): + self.assertAlmostEqual(self.calc.evaluate("asin(1)"), 90.0, places=2) + + def test_acos_one(self): + self.assertAlmostEqual(self.calc.evaluate("acos(1)"), 0.0, places=2) + + def test_expression_mix(self): + self.assertAlmostEqual(self.calc.evaluate("5 + 2*cos(60)"), 6.0, places=2) + + + def test_invalid_expression(self): + with self.assertRaises(ValueError): + self.calc.evaluate("sin()") + + def test_invalid_function(self): + with self.assertRaises(ValueError): + self.calc.evaluate("sinn(30)") + + def test_division_by_zero(self): + with self.assertRaises(DivisionByZeroError): + self.calc.evaluate("10/0") + + def test_invalid_expression(self): + with self.assertRaises(InvalidExpressionError): + self.calc.evaluate("2 + * 3") + + def test_asin_domain_error(self): + with self.assertRaises(DomainError): + self.calc.evaluate("asin(2)") + + def test_empty_expression(self): + with self.assertRaises(InvalidExpressionError): + self.calc.evaluate("") + + def test_non_string_expression(self): + with self.assertRaises(InvalidExpressionError): + self.calc.evaluate(123) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/trigonometry_module/trigonometric.py b/trigonometry_module/trigonometric.py new file mode 100644 index 0000000..7143cb3 --- /dev/null +++ b/trigonometry_module/trigonometric.py @@ -0,0 +1,54 @@ +import math +from exceptions import DomainError + +class Trigonometric: + + def to_radians(self, x): + return math.radians(float(x)) + + def sin(self, x): + return math.sin(self.to_radians(x)) + + def cos(self, x): + return math.cos(self.to_radians(x)) + + def tan(self, x): + x = float(x) + + # tan is undefined at 90 + k*180 + if x % 180 == 90: + raise ValueError("tan undefined at 90 + k*180") + + return math.tan(self.to_radians(x)) + + def asin_domain(self, x): + x = float(x) + if x < -1 or x > 1: + raise ValueError("asin domain is [-1,1]") + return math.degrees(math.asin(x)) + + def acos_domain(self, x): + x = float(x) + if x < -1 or x > 1: + raise ValueError("acos domain is [-1,1]") + return math.degrees(math.acos(x)) + + + def asin_value(self, x): + return math.degrees(math.asin(float(x))) + + def acos_value(self, x): + return math.degrees(math.acos(float(x))) + + def atan(self, x): + return math.degrees(math.atan(float(x))) + + def sinh(self, x): + return math.sinh(float(x)) + + def cosh(self, x): + return math.cosh(float(x)) + + def tanh(self, x): + return math.tanh(float(x)) + \ No newline at end of file