diff --git a/calctests.py b/calctests.py index 1964570..f026d57 100644 --- a/calctests.py +++ b/calctests.py @@ -19,7 +19,68 @@ def test_add3(self): def test_sub(self): c = Calculator() self.assertEqual(c.sub(9, 3), 6) - + def test_sub2(self): + c = Calculator() + self.assertEqual(c.sub(12, -10), 22) + def test_sub3(self): + c = Calculator() + self.assertEqual(c.sub(5, 8), -3) + def test_multiply(self): + c = Calculator() + self.assertEqual(c.multiply(3, 4), 12) + def test_multiply2(self): + c = Calculator() + self.assertEqual(c.multiply(5, -2), -10) + def test_multiply3(self): + c = Calculator() + self.assertEqual(c.multiply(7, 6), 42) + def test_divide(self): + c = Calculator() + self.assertEqual(c.divide(10, 2), 5) + def test_divide2(self): + c = Calculator() + self.assertEqual(c.divide(9, -3), -3) + def test_divide3(self): + c = Calculator() + self.assertEqual(c.divide(7, 7), 1) + def test_square(self): + c = Calculator() + self.assertEqual(c.square(5), 25) + def test_square2(self): + c = Calculator() + self.assertEqual(c.square(9), 81) + def test_square3(self): + c = Calculator() + self.assertEqual(c.square(0), 0) + def test_square_root(self): + c = Calculator() + self.assertEqual(c.square_root(25), 5) + self.assertEqual(c.square_root(0), 0) + def test_variable_exponentiation(self): + c = Calculator() + self.assertEqual(c.variable_exponentiation(2, 3), 8) + self.assertEqual(c.variable_exponentiation(5, 0), 1) + def test_inverse(self): + c = Calculator() + self.assertEqual(c.inverse(2), 0.5) + self.assertEqual(c.inverse(4), 0.25) + def test_invert_sign(self): + c = Calculator() + self.assertEqual(c.invert_sign(5), -5) + self.assertEqual(c.invert_sign(-3), 3) + def test_degrees_to_radians(self): + c = Calculator() + self.assertAlmostEqual(c.degrees_to_radians(180), 3.141592653589793) + self.assertAlmostEqual(c.degrees_to_radians(90), 1.5707963267948966) + def test_radians_to_degrees(self): + c = Calculator() + self.assertAlmostEqual(c.radians_to_degrees(3.141592653589793), 180) + self.assertAlmostEqual(c.radians_to_degrees(1.5707963267948966), 90) + def test_percent(self): + c = Calculator() + self.assertEqual(c.percent(50), 0.5) + self.assertEqual(c.percent(25), 0.25) + self.assertEqual(c.percent(100), 1) if __name__ == '__main__': unittest.main() diff --git a/calculator.py b/calculator.py index 3c85ead..2a8857c 100644 --- a/calculator.py +++ b/calculator.py @@ -1,12 +1,60 @@ + class Calculator: def __init__(self): - pass + # single-entry memory and last result tracking + self.memory = None + self.last_result = None + def _store_last(self, result): + self.last_result = result + return result + def add(self, x, y): - return x + y + return self._store_last(x + y) def sub(self, x, y): - return 0 + return self._store_last(x - y) + + def multiply(self, x, y): + return self._store_last(x * y) + + def divide(self, x, y): + if y == 0: + raise ValueError("Cannot divide by zero") + return self._store_last(x / y) + + def square(self, x): + return self._store_last(x * x) + + def square_root(self, x): + return self._store_last(x ** 0.5) + + def variable_exponentiation(self, x, y): + return self._store_last(x ** y) + + def inverse(self, x): + return self._store_last(1 / x) + + def invert_sign(self, x): + return self._store_last(-x) + + def degrees_to_radians(self, x): + return self._store_last(x * (3.141592653589793 / 180)) + + def radians_to_degrees(self, x): + return self._store_last(x * (180 / 3.141592653589793)) + + def percent(self, x): + return self._store_last(x / 100) + + def memory_test(self): + self.memory = 0 + self.memory += 5 + print(self.memory) # should print 5 + self.memory *= 2 + print(self.memory) # should print 10 -# add lots more methods to this calculator class. + def memory_clear(self): + self.memory = 0 + print(self.memory) # should print 0 diff --git a/core_calc.py b/core_calc.py new file mode 100644 index 0000000..0e407c2 --- /dev/null +++ b/core_calc.py @@ -0,0 +1,17 @@ +class CoreCalculator: + 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: + return "Error: cannot divide by zero" + return a / b + + def power(self, a, b): + return a ** b diff --git a/helpers.py b/helpers.py new file mode 100644 index 0000000..3c26f15 --- /dev/null +++ b/helpers.py @@ -0,0 +1,47 @@ +def getOneNumber(): + return float(input("Enter a number: ")) + + +def getTwoNumbers(): + a = float(input("Enter first number: ")) + b = float(input("Enter second number: ")) + return a, b + + +def displayResult(calc, result): + print("Result:", calc.formatResult(result)) + + +def printTitle(): + print("\n========================================") + print(" SCIENTIFIC CALCULATOR") + print("========================================") + print(" Core + Scientific + Memory System") + print(" Display Modes | Angle Modes | Help Menu") + print("========================================\n") + + print("Welcome! This calculator supports core and scientific functions.") + print("Display modes: binary, octal, decimal, hexadecimal") + print("Angle modes: degree, radian") + print("Type 'help' for full command list.\n") + +def printMenu(calc): + print(f"\nDisplay Mode: {calc.display_mode}") + print(f"Angle Mode: {calc.angle_mode}") + print(f"Memory: {calc.formatResult(calc.memoryRecall())}") + print("Commands:") + print("add sub mul div pow") + print("sqrt sin cos tan log ln fact percent") + print("mode setmode angle setangle") + print("ms mr mc m+ m-") + print("help q") + + +def printHelp(): + print("\n===== HELP =====") + print("Core: add, sub, mul, div, pow") + print("Scientific: sqrt, sin, cos, tan, log, ln, fact, percent") + print("Display: mode, setmode") + print("Angle: angle, setangle") + print("Memory: ms, mr, mc, m+, m-") + print("Other: help, q\n") diff --git a/main-app.py b/main-app.py index a7cc4e2..e38769c 100644 --- a/main-app.py +++ b/main-app.py @@ -1,3 +1,4 @@ + from calculator import Calculator @@ -19,9 +20,41 @@ def performCalcLoop(calc): elif choice == 'add': a, b = getTwoNumbers() displayResult(calc.add(a, b)) + elif choice == 'subtract': + a, b = getTwoNumbers() + displayResult(calc.subtract(a, b)) + elif choice == 'multiply': + a, b = getTwoNumbers() + displayResult(calc.multiply(a, b)) + elif choice == 'square': + a, b = getTwoNumbers() + displayResult(calc.square(a, b)) + elif choice == 'inverse': + a, b = getTwoNumbers() + displayResult(calc.inverse(a, b)) + elif choice == 'invert_sign': + a, b = getTwoNumbers() + displayResult(calc.invert_sign(a, b)) + elif choice == 'degrees_to_radians': + a, b = getTwoNumbers() + displayResult(calc.degrees_to_radians(a, b)) + elif choice == 'radians_to_degrees': + a, b = getTwoNumbers() + displayResult(calc.radians_to_degrees(a, b)) + elif choice == 'percent': + a, b = getTwoNumbers() + displayResult(calc.percent(a, b)) + elif choice == 'memory_test': + calc.memory_test() + elif choice == 'memory_clear': + calc.memory_clear() else: print("That is not a valid input.") + +def memoryClear(calc): + calc.memory = 0 + print(calc.memory) # should print 0 # main start def main(): diff --git a/main.py b/main.py new file mode 100644 index 0000000..f146a57 --- /dev/null +++ b/main.py @@ -0,0 +1,66 @@ +from core_calc import CoreCalculator +from scientific_calc import ScientificCalculator +from helpers import * + + +def runCalculator(): + core = CoreCalculator() + sci = ScientificCalculator() + + printTitle() + + while True: + printMenu(sci) + op = input("Operation: ").lower() + + if op == "help": + printHelp() + + elif op == "q": + print("Thank you for using Niciah's and Shocka's Scientific Calculator, Goodbye!") + break + + elif op == "mode": + sci.switchDisplayMode() + + elif op == "setmode": + sci.switchDisplayMode(input("Enter mode: ")) + + elif op == "angle": + sci.switchAngleMode() + + elif op == "setangle": + sci.switchAngleMode(input("Enter angle mode: ")) + + elif op == "ms": + sci.memoryStore(sci.last_result) + + elif op == "mr": + print("Memory:", sci.formatResult(sci.memoryRecall())) + + elif op == "mc": + sci.memoryClear() + + elif op == "m+": + sci.memoryAdd(sci.last_result) + + elif op == "m-": + sci.memorySubtract(sci.last_result) + + elif op in ["add", "sub", "mul", "div", "pow"]: + a, b = getTwoNumbers() + result = getattr(core, {"add":"add","sub":"subtract","mul":"multiply","div":"divide","pow":"power"}[op])(a,b) + sci.setLastResult(result) + displayResult(sci, result) + + elif op in ["sqrt","sin","cos","tan","log","ln","fact","percent"]: + x = getOneNumber() + result = getattr(sci, {"fact":"factorial"}.get(op, op))(x) + sci.setLastResult(result) + displayResult(sci, result) + + else: + print("Invalid operation") + + +runCalculator() diff --git a/scientific_calc.py b/scientific_calc.py new file mode 100644 index 0000000..e08de66 --- /dev/null +++ b/scientific_calc.py @@ -0,0 +1,112 @@ +import math + + +class ScientificCalculator: + def __init__(self): + self.display_mode = "decimal" + self.angle_mode = "degree" + self.memory = 0 + self.last_result = 0 + + # Display Mode + def switchDisplayMode(self, mode=None): + modes = ["binary", "octal", "decimal", "hexadecimal"] + + if mode is None: + index = modes.index(self.display_mode) + self.display_mode = modes[(index + 1) % len(modes)] + else: + mode = mode.lower() + if mode in modes: + self.display_mode = mode + else: + return "Invalid display mode." + + def formatResult(self, value): + if isinstance(value, str): + return value + + if self.display_mode == "decimal": + return str(value) + + if not float(value).is_integer(): + return "Error: non-decimal modes require whole numbers" + + value = int(value) + + if self.display_mode == "binary": + return bin(value)[2:] + elif self.display_mode == "octal": + return oct(value)[2:] + elif self.display_mode == "hexadecimal": + return hex(value)[2:].upper() + + return str(value) + + # Angle Mode + def switchAngleMode(self, mode=None): + modes = ["degree", "radian"] + + if mode is None: + index = modes.index(self.angle_mode) + self.angle_mode = modes[(index + 1) % len(modes)] + else: + mode = mode.lower() + if mode in modes: + self.angle_mode = mode + else: + return "Invalid angle mode." + + # Memory + def setLastResult(self, value): + if not isinstance(value, str): + self.last_result = value + + def memoryStore(self, value): + self.memory = value + + def memoryRecall(self): + return self.memory + + def memoryClear(self): + self.memory = 0 + + def memoryAdd(self, value): + self.memory += value + + def memorySubtract(self, value): + self.memory -= value + + # Scientific Functions + def sqrt(self, x): + if x < 0: + return "Error: cannot take square root of negative" + return math.sqrt(x) + + def sin(self, x): + return math.sin(math.radians(x)) if self.angle_mode == "degree" else math.sin(x) + + def cos(self, x): + return math.cos(math.radians(x)) if self.angle_mode == "degree" else math.cos(x) + + def tan(self, x): + return math.tan(math.radians(x)) if self.angle_mode == "degree" else math.tan(x) + + def log(self, x): + if x <= 0: + return "Error: log input must be > 0" + return math.log10(x) + + def ln(self, x): + if x <= 0: + return "Error: ln input must be > 0" + return math.log(x) + + def factorial(self, x): + if x < 0 or not float(x).is_integer(): + return "Error: factorial requires whole number" + return math.factorial(int(x)) + + def percent(self, x): + return x / 100 + diff --git a/test_calculator.py b/test_calculator.py new file mode 100644 index 0000000..8d0ac40 --- /dev/null +++ b/test_calculator.py @@ -0,0 +1,50 @@ +from core_calc import CoreCalculator +from scientific_calc import ScientificCalculator + +core = CoreCalculator() +sci = ScientificCalculator() + +print("===== RUNNING TESTS =====\n") + +# CORE TESTS +print("Add Test:", core.add(5, 3)) # 8 +print("Subtract Test:", core.subtract(10, 4)) # 6 +print("Multiply Test:", core.multiply(6, 7)) # 42 +print("Divide Test:", core.divide(10, 2)) # 5 +print("Divide by Zero Test:", core.divide(10, 0)) # Error +print("Power Test:", core.power(2, 4)) # 16 + +# SCIENTIFIC TESTS +print("\n--- Scientific ---") +print("Sqrt Test:", sci.sqrt(9)) # 3 +print("Sin Test (deg):", sci.sin(30)) # ~0.5 +print("Cos Test:", sci.cos(60)) # ~0.5 +print("Tan Test:", sci.tan(45)) # ~1 +print("Log Test:", sci.log(100)) # 2 +print("Ln Test:", sci.ln(2.71828)) # ~1 +print("Factorial Test:", sci.factorial(5)) # 120 +print("Percent Test:", sci.percent(75)) # 0.75 + +# DISPLAY MODE TEST +print("\n--- Display Mode ---") +sci.switchDisplayMode("binary") +print("Binary Mode Test (8):", sci.formatResult(8)) # 1000 + +# ANGLE MODE TEST +print("\n--- Angle Mode ---") +sci.switchAngleMode("radian") +print("Sin Test (rad):", sci.sin(1.5708)) # ~1 + +# MEMORY TEST +print("\n--- Memory ---") +sci.setLastResult(10) +sci.memoryStore(10) +print("Memory Recall:", sci.memoryRecall()) # 10 +sci.memoryAdd(5) +print("Memory Add:", sci.memoryRecall()) # 15 +sci.memorySubtract(5) +print("Memory Subtract:", sci.memoryRecall()) # 10 +sci.memoryClear() +print("Memory Cleared:", sci.memoryRecall()) # 0 + +print("\n===== TESTS COMPLETE =====")