From 91e7c707a565000242a6794fd0b5b32e392c6b5c Mon Sep 17 00:00:00 2001 From: sloane Date: Fri, 10 Jul 2026 17:17:43 -0400 Subject: [PATCH 01/19] added sloane.txt --- sloane.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 sloane.txt diff --git a/sloane.txt b/sloane.txt new file mode 100644 index 0000000..1b320d2 --- /dev/null +++ b/sloane.txt @@ -0,0 +1 @@ +sloane \ No newline at end of file From 6ed09a6088843aba047d0cefd3db70f1818d373f Mon Sep 17 00:00:00 2001 From: sloane Date: Fri, 10 Jul 2026 17:19:44 -0400 Subject: [PATCH 02/19] remove sloane.txt --- sloane.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 sloane.txt diff --git a/sloane.txt b/sloane.txt deleted file mode 100644 index 1b320d2..0000000 --- a/sloane.txt +++ /dev/null @@ -1 +0,0 @@ -sloane \ No newline at end of file From 735e346cb168e15c89c34a8510e0af4b970b88a9 Mon Sep 17 00:00:00 2001 From: sloane Date: Fri, 10 Jul 2026 17:22:42 -0400 Subject: [PATCH 03/19] update read me --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6445ffa..2680732 100644 --- a/README.md +++ b/README.md @@ -91,3 +91,5 @@ The following functions should take the displayed value (x) and updated it accor ## Submission Completed projects should be submitted by submitting a pull request against the [original repository](https://git.zipcode.rocks/Cohort4.2/ZCW-MacroLabs-OOP-ScientificCalculator). All work should be done in your own repository. + +sloane was here \ No newline at end of file From 6a115555b93f2e26164b2232b22c3d1ad2ec6ebf Mon Sep 17 00:00:00 2001 From: sloane Date: Sat, 11 Jul 2026 10:56:01 -0400 Subject: [PATCH 04/19] added some operations --- calculator.py | 14 +++++++++++++- main-app.py | 3 +++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/calculator.py b/calculator.py index 3c85ead..7942870 100644 --- a/calculator.py +++ b/calculator.py @@ -7,6 +7,18 @@ def add(self, x, y): return x + y def sub(self, x, y): - return 0 + return x - y + + def mult(self, x, y): + return x * y + + def square(self, x, y): + return x ** y + + def squareroot(self, x, y=2): + return x ** (1/y) + + + # add lots more methods to this calculator class. diff --git a/main-app.py b/main-app.py index a7cc4e2..d3f560f 100644 --- a/main-app.py +++ b/main-app.py @@ -19,6 +19,9 @@ def performCalcLoop(calc): elif choice == 'add': a, b = getTwoNumbers() displayResult(calc.add(a, b)) + elif choice == "sub": + a, b = getTwoNumbers() + displayResult(calc.sub(a, b)) else: print("That is not a valid input.") From 07b8334084d7354709fb730ed636dd3546f2f9f3 Mon Sep 17 00:00:00 2001 From: sloane Date: Sat, 11 Jul 2026 11:24:23 -0400 Subject: [PATCH 05/19] added getOneNumber and square root function so far --- main-app.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/main-app.py b/main-app.py index d3f560f..0de3ece 100644 --- a/main-app.py +++ b/main-app.py @@ -6,8 +6,11 @@ def getTwoNumbers(): b = float(input("second number? ")) return a, b +def getOneNumber() + a = float(input("first number?")) + return a -def displayResult(x: float): +def displayResult(x): print(x, "\n") @@ -21,7 +24,16 @@ def performCalcLoop(calc): displayResult(calc.add(a, b)) elif choice == "sub": a, b = getTwoNumbers() - displayResult(calc.sub(a, b)) + displayResult(calc.sub(a, b)) + elif choice == "mult": + a, b = getTwoNumbers() + displayResult(calc.mult(a, b)) + elif choice == "square": + a, b = GetTwoNumbers() + displayResult(calc.square(a,b)) + elif choice == "squareroot": + a = getOneNumber() + displayResult(calc.squareroot(a)) else: print("That is not a valid input.") From d1d25d73a7c4a26459b979bf2b709747ca4bbc6b Mon Sep 17 00:00:00 2001 From: sloane Date: Sat, 11 Jul 2026 11:38:07 -0400 Subject: [PATCH 06/19] removed an error --- calculator.py | 3 +++ main-app.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/calculator.py b/calculator.py index 7942870..1c4b0a2 100644 --- a/calculator.py +++ b/calculator.py @@ -18,6 +18,9 @@ def square(self, x, y): def squareroot(self, x, y=2): return x ** (1/y) + def clear(self) + self.status = "OK" + diff --git a/main-app.py b/main-app.py index 0de3ece..edfe5c2 100644 --- a/main-app.py +++ b/main-app.py @@ -6,7 +6,7 @@ def getTwoNumbers(): b = float(input("second number? ")) return a, b -def getOneNumber() +def getOneNumber(): a = float(input("first number?")) return a @@ -29,7 +29,7 @@ def performCalcLoop(calc): a, b = getTwoNumbers() displayResult(calc.mult(a, b)) elif choice == "square": - a, b = GetTwoNumbers() + a, b = getTwoNumbers() displayResult(calc.square(a,b)) elif choice == "squareroot": a = getOneNumber() From 6ce16b791aece68dea562903ebda6fde081c0d03 Mon Sep 17 00:00:00 2001 From: sloane Date: Sat, 11 Jul 2026 11:39:17 -0400 Subject: [PATCH 07/19] removed another error --- calculator.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/calculator.py b/calculator.py index 1c4b0a2..a68a52d 100644 --- a/calculator.py +++ b/calculator.py @@ -17,9 +17,7 @@ def square(self, x, y): def squareroot(self, x, y=2): return x ** (1/y) - - def clear(self) - self.status = "OK" + From 3094743697ab0e23635085f07dc65b5182931799 Mon Sep 17 00:00:00 2001 From: leigh Date: Sat, 11 Jul 2026 11:45:14 -0400 Subject: [PATCH 08/19] added state to calculator.py - ljd 7/11/26 --- calculator.py | 6 +++++- test_math.py | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 test_math.py diff --git a/calculator.py b/calculator.py index 3c85ead..f5cbe06 100644 --- a/calculator.py +++ b/calculator.py @@ -1,7 +1,11 @@ +import math +from typing import Union +Number = Union[int,float] + class Calculator: def __init__(self): - pass + self.display = 0 def add(self, x, y): return x + y diff --git a/test_math.py b/test_math.py new file mode 100644 index 0000000..c2083f2 --- /dev/null +++ b/test_math.py @@ -0,0 +1,3 @@ +import math + +print(math.sqrt(64)) From 3deca9e65c114259d8be998f178a3fb735a368c7 Mon Sep 17 00:00:00 2001 From: leigh Date: Sat, 11 Jul 2026 12:01:47 -0400 Subject: [PATCH 09/19] Add status and test file 7/11/26 - LJD --- calculator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calculator.py b/calculator.py index f5cbe06..1ce5c80 100644 --- a/calculator.py +++ b/calculator.py @@ -3,7 +3,7 @@ Number = Union[int,float] class Calculator: - +#status set here def __init__(self): self.display = 0 From 26983432c75d5b04aa21458d83861302f46d2416 Mon Sep 17 00:00:00 2001 From: leigh Date: Sun, 12 Jul 2026 19:54:38 -0400 Subject: [PATCH 10/19] restore calculator functionality and custom feature - LJD 7:54PM 7/12/26 --- calculator.py | 276 +++++++++++++++++++++++++++++++++++++++++++++++--- main-app.py | 206 +++++++++++++++++++++++++++++++------ sciCalc.py | 101 ++++++++++++++++++ 3 files changed, 538 insertions(+), 45 deletions(-) create mode 100644 sciCalc.py diff --git a/calculator.py b/calculator.py index c24b316..ff807c3 100644 --- a/calculator.py +++ b/calculator.py @@ -1,29 +1,275 @@ -import math -from typing import Union -Number = Union[int,float] - class Calculator: + + # Error Messages + INVALID_INPUT_ERROR = "Invalid input" + DIVIDE_BY_ZERO_ERROR = "Cannot divide by zero" + OVERFLOW_ERROR = "Number too large" + INVALID_EXPONENT_ERROR = "Invalid expoent" + INVALID_OPERATION_ERROR = "Invalid operation" + ## ERROR = "Err"------- + #status set here def __init__(self): self.display = 0 + self.error = None + self.memory = 0 - def add(self, x, y): - return x + y +#---------------- +#Display methods +#---------------- + def getDisplay(self): + """ + Return the error message when an error exists. + Otherwise, return the current numeric display. + """ + if self.hasError(): + return self.error + + return self.display + + def hasError(self): + """ + Return True when an error message has been stored. + """ + return self.error is not None + + def clear(self): + """ + Reset both the display and the error status. + """ + self.display = 0 + self.error = None + return self.display + + def setDisplay(self, value): + """ + Set the calculator display to a valid number. + The calculator must be cleared before continuing + after an error. + """ + if self.hasError(): + return self.error + + if not self.isValidNumber(value): + self.error = self.INVALID_INPUT_ERROR + return self.error + + self.display = value + return self.display + + def isValidNumber(self, value): + """ + Boolean values are excluded bc Python treats T / F as int + """ + + return( + isinstance(value, (int, float)) + and not isinstance(value, bool) + ) +#--------------- +#Memory methods +#--------------- + def memory_add(self): #M+ + """ + Add the current display value to memory. + Update both memory and the display. + """ + if self.hasError(): + return self.error + + if not self.isValidNumber(self.display): + self.error = self.INVALID_INPUT_ERROR + return self.error + + self.memory += self.display + self.display = self.memory + return self.display + + def memory_clear(self): #MC + """ + Reset memory to zero. + Leave the current display unchanged. + """ + + if self.hasError(): + return self.error + + self.memory = 0 + return self.display + + def memory_recall(self): # MRC + """ + Copy the stored memory value to the display. + """ + if self.hasError(): + return self.error + + self.display = self.memory - def sub(self, x, y): - return x - y + return self.display - def mult(self, x, y): - return x * y + def memory_store(self): + """ + Store the current display value in memory. + Leave the display unchanged. + """ + if self.hasError(): + return self.error + + if not self.isValidNumber(self.display): + self.error = self.INVALID_INPUT_ERROR + return self.error + + self.memory = self.display + return self.display + +#--------------- +#Simple math functions +#--------------- + def add(self, x, y): + if self.hasError(): + return self.error + + if not self.isValidNumber(x) or not self.isValidNumber(y): + self.error = self.INVALID_INPUT_ERROR + return self.error + try: + self.display = x + y + except OverflowError: + self.error = self.OVERFLOW_ERROR + + return self.getDisplay() + + def subtract(self, x, y): + if self.hasError(): + return self.error + + if not self.isValidNumber(x) or not self.isValidNumber(y): + self.error = self.INVALID_INPUT_ERROR + return self.error + + try: + self.display = x - y + except OverflowError: + self.error = self.OVERFLOW_ERROR + + return self.getDisplay() - def square(self, x, y): - return x ** y + def multiply(self, x, y): + if self.hasError(): + return self.error + + if not self.isValidNumber(x) or not self.isValidNumber(y): + self.error = self.INVALID_INPUT_ERROR + return self.error + + try: + self.display = x * y + except OverflowError: + self.error = self.OVERFLOW_ERROR + + return self.getDisplay() - def squareroot(self, x, y=2): - return x ** (1/y) + def divide(self, x, y): + if self.hasError(): + return self.error + + if not self.isValidNumber(x) or not self.isValidNumber(y): + self.error = self.INVALID_INPUT_ERROR + return self.error + + if y == 0: + self.error = self.DIVIDE_BY_ZERO_ERROR + return self.error + + try: + self.display = x / y + except OverflowError: + self.error = self.OVERFLOW_ERROR + return self.getDisplay() + def inverse(self): + if self.hasError(): + return self.error + + if not self.isValidNumber(self.display): + self.error = self.INVALID_INPUT_ERROR + return self.error + + if self.display == 0: + self.error = self.DIVIDE_BY_ZERO_ERROR + return self.error + + try: + # ensure numeric division even if display is a numeric string; will raise on invalid types + self.display = 1 / float(self.display) + except OverflowError: + self.error = self.OVERFLOW_ERROR + + return self.getDisplay() + + def exponentiate(self, exponent): + if self.hasError(): + return self.error + + if not self.isValidNumber(self.display): + self.error = self.INVALID_INPUT_ERROR + return self.error + + if not self.isValidNumber(exponent): + self.error = self.INVALID_INPUT_ERROR + return self.error + + try: + result = self.display ** exponent + + if isinstance(result, complex): + self.error = self.INVALID_EXPONENT_ERROR + else: + self.display = result + + except ZeroDivisionError: + self.error = self.DIVIDE_BY_ZERO_ERROR + except OverflowError: + self.error = self.OVERFLOW_ERROR + except (TypeError, ValueError): + self.error = self.INVALID_EXPONENT_ERROR + return self.getDisplay() + + def switch_sign(self): + if self.hasError(): + return self.error + + if not self.isValidNumber(self.display): + self.error = self.INVALID_INPUT_ERROR + return self.error + + self.display = -self.display + return self.display + +#--------------- +# Utility functions +#--------------- + def absolute_value(self): + if self.hasError(): + return self.error + + if not self.isValidNumber(self.display): + self.error = self.INVALID_INPUT_ERROR + return self.error + + self.display = abs(self.display) + return self.display + + def percentage(self): + if self.hasError(): + return self.error + if not self.isValidNumber(self.display): + self.error = self.INVALID_INPUT_ERROR + return self.error -# add lots more methods to this calculator class. + self.display = self.display / 100 + return self.display \ No newline at end of file diff --git a/main-app.py b/main-app.py index edfe5c2..9a76197 100644 --- a/main-app.py +++ b/main-app.py @@ -1,46 +1,192 @@ -from calculator import Calculator +from sciCalc import SciCalc +def getNumber(message): +#ask the user for one number. +#keep asking until the user enters a valid int or dec. + while True: + try: + return float(input(message)) + except ValueError: + print("Please enter a valid number.") def getTwoNumbers(): - a = float(input("first number? ")) - b = float(input("second number? ")) - return a, b + while True: + try: + a = float(input("first number? ")) + b = float(input("second number? ")) + return a, b + + except ValueError: + print("Error: Please enter valid numbers") + print() -def getOneNumber(): - a = float(input("first number?")) - return a +def displayResult(result): + print("Display:", result) + print() + +def displayMenu(): + print("Available operations:") + print("-------------------------------") + print(" set - Put a number on the display") + print(" add - Add a number to the display") + print(" subtract - Subtract a number from the display") + print(" multiply - Multiply the display by a number") + print(" divide - Divide the display by a number") + print(" power - Raise the display to an exponent") + print(" inverse - Calculate 1 dividend by the display") + print(" sign - Switch the display between positive and negative") + print(" absolute - Calculate the absolute value") + print(" percentage - Divide the display by 100") + print(" factorial - Calculate factorial") + print(" log - Base 10 logarithm") + print(" invlog - 10 raised to the display number") + print(" nl - Natural Logarithm") + print(" exp - e raised to the display number") + print(" ms - Store memory") + print(" m+ - Add display to memory") + print(" mc - Clear memory") + print(" mrc - Recall memory") + print(" tip - Calculate a tip") + print(" temp - Convert temperature") + print(" clear - Reset the display to 0") + print(" show - Show the current display") + print(" help - Show the available operations") + print(" q - Quit") + print() + +def performCalcLoop(calc): + displayMenu() -def displayResult(x): - print(x, "\n") + while True: + choice = input("Operation?").strip().lower() + if choice == "q": + break # user types q to quit calulator. + + #when the calculator displays Err, only clear, show, help and quit allowed + if calc.hasError() and choice not in ( + "clear", "show", "help", "q" + ): + print("The calculator is displaying an Error") + print("Type 'clear' before performing another operation.") + print() + continue + + if choice == "set": + number = getNumber("Number? ") + displayResult(calc.setDisplay(number)) -def performCalcLoop(calc): - while True: - choice = input("Operation? ") - if choice == 'q': - break # user types q to quit calulator. elif choice == 'add': - a, b = getTwoNumbers() - displayResult(calc.add(a, b)) - elif choice == "sub": - a, b = getTwoNumbers() - displayResult(calc.sub(a, b)) - elif choice == "mult": - a, b = getTwoNumbers() - displayResult(calc.mult(a, b)) - elif choice == "square": - a, b = getTwoNumbers() - displayResult(calc.square(a,b)) - elif choice == "squareroot": - a = getOneNumber() - displayResult(calc.squareroot(a)) + x, y = getTwoNumbers() + displayResult(calc.add(x, y)) + + elif choice == "subtract": + x, y = getTwoNumbers() + displayResult(calc.subtract(x, y)) + + elif choice == "multiply": + x, y = getTwoNumbers() + displayResult(calc.multiply(x, y)) + + elif choice == "divide": + x, y = getTwoNumbers() + displayResult(calc.divide(x, y)) + + elif choice == "power": + base = getNumber("Base number? ") + exponent = getNumber("Exponent? ") + + calc.setDisplay(base) + displayResult(calc.exponentiate(exponent)) + + elif choice == "inverse": + number = getNumber("Number? ") + + calc.setDisplay(number) + displayResult(calc.inverse()) + + elif choice == "sign": + number = getNumber("Number? ") + + calc.setDisplay(number) + displayResult(calc.switch_sign()) + + elif choice == "absolute": + number = getNumber("Number? ") + + calc.setDisplay(number) + displayResult(calc.absolute_value()) + + elif choice == "percentage": + number = getNumber("Number? ") + + calc.setDisplay(number) + displayResult(calc.percentage()) + + elif choice == "factorial": + number = getNumber("Number? ") + calc.setDisplay(number) + displayResult(calc.factorial()) + + elif choice == "log": + number = getNumber("Number? ") + calc.setDisplay(number) + displayResult(calc.log()) + + elif choice == "invlog": + number = getNumber("Number? ") + calc.setDisplay(number) + displayResult(calc.inverse_log()) + + elif choice == "nl": + number = getNumber("Number? ") + calc.setDisplay(number) + displayResult(calc.natural_log()) + + elif choice == "exp": + number = getNumber("Number? ") + calc.setDisplay(number) + displayResult(calc.inverse_natural_log()) + + elif choice == "tip": + bill = getNumber("Bill Amount? ") + percent = getNumber("Tip Percentage? ") + displayResult(calc.tip_calculator(bill, percent)) + + elif choice == "temp": + temperature = getNumber("Temperature? ") + conversion = input("Convert to (C/F)? ") + displayResult(calc.temperature_converter(temperature, conversion)) + + elif choice == "ms": + displayResult(calc.memory_story()) + + elif choice == "m+": + displayResult(calc.memory_add()) + + elif choice == "mc": + displayResult(calc.memory_clear()) + + elif choice == "mrc": + displayResult(calc.memory_recall()) + + elif choice == "clear": + displayResult(calc.clear()) + + elif choice == "show": + displayResult(calc.getDisplay()) + + elif choice == "help": + displayMenu() + else: print("That is not a valid input.") - + print("Type 'help' to see the available operations.") + print() # main start def main(): - calc = Calculator() + calc = SciCalc() performCalcLoop(calc) print("Done Calculating.") diff --git a/sciCalc.py b/sciCalc.py new file mode 100644 index 0000000..f82c256 --- /dev/null +++ b/sciCalc.py @@ -0,0 +1,101 @@ +import math +from calculator import Calculator + +class SciCalc(Calculator): + + def factorial(self): + if self.hasError(): + return self.error + + if not self.isValidNumber(self.display): + self.error = self.INVALID_INPUT_ERROR + return self.error + + if self.display < 0: + self.error = "Factorial requires a non-negative number" + return self.error + if self.display != int(self.display): + self.error = "Factorial requries a whole number" + return self.error + + self.display = math.factorial(int(self.display)) + return self.display + + def log(self): + if self.hasError(): + return self.error + + if self.display <= 0: + self.error = "Log undefined for zeror or negative numbers" + return self.error + + self.display = math.log10(self.display) + return self.display + + def inverse_log(self): + if self.hasError(): + return self.error + + self.display = 10 ** self.display + return self.display + + def natural_log(self): + if self.hasError(): + return self.error + + if self.display <= 0: + self.error = "Natural log undefined for zero or negative nubmers" + return self.error + + self.display = math.log(self.display) + return self.display + + def inverse_natural_log(self): + if self.hasError(): + return self.error + self.display = math.exp(self.display) + return self.display + +#---------------- +# Custom features +#---------------- + +# Tip Function + def tip_calculator(self, billAmount, tipPercent): + if self.hasError(): + return self.error + + if not self.isValidNumber(billAmount) or not self.isValidNumber(tipPercent): + self.error = self.INVALID_INPUT_ERROR + return self.error + + if billAmount < 0 or tipPercent < 0: + self.error = "Amounts cannot be negative" + return self.error + + tip = billAmount * (tipPercent /100) + self.display = billAmount + tip + + return self.display + + # Convert temp + def temperature_converter(self, temperature, conversion): + if self.hasError(): + return self.error + + if not self.isValidNumber(temperature): + self.error = self.INVALID_INPUT_ERROR + return self.error + + conversion = conversion.upper() + + if conversion == "C": + self.display = (temperature - 32) * 5 / 9 + + elif conversion == "F": + self.display = (temperature * 9 / 5) + 32 + + else: + self.error = "Use C or F" + + return self.getDisplay() \ No newline at end of file From 1befc16cb4eb474b9d41c11e57251be177c969b8 Mon Sep 17 00:00:00 2001 From: leigh Date: Sun, 12 Jul 2026 20:41:04 -0400 Subject: [PATCH 11/19] Integrate calculator, scientific, mem and custom features - LJD 8:40PM 7/12/26 --- calculator.py | 49 ++++++++++++++++++---- main-app.py | 64 ++++++++++++++++++++++++++++- sciCalc.py | 112 +++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 216 insertions(+), 9 deletions(-) diff --git a/calculator.py b/calculator.py index ff807c3..377923c 100644 --- a/calculator.py +++ b/calculator.py @@ -4,15 +4,16 @@ class Calculator: INVALID_INPUT_ERROR = "Invalid input" DIVIDE_BY_ZERO_ERROR = "Cannot divide by zero" OVERFLOW_ERROR = "Number too large" - INVALID_EXPONENT_ERROR = "Invalid expoent" + INVALID_EXPONENT_ERROR = "Invalid exponent" INVALID_OPERATION_ERROR = "Invalid operation" ## ERROR = "Err"------- #status set here def __init__(self): - self.display = 0 - self.error = None - self.memory = 0 + self.display: int | float = 0 + self.error: str | None = None + self.memory: int | float = 0 + self.trig_mode: str = "Radians" #---------------- #Display methods @@ -77,9 +78,9 @@ def memory_add(self): #M+ if self.hasError(): return self.error - if not self.isValidNumber(self.display): - self.error = self.INVALID_INPUT_ERROR - return self.error + # if not self.isValidNumber(self.display): + # self.error = self.INVALID_INPUT_ERROR + # return self.error self.memory += self.display self.display = self.memory @@ -249,6 +250,40 @@ def switch_sign(self): self.display = -self.display return self.display + # added Sloanes code 7/12/26 8:07p + def square(self, x): + if self.hasError(): + return self.display + + if not self.isValidNumber(x): + self.error = self.INVALID_INPUT_ERROR + return self.error + + try: + self.display = x ** 2 + except OverflowError: + self.error = self.OVERFLOW_ERROR + return self.getDisplay() + + def squareroot(self, x): + if self.hasError(): + return self.error + + if not self.isValidNumber(x): + self.error = self.INVALID_INPUT_ERROR + return self.error + + if x < 0: + self.error = "Cannot calculate the square root of a negative number" + return self.error + + try: + self.display = x ** 0.5 + except OverflowError: + self.error = self.OVERFLOW_ERROR + return self.getDisplay() + + #--------------- # Utility functions #--------------- diff --git a/main-app.py b/main-app.py index 9a76197..d5202a2 100644 --- a/main-app.py +++ b/main-app.py @@ -1,4 +1,5 @@ from sciCalc import SciCalc +import math def getNumber(message): #ask the user for one number. @@ -20,6 +21,10 @@ def getTwoNumbers(): print("Error: Please enter valid numbers") print() +def getOneNumber(): + a = float(input("first number? ")) + return a + def displayResult(result): print("Display:", result) print() @@ -32,9 +37,20 @@ def displayMenu(): print(" subtract - Subtract a number from the display") print(" multiply - Multiply the display by a number") print(" divide - Divide the display by a number") + print(" square - Raise the display to the second power") + print(" squareroot - Find the square root of the number on display") print(" power - Raise the display to an exponent") print(" inverse - Calculate 1 dividend by the display") print(" sign - Switch the display between positive and negative") + # scientific functions + print(" mode - Switch trig units between Degrees and Radians") + print(" sine - Calculate the sine of the number on display") + print(" cosine - Calculate the cosine of the number on display") + print(" tangent - Calculate the tangent of the number on display") + print(" asine - Calculate the inverse sine of the number on display") + print(" acosine - Calculate the inverse cosine of the number on display") + print(" atangent - Calculate the inverse tangent of the number on display") + # custom functions print(" absolute - Calculate the absolute value") print(" percentage - Divide the display by 100") print(" factorial - Calculate factorial") @@ -58,7 +74,7 @@ def performCalcLoop(calc): displayMenu() while True: - choice = input("Operation?").strip().lower() + choice = input("What do you want to do, pick a function? ").strip().lower() if choice == "q": break # user types q to quit calulator. @@ -92,6 +108,14 @@ def performCalcLoop(calc): x, y = getTwoNumbers() displayResult(calc.divide(x, y)) + elif choice == "square": + x = getOneNumber() + displayResult(calc.square(x)) + + elif choice == "squareroot": + x = getOneNumber() + displayResult(calc.squareroot(x)) + elif choice == "power": base = getNumber("Base number? ") exponent = getNumber("Exponent? ") @@ -123,6 +147,44 @@ def performCalcLoop(calc): calc.setDisplay(number) displayResult(calc.percentage()) + # scientific functions + + elif choice == "mode": + new_mode = calc.switchUnitsMode() + print(f"Trig unit mode changed to: {new_mode}") + print() + + elif choice == "sine": + number = getNumber("Number? ") + calc.setDisplay(number) + displayResult(calc.sine()) + + elif choice == "cosine": + number = getNumber("Number? ") + calc.setDisplay(number) + displayResult(calc.cosine()) + + elif choice == "tangent": + number = getNumber("Number? ") + calc.setDisplay(number) + displayResult(calc.tangent()) + + elif choice == "asine": + number = getNumber("Number (between -1 and 1)? ") + calc.setDisplay(number) + displayResult(calc.inverse_sine()) + + elif choice == "acosine": + number = getNumber("Number (between -1 and 1)? ") + calc.setDisplay(number) + displayResult(calc.inverse_cosine()) + + elif choice == "atangent": + number = getNumber("Number (between -1 and 1)? ") + calc.setDisplay(number) + displayResult(calc.inverse_tangent()) +#----end of scientific + elif choice == "factorial": number = getNumber("Number? ") calc.setDisplay(number) diff --git a/sciCalc.py b/sciCalc.py index f82c256..b935881 100644 --- a/sciCalc.py +++ b/sciCalc.py @@ -98,4 +98,114 @@ def temperature_converter(self, temperature, conversion): else: self.error = "Use C or F" - return self.getDisplay() \ No newline at end of file + return self.getDisplay() +#------------------- +#Scientific Functions +#------------------- +# to switch between degrees and radians + def getTrigMode(self): + return self.trig_mode + + def switchUnitsMode(self, mode=None): + if self.hasError(): + return self.trig_mode + + if mode in {"Degrees", "Radians"}: + self.trig_mode = mode + else: + self.trig_mode = "Degrees" if self.trig_mode == "Radians" else "Radians" + + return self.trig_mode + + def sine(self): + if self.hasError(): + return self.display + try: #needs to be a float + val = float(self.display) + if self.trig_mode == "Degrees": + val = math.radians(val) + + result = math.sin(val) + + self.display = 0.0 if abs(result) < 1e-15 else result + except (TypeError, ValueError, OverflowError): + self.display = self.error + return self.display + + def cosine(self): + if self.hasError(): + return self.display + try: #needs to be a float + val = float(self.display) + if self.trig_mode == "Degrees": + val = math.radians(val) + + result = math.cos(val) + + self.display = 0.0 if abs(result) < 1e-15 else result + except (TypeError, ValueError, OverflowError): + self.display = self.error + return self.display + + def tangent(self): + if self.hasError(): + return self.display + try: #needs to be a float + val = float(self.display) + if self.trig_mode == "Degrees": + val = math.radians(val) + + result = math.tan(val) + + self.display = 0.0 if abs(result) < 1e-15 else result #decimal places + except (TypeError, ValueError, OverflowError): + self.display = self.error + return self.display + + def inverse_sine(self): + if self.hasError(): + return self.display + try: + val = float(self.display) + + result = math.asin(val) #value error if not between -1 and 1 + + if self.trig_mode == "Degrees": #convert radians back to degrees + result = math.degrees(result) + + self.display = 0.0 if abs(result) < 1e-15 else result + except (TypeError, ValueError, OverflowError): + self.display = self.error + return self.display + + def inverse_cosine(self): + if self.hasError(): + return self.display + try: + val = float(self.display) + + result = math.acos(val) #value error if not between -1 and 1 + + if self.trig_mode == "Degrees": #convert radians back to degrees + result = math.degrees(result) + + self.display = 0.0 if abs(result) < 1e-15 else result + except (TypeError, ValueError, OverflowError): + self.display = self.error + return self.display + + def inverse_tangent(self): + if self.hasError(): + return self.display + try: + val = float(self.display) + + result = math.atan(val) #value error if not between -1 and 1 + + if self.trig_mode == "Degrees": #convert radians back to degrees + result = math.degrees(result) + + self.display = 0.0 if abs(result) < 1e-15 else result + except (TypeError, ValueError, OverflowError): + self.display = self.error + return self.display \ No newline at end of file From cac64caa7f63265cf3878ee8ca6fe0566b90f261 Mon Sep 17 00:00:00 2001 From: leigh Date: Sun, 12 Jul 2026 21:57:03 -0400 Subject: [PATCH 12/19] Update README with completed calculator features LJD - 7/12/26 9:56PM --- README.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2680732..3a1ec65 100644 --- a/README.md +++ b/README.md @@ -66,9 +66,25 @@ - Ln (natural logarithm) - ex (inverse natural logarithm) +### Custom Features +- Tip Calculator + allows users to calculate the total cost of a bill including gratuity. +- Temperature Converter + allows users to convert temperatures between Celsius and Fahrenheit. -### Custom Features +These custom features were selected bec they demonstrate the ability to extend the calculator beyond standard mathematical operations while applying real-world problem-solving. The Tip Calculator provides a practical financial calculation, while the Temperature Converter demonstrates the implementation of mathematical formulas and conditional logic. + +### User-Friendly Menu System +-The app provides an interactive menu that displays all available calculator operations. Users can simply enter the name of the desired operation rather than remembering commands. The menu also includes a HELP option to redisplay all available commands at any time, making the calculator easier to navigate and use. + +### Error Handling +-The calculator validates user input and displays meaningful error messages for invalid operations, including: + - Invalid numeric input + - Division by zero + - Invalid exponent operations + - Invalid mathematical operations +-The calculator prevents additional operations while an error is displayed until the user clears the error, ensuring consistent and predicatable behavior. In addition to the Core and Scientific features, you are required to create at least two of your own features for the calculator. They can be any two features that are not already covered and that you can implement as you see fit. These features must be properly tested. @@ -92,4 +108,3 @@ The following functions should take the displayed value (x) and updated it accor Completed projects should be submitted by submitting a pull request against the [original repository](https://git.zipcode.rocks/Cohort4.2/ZCW-MacroLabs-OOP-ScientificCalculator). All work should be done in your own repository. -sloane was here \ No newline at end of file From 11eb231f32628c9e6c68c5c74a24122f9b1fcc3c Mon Sep 17 00:00:00 2001 From: leigh Date: Sun, 12 Jul 2026 22:40:42 -0400 Subject: [PATCH 13/19] Add unit tests for calculator and scientific calculators LJD 7/12/26 10:40PM --- test_calculator.py | 463 +++++++++++++++++++++++++++++++++++++++++ test_sciCalc.py | 504 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 967 insertions(+) create mode 100644 test_calculator.py create mode 100644 test_sciCalc.py diff --git a/test_calculator.py b/test_calculator.py new file mode 100644 index 0000000..0103ab6 --- /dev/null +++ b/test_calculator.py @@ -0,0 +1,463 @@ +import unittest + +from calculator import Calculator + + +class TestCalculator(unittest.TestCase): + + def setUp(self): + """ + Create a new Calculator before every test. + + This keeps each test independent. + """ + self.calc = Calculator() + + # ------------------------------------------------- + # Initial state and display tests + # ------------------------------------------------- + + def test_default_display_is_zero(self): + self.assertEqual(self.calc.getDisplay(), 0) + + def test_default_error_is_none(self): + self.assertFalse(self.calc.hasError()) + + def test_default_memory_is_zero(self): + self.assertEqual(self.calc.memory, 0) + + def test_default_trig_mode_is_radians(self): + self.assertEqual(self.calc.trig_mode, "Radians") + + def test_set_display_updates_display(self): + result = self.calc.setDisplay(25) + + self.assertEqual(result, 25) + self.assertEqual(self.calc.getDisplay(), 25) + + def test_set_display_accepts_decimal_number(self): + result = self.calc.setDisplay(12.5) + + self.assertEqual(result, 12.5) + + def test_set_display_rejects_string(self): + result = self.calc.setDisplay("hello") + + self.assertEqual(result, self.calc.INVALID_INPUT_ERROR) + self.assertTrue(self.calc.hasError()) + + def test_set_display_rejects_boolean(self): + result = self.calc.setDisplay(True) + + self.assertEqual(result, self.calc.INVALID_INPUT_ERROR) + self.assertTrue(self.calc.hasError()) + + def test_set_display_is_blocked_when_error_exists(self): + self.calc.setDisplay("bad value") + + result = self.calc.setDisplay(10) + + self.assertEqual(result, self.calc.INVALID_INPUT_ERROR) + self.assertTrue(self.calc.hasError()) + + def test_clear_resets_display_and_error(self): + self.calc.setDisplay("bad value") + + result = self.calc.clear() + + self.assertEqual(result, 0) + self.assertEqual(self.calc.getDisplay(), 0) + self.assertFalse(self.calc.hasError()) + + # ------------------------------------------------- + # Number validation tests + # ------------------------------------------------- + + def test_is_valid_number_accepts_integer(self): + self.assertTrue(self.calc.isValidNumber(10)) + + def test_is_valid_number_accepts_float(self): + self.assertTrue(self.calc.isValidNumber(10.5)) + + def test_is_valid_number_rejects_string(self): + self.assertFalse(self.calc.isValidNumber("10")) + + def test_is_valid_number_rejects_boolean(self): + self.assertFalse(self.calc.isValidNumber(True)) + + # ------------------------------------------------- + # Memory tests + # ------------------------------------------------- + + def test_memory_add_adds_display_to_memory(self): + self.calc.setDisplay(10) + + result = self.calc.memory_add() + + self.assertEqual(result, 10) + self.assertEqual(self.calc.memory, 10) + self.assertEqual(self.calc.getDisplay(), 10) + + def test_memory_add_accumulates_values(self): + self.calc.setDisplay(10) + self.calc.memory_add() + + self.calc.setDisplay(5) + result = self.calc.memory_add() + + self.assertEqual(result, 15) + self.assertEqual(self.calc.memory, 15) + self.assertEqual(self.calc.getDisplay(), 15) + + def test_memory_clear_resets_memory_to_zero(self): + self.calc.setDisplay(10) + self.calc.memory_add() + + result = self.calc.memory_clear() + + self.assertEqual(self.calc.memory, 0) + self.assertEqual(result, 10) + + def test_memory_recall_copies_memory_to_display(self): + self.calc.setDisplay(12) + self.calc.memory_store() + + self.calc.setDisplay(0) + result = self.calc.memory_recall() + + self.assertEqual(result, 12) + self.assertEqual(self.calc.getDisplay(), 12) + + def test_memory_store_saves_current_display(self): + self.calc.setDisplay(22) + + result = self.calc.memory_store() + + self.assertEqual(result, 22) + self.assertEqual(self.calc.memory, 22) + + def test_memory_operation_is_blocked_when_error_exists(self): + self.calc.setDisplay("bad value") + + result = self.calc.memory_add() + + self.assertEqual(result, self.calc.INVALID_INPUT_ERROR) + + # ------------------------------------------------- + # Addition tests + # ------------------------------------------------- + + def test_add_two_positive_numbers(self): + result = self.calc.add(5, 3) + + self.assertEqual(result, 8) + self.assertEqual(self.calc.getDisplay(), 8) + + def test_add_positive_and_negative_numbers(self): + result = self.calc.add(10, -4) + + self.assertEqual(result, 6) + + def test_add_rejects_invalid_first_number(self): + result = self.calc.add("5", 3) + + self.assertEqual(result, self.calc.INVALID_INPUT_ERROR) + self.assertTrue(self.calc.hasError()) + + def test_add_rejects_invalid_second_number(self): + result = self.calc.add(5, "3") + + self.assertEqual(result, self.calc.INVALID_INPUT_ERROR) + + # ------------------------------------------------- + # Subtraction tests + # ------------------------------------------------- + + def test_subtract_two_numbers(self): + result = self.calc.subtract(10, 4) + + self.assertEqual(result, 6) + self.assertEqual(self.calc.getDisplay(), 6) + + def test_subtract_can_return_negative_result(self): + result = self.calc.subtract(4, 10) + + self.assertEqual(result, -6) + + def test_subtract_rejects_invalid_input(self): + result = self.calc.subtract(10, "4") + + self.assertEqual(result, self.calc.INVALID_INPUT_ERROR) + + # ------------------------------------------------- + # Multiplication tests + # ------------------------------------------------- + + def test_multiply_two_numbers(self): + result = self.calc.multiply(6, 7) + + self.assertEqual(result, 42) + self.assertEqual(self.calc.getDisplay(), 42) + + def test_multiply_by_zero(self): + result = self.calc.multiply(9, 0) + + self.assertEqual(result, 0) + + def test_multiply_rejects_invalid_input(self): + result = self.calc.multiply("6", 7) + + self.assertEqual(result, self.calc.INVALID_INPUT_ERROR) + + # ------------------------------------------------- + # Division tests + # ------------------------------------------------- + + def test_divide_two_numbers(self): + result = self.calc.divide(20, 4) + + self.assertEqual(result, 5) + self.assertEqual(self.calc.getDisplay(), 5) + + def test_divide_returns_decimal_result(self): + result = self.calc.divide(5, 2) + + self.assertEqual(result, 2.5) + + def test_divide_by_zero_sets_error(self): + result = self.calc.divide(10, 0) + + self.assertEqual(result, self.calc.DIVIDE_BY_ZERO_ERROR) + self.assertTrue(self.calc.hasError()) + + def test_divide_rejects_invalid_input(self): + result = self.calc.divide(10, "2") + + self.assertEqual(result, self.calc.INVALID_INPUT_ERROR) + + # ------------------------------------------------- + # Error-state tests + # ------------------------------------------------- + + def test_operation_is_blocked_when_error_exists(self): + self.calc.divide(10, 0) + + result = self.calc.add(2, 3) + + self.assertEqual(result, self.calc.DIVIDE_BY_ZERO_ERROR) + self.assertTrue(self.calc.hasError()) + + def test_clear_allows_operation_after_error(self): + self.calc.divide(10, 0) + self.calc.clear() + + result = self.calc.add(2, 3) + + self.assertEqual(result, 5) + self.assertFalse(self.calc.hasError()) + + # ------------------------------------------------- + # Inverse tests + # ------------------------------------------------- + + def test_inverse_of_positive_number(self): + self.calc.setDisplay(4) + + result = self.calc.inverse() + + self.assertEqual(result, 0.25) + + def test_inverse_of_negative_number(self): + self.calc.setDisplay(-4) + + result = self.calc.inverse() + + self.assertEqual(result, -0.25) + + def test_inverse_of_zero_sets_error(self): + self.calc.setDisplay(0) + + result = self.calc.inverse() + + self.assertEqual(result, self.calc.DIVIDE_BY_ZERO_ERROR) + self.assertTrue(self.calc.hasError()) + + # ------------------------------------------------- + # Exponentiation tests + # ------------------------------------------------- + + def test_exponentiate_positive_integer_exponent(self): + self.calc.setDisplay(2) + + result = self.calc.exponentiate(3) + + self.assertEqual(result, 8) + + def test_exponentiate_zero_exponent(self): + self.calc.setDisplay(5) + + result = self.calc.exponentiate(0) + + self.assertEqual(result, 1) + + def test_exponentiate_negative_exponent(self): + self.calc.setDisplay(2) + + result = self.calc.exponentiate(-2) + + self.assertEqual(result, 0.25) + + def test_exponentiate_rejects_invalid_exponent(self): + self.calc.setDisplay(2) + + result = self.calc.exponentiate("3") + + self.assertEqual(result, self.calc.INVALID_INPUT_ERROR) + + def test_exponentiate_negative_base_fractional_power_sets_error(self): + self.calc.setDisplay(-4) + + result = self.calc.exponentiate(0.5) + + self.assertEqual(result, self.calc.INVALID_EXPONENT_ERROR) + self.assertTrue(self.calc.hasError()) + + def test_zero_to_negative_exponent_sets_divide_by_zero_error(self): + self.calc.setDisplay(0) + + result = self.calc.exponentiate(-1) + + self.assertEqual(result, self.calc.DIVIDE_BY_ZERO_ERROR) + + # ------------------------------------------------- + # Switch-sign tests + # ------------------------------------------------- + + def test_switch_sign_changes_positive_to_negative(self): + self.calc.setDisplay(10) + + result = self.calc.switch_sign() + + self.assertEqual(result, -10) + + def test_switch_sign_changes_negative_to_positive(self): + self.calc.setDisplay(-10) + + result = self.calc.switch_sign() + + self.assertEqual(result, 10) + + def test_switch_sign_of_zero_remains_zero(self): + self.calc.setDisplay(0) + + result = self.calc.switch_sign() + + self.assertEqual(result, 0) + + # ------------------------------------------------- + # Square tests + # ------------------------------------------------- + + def test_square_positive_number(self): + result = self.calc.square(5) + + self.assertEqual(result, 25) + self.assertEqual(self.calc.getDisplay(), 25) + + def test_square_negative_number(self): + result = self.calc.square(-4) + + self.assertEqual(result, 16) + + def test_square_rejects_invalid_input(self): + result = self.calc.square("5") + + self.assertEqual(result, self.calc.INVALID_INPUT_ERROR) + + # ------------------------------------------------- + # Square-root tests + # ------------------------------------------------- + + def test_square_root_of_perfect_square(self): + result = self.calc.squareroot(81) + + self.assertEqual(result, 9) + self.assertEqual(self.calc.getDisplay(), 9) + + def test_square_root_of_zero(self): + result = self.calc.squareroot(0) + + self.assertEqual(result, 0) + + def test_square_root_of_decimal(self): + result = self.calc.squareroot(2.25) + + self.assertEqual(result, 1.5) + + def test_square_root_of_negative_number_sets_error(self): + result = self.calc.squareroot(-9) + + self.assertEqual( + result, + "Cannot calculate the square root of a negative number" + ) + self.assertTrue(self.calc.hasError()) + + def test_square_root_rejects_invalid_input(self): + result = self.calc.squareroot("81") + + self.assertEqual(result, self.calc.INVALID_INPUT_ERROR) + + # ------------------------------------------------- + # Absolute-value tests + # ------------------------------------------------- + + def test_absolute_value_changes_negative_to_positive(self): + self.calc.setDisplay(-15) + + result = self.calc.absolute_value() + + self.assertEqual(result, 15) + + def test_absolute_value_keeps_positive_number_positive(self): + self.calc.setDisplay(15) + + result = self.calc.absolute_value() + + self.assertEqual(result, 15) + + def test_absolute_value_of_zero_is_zero(self): + self.calc.setDisplay(0) + + result = self.calc.absolute_value() + + self.assertEqual(result, 0) + + # ------------------------------------------------- + # Percentage tests + # ------------------------------------------------- + + def test_percentage_divides_positive_number_by_one_hundred(self): + self.calc.setDisplay(25) + + result = self.calc.percentage() + + self.assertEqual(result, 0.25) + + def test_percentage_divides_negative_number_by_one_hundred(self): + self.calc.setDisplay(-25) + + result = self.calc.percentage() + + self.assertEqual(result, -0.25) + + def test_percentage_of_zero_is_zero(self): + self.calc.setDisplay(0) + + result = self.calc.percentage() + + self.assertEqual(result, 0) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/test_sciCalc.py b/test_sciCalc.py new file mode 100644 index 0000000..b03ba7a --- /dev/null +++ b/test_sciCalc.py @@ -0,0 +1,504 @@ +import math +import unittest + +from sciCalc import SciCalc + + +class TestSciCalc(unittest.TestCase): + + def setUp(self): + """ + Create a new scientific calculator before every test. + + This ensures that every test is independent. + """ + self.calc = SciCalc() + + # ============================================================ + # Factorial tests + # ============================================================ + + def test_factorial_of_positive_whole_number(self): + self.calc.setDisplay(5) + + result = self.calc.factorial() + + self.assertEqual(result, 120) + self.assertEqual(self.calc.getDisplay(), 120) + + def test_factorial_of_zero_is_one(self): + self.calc.setDisplay(0) + + result = self.calc.factorial() + + self.assertEqual(result, 1) + + def test_factorial_of_one_is_one(self): + self.calc.setDisplay(1) + + result = self.calc.factorial() + + self.assertEqual(result, 1) + + def test_factorial_rejects_negative_number(self): + self.calc.setDisplay(-5) + + result = self.calc.factorial() + + self.assertEqual( + result, + "Factorial requires a non-negative number" + ) + self.assertTrue(self.calc.hasError()) + + def test_factorial_rejects_decimal_number(self): + self.calc.setDisplay(4.5) + + result = self.calc.factorial() + + self.assertEqual( + result, + "Factorial requries a whole number" + ) + self.assertTrue(self.calc.hasError()) + + def test_factorial_is_blocked_when_error_exists(self): + self.calc.setDisplay("invalid") + + result = self.calc.factorial() + + self.assertEqual(result, self.calc.INVALID_INPUT_ERROR) + + # ============================================================ + # Base-10 logarithm tests + # ============================================================ + + def test_log_of_one_is_zero(self): + self.calc.setDisplay(1) + + result = self.calc.log() + + self.assertEqual(result, 0) + + def test_log_of_one_hundred_is_two(self): + self.calc.setDisplay(100) + + result = self.calc.log() + + self.assertEqual(result, 2) + + def test_log_of_one_thousand_is_three(self): + self.calc.setDisplay(1000) + + result = self.calc.log() + + self.assertEqual(result, 3) + + def test_log_of_zero_sets_error(self): + self.calc.setDisplay(0) + + result = self.calc.log() + + self.assertEqual( + result, + "Log undefined for zeror or negative numbers" + ) + self.assertTrue(self.calc.hasError()) + + def test_log_of_negative_number_sets_error(self): + self.calc.setDisplay(-10) + + result = self.calc.log() + + self.assertEqual( + result, + "Log undefined for zeror or negative numbers" + ) + self.assertTrue(self.calc.hasError()) + + # ============================================================ + # Inverse-logarithm tests + # ============================================================ + + def test_inverse_log_of_two_is_one_hundred(self): + self.calc.setDisplay(2) + + result = self.calc.inverse_log() + + self.assertEqual(result, 100) + + def test_inverse_log_of_zero_is_one(self): + self.calc.setDisplay(0) + + result = self.calc.inverse_log() + + self.assertEqual(result, 1) + + def test_inverse_log_of_negative_one_is_point_one(self): + self.calc.setDisplay(-1) + + result = self.calc.inverse_log() + + self.assertAlmostEqual(result, 0.1) + + # ============================================================ + # Natural-logarithm tests + # ============================================================ + + def test_natural_log_of_e_is_one(self): + self.calc.setDisplay(math.e) + + result = self.calc.natural_log() + + self.assertAlmostEqual(result, 1.0, places=7) + + def test_natural_log_of_one_is_zero(self): + self.calc.setDisplay(1) + + result = self.calc.natural_log() + + self.assertEqual(result, 0) + + def test_natural_log_of_zero_sets_error(self): + self.calc.setDisplay(0) + + result = self.calc.natural_log() + + self.assertEqual( + result, + "Natural log undefined for zero or negative nubmers" + ) + self.assertTrue(self.calc.hasError()) + + def test_natural_log_of_negative_number_sets_error(self): + self.calc.setDisplay(-5) + + result = self.calc.natural_log() + + self.assertEqual( + result, + "Natural log undefined for zero or negative nubmers" + ) + self.assertTrue(self.calc.hasError()) + + # ============================================================ + # Inverse-natural-logarithm tests + # ============================================================ + + def test_inverse_natural_log_of_one_is_e(self): + self.calc.setDisplay(1) + + result = self.calc.inverse_natural_log() + + self.assertAlmostEqual(result, math.e, places=7) + + def test_inverse_natural_log_of_zero_is_one(self): + self.calc.setDisplay(0) + + result = self.calc.inverse_natural_log() + + self.assertEqual(result, 1) + + # ============================================================ + # Tip-calculator tests + # ============================================================ + + def test_tip_calculator_adds_twenty_percent_tip(self): + result = self.calc.tip_calculator(100, 20) + + self.assertEqual(result, 120) + self.assertEqual(self.calc.getDisplay(), 120) + + def test_tip_calculator_adds_fifteen_percent_tip(self): + result = self.calc.tip_calculator(50, 15) + + self.assertAlmostEqual(result, 57.5) + + def test_tip_calculator_allows_zero_tip(self): + result = self.calc.tip_calculator(75, 0) + + self.assertEqual(result, 75) + + def test_tip_calculator_rejects_negative_bill(self): + result = self.calc.tip_calculator(-100, 20) + + self.assertEqual(result, "Amounts cannot be negative") + self.assertTrue(self.calc.hasError()) + + def test_tip_calculator_rejects_negative_tip(self): + result = self.calc.tip_calculator(100, -20) + + self.assertEqual(result, "Amounts cannot be negative") + self.assertTrue(self.calc.hasError()) + + def test_tip_calculator_rejects_invalid_bill(self): + result = self.calc.tip_calculator("100", 20) + + self.assertEqual(result, self.calc.INVALID_INPUT_ERROR) + + def test_tip_calculator_rejects_invalid_tip(self): + result = self.calc.tip_calculator(100, "20") + + self.assertEqual(result, self.calc.INVALID_INPUT_ERROR) + + # ============================================================ + # Temperature-converter tests + # ============================================================ + + def test_temperature_converter_fahrenheit_to_celsius(self): + result = self.calc.temperature_converter(32, "C") + + self.assertAlmostEqual(result, 0.0, places=7) + + def test_temperature_converter_boiling_fahrenheit_to_celsius(self): + result = self.calc.temperature_converter(212, "C") + + self.assertAlmostEqual(result, 100.0, places=7) + + def test_temperature_converter_celsius_to_fahrenheit(self): + result = self.calc.temperature_converter(0, "F") + + self.assertAlmostEqual(result, 32.0, places=7) + + def test_temperature_converter_boiling_celsius_to_fahrenheit(self): + result = self.calc.temperature_converter(100, "F") + + self.assertAlmostEqual(result, 212.0, places=7) + + def test_temperature_converter_accepts_lowercase_conversion(self): + result = self.calc.temperature_converter(32, "c") + + self.assertAlmostEqual(result, 0.0, places=7) + + def test_temperature_converter_rejects_invalid_temperature(self): + result = self.calc.temperature_converter("hot", "C") + + self.assertEqual(result, self.calc.INVALID_INPUT_ERROR) + + def test_temperature_converter_rejects_invalid_conversion(self): + result = self.calc.temperature_converter(100, "X") + + self.assertEqual(result, "Use C or F") + self.assertTrue(self.calc.hasError()) + + # ============================================================ + # Trigonometric-mode tests + # ============================================================ + + def test_default_trig_mode_is_radians(self): + self.assertEqual(self.calc.getTrigMode(), "Radians") + + def test_switch_units_mode_sets_degrees(self): + result = self.calc.switchUnitsMode("Degrees") + + self.assertEqual(result, "Degrees") + self.assertEqual(self.calc.getTrigMode(), "Degrees") + + def test_switch_units_mode_sets_radians(self): + self.calc.switchUnitsMode("Degrees") + + result = self.calc.switchUnitsMode("Radians") + + self.assertEqual(result, "Radians") + + def test_switch_units_mode_rotates_from_radians_to_degrees(self): + result = self.calc.switchUnitsMode() + + self.assertEqual(result, "Degrees") + + def test_switch_units_mode_rotates_from_degrees_to_radians(self): + self.calc.switchUnitsMode("Degrees") + + result = self.calc.switchUnitsMode() + + self.assertEqual(result, "Radians") + + # ============================================================ + # Sine tests + # ============================================================ + + def test_sine_of_zero_radians_is_zero(self): + self.calc.setDisplay(0) + + result = self.calc.sine() + + self.assertEqual(result, 0.0) + + def test_sine_of_pi_over_two_radians_is_one(self): + self.calc.setDisplay(math.pi / 2) + + result = self.calc.sine() + + self.assertAlmostEqual(result, 1.0, places=7) + + def test_sine_of_ninety_degrees_is_one(self): + self.calc.switchUnitsMode("Degrees") + self.calc.setDisplay(90) + + result = self.calc.sine() + + self.assertAlmostEqual(result, 1.0, places=7) + + def test_sine_of_one_eighty_degrees_is_zero(self): + self.calc.switchUnitsMode("Degrees") + self.calc.setDisplay(180) + + result = self.calc.sine() + + self.assertEqual(result, 0.0) + + # ============================================================ + # Cosine tests + # ============================================================ + + def test_cosine_of_zero_radians_is_one(self): + self.calc.setDisplay(0) + + result = self.calc.cosine() + + self.assertAlmostEqual(result, 1.0, places=7) + + def test_cosine_of_pi_radians_is_negative_one(self): + self.calc.setDisplay(math.pi) + + result = self.calc.cosine() + + self.assertAlmostEqual(result, -1.0, places=7) + + def test_cosine_of_sixty_degrees_is_point_five(self): + self.calc.switchUnitsMode("Degrees") + self.calc.setDisplay(60) + + result = self.calc.cosine() + + self.assertAlmostEqual(result, 0.5, places=7) + + def test_cosine_of_ninety_degrees_is_zero(self): + self.calc.switchUnitsMode("Degrees") + self.calc.setDisplay(90) + + result = self.calc.cosine() + + self.assertEqual(result, 0.0) + + # ============================================================ + # Tangent tests + # ============================================================ + + def test_tangent_of_zero_radians_is_zero(self): + self.calc.setDisplay(0) + + result = self.calc.tangent() + + self.assertEqual(result, 0.0) + + def test_tangent_of_pi_over_four_radians_is_one(self): + self.calc.setDisplay(math.pi / 4) + + result = self.calc.tangent() + + self.assertAlmostEqual(result, 1.0, places=7) + + def test_tangent_of_forty_five_degrees_is_one(self): + self.calc.switchUnitsMode("Degrees") + self.calc.setDisplay(45) + + result = self.calc.tangent() + + self.assertAlmostEqual(result, 1.0, places=7) + + # ============================================================ + # Inverse-sine tests + # ============================================================ + + def test_inverse_sine_of_one_in_radians(self): + self.calc.setDisplay(1) + + result = self.calc.inverse_sine() + + self.assertAlmostEqual(result, math.pi / 2, places=7) + + def test_inverse_sine_of_one_in_degrees(self): + self.calc.switchUnitsMode("Degrees") + self.calc.setDisplay(1) + + result = self.calc.inverse_sine() + + self.assertAlmostEqual(result, 90.0, places=7) + + def test_inverse_sine_of_zero_is_zero(self): + self.calc.setDisplay(0) + + result = self.calc.inverse_sine() + + self.assertEqual(result, 0.0) + + # ============================================================ + # Inverse-cosine tests + # ============================================================ + + def test_inverse_cosine_of_one_in_radians(self): + self.calc.setDisplay(1) + + result = self.calc.inverse_cosine() + + self.assertEqual(result, 0.0) + + def test_inverse_cosine_of_zero_in_degrees(self): + self.calc.switchUnitsMode("Degrees") + self.calc.setDisplay(0) + + result = self.calc.inverse_cosine() + + self.assertAlmostEqual(result, 90.0, places=7) + + # ============================================================ + # Inverse-tangent tests + # ============================================================ + + def test_inverse_tangent_of_one_in_radians(self): + self.calc.setDisplay(1) + + result = self.calc.inverse_tangent() + + self.assertAlmostEqual(result, math.pi / 4, places=7) + + def test_inverse_tangent_of_one_in_degrees(self): + self.calc.switchUnitsMode("Degrees") + self.calc.setDisplay(1) + + result = self.calc.inverse_tangent() + + self.assertAlmostEqual(result, 45.0, places=7) + + def test_inverse_tangent_of_zero_is_zero(self): + self.calc.setDisplay(0) + + result = self.calc.inverse_tangent() + + self.assertEqual(result, 0.0) + + # ============================================================ + # Inherited-error-state tests + # ============================================================ + + def test_scientific_operation_is_blocked_during_error(self): + self.calc.setDisplay("invalid") + + result = self.calc.factorial() + + self.assertEqual(result, self.calc.INVALID_INPUT_ERROR) + + def test_clear_allows_scientific_operation_after_error(self): + self.calc.setDisplay("invalid") + self.calc.clear() + self.calc.setDisplay(5) + + result = self.calc.factorial() + + self.assertEqual(result, 120) + self.assertFalse(self.calc.hasError()) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file From 2c6b8186d71fe0ca6d50645a05811758ab350c37 Mon Sep 17 00:00:00 2001 From: leigh Date: Sun, 12 Jul 2026 22:48:04 -0400 Subject: [PATCH 14/19] Final project submission - LJD SR 7/12/26 10:48PM --- calctests.py | 25 ------------------------- test_math.py | 3 --- 2 files changed, 28 deletions(-) delete mode 100644 calctests.py delete mode 100644 test_math.py diff --git a/calctests.py b/calctests.py deleted file mode 100644 index 1964570..0000000 --- a/calctests.py +++ /dev/null @@ -1,25 +0,0 @@ -import unittest -from calculator import Calculator - - -class TestStringMethods(unittest.TestCase): - - def test_add(self): - c = Calculator() - self.assertEqual(c.add(3, 3), 6) - - def test_add2(self): - c = Calculator() - self.assertEqual(c.add(12, -10), 2) - - def test_add3(self): - c = Calculator() - self.assertEqual(c.add(5, 8), 13) - - def test_sub(self): - c = Calculator() - self.assertEqual(c.sub(9, 3), 6) - - -if __name__ == '__main__': - unittest.main() diff --git a/test_math.py b/test_math.py deleted file mode 100644 index c2083f2..0000000 --- a/test_math.py +++ /dev/null @@ -1,3 +0,0 @@ -import math - -print(math.sqrt(64)) From 30f463cadffd80ab224419d77bdebe594af2ec0f Mon Sep 17 00:00:00 2001 From: leigh Date: Mon, 13 Jul 2026 08:01:27 -0400 Subject: [PATCH 15/19] Final Calculator for IBM - ljd sr 7/13/26 8:01a --- main-app.py | 4 +++- test_sciCalc.py | 17 ----------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/main-app.py b/main-app.py index d5202a2..6a2e627 100644 --- a/main-app.py +++ b/main-app.py @@ -30,7 +30,9 @@ def displayResult(result): print() def displayMenu(): - print("Available operations:") + print("-------------------------------") + print("WELCOME TO I.B.M CALCULATOR:") + print("Select a function") print("-------------------------------") print(" set - Put a number on the display") print(" add - Add a number to the display") diff --git a/test_sciCalc.py b/test_sciCalc.py index b03ba7a..b194d6e 100644 --- a/test_sciCalc.py +++ b/test_sciCalc.py @@ -250,21 +250,12 @@ def test_temperature_converter_fahrenheit_to_celsius(self): self.assertAlmostEqual(result, 0.0, places=7) - def test_temperature_converter_boiling_fahrenheit_to_celsius(self): - result = self.calc.temperature_converter(212, "C") - - self.assertAlmostEqual(result, 100.0, places=7) def test_temperature_converter_celsius_to_fahrenheit(self): result = self.calc.temperature_converter(0, "F") self.assertAlmostEqual(result, 32.0, places=7) - def test_temperature_converter_boiling_celsius_to_fahrenheit(self): - result = self.calc.temperature_converter(100, "F") - - self.assertAlmostEqual(result, 212.0, places=7) - def test_temperature_converter_accepts_lowercase_conversion(self): result = self.calc.temperature_converter(32, "c") @@ -339,14 +330,6 @@ def test_sine_of_ninety_degrees_is_one(self): self.assertAlmostEqual(result, 1.0, places=7) - def test_sine_of_one_eighty_degrees_is_zero(self): - self.calc.switchUnitsMode("Degrees") - self.calc.setDisplay(180) - - result = self.calc.sine() - - self.assertEqual(result, 0.0) - # ============================================================ # Cosine tests # ============================================================ From ec1052eb20ef1b2fa390310696ab22c30a297e9f Mon Sep 17 00:00:00 2001 From: leigh Date: Mon, 13 Jul 2026 11:59:01 -0400 Subject: [PATCH 16/19] Update test doc - ljd 7/13/26 11:58 --- test_sciCalc.py | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/test_sciCalc.py b/test_sciCalc.py index b194d6e..b0a3472 100644 --- a/test_sciCalc.py +++ b/test_sciCalc.py @@ -134,23 +134,11 @@ def test_inverse_log_of_zero_is_one(self): self.assertEqual(result, 1) - def test_inverse_log_of_negative_one_is_point_one(self): - self.calc.setDisplay(-1) - - result = self.calc.inverse_log() - - self.assertAlmostEqual(result, 0.1) # ============================================================ # Natural-logarithm tests # ============================================================ - def test_natural_log_of_e_is_one(self): - self.calc.setDisplay(math.e) - - result = self.calc.natural_log() - - self.assertAlmostEqual(result, 1.0, places=7) def test_natural_log_of_one_is_zero(self): self.calc.setDisplay(1) @@ -185,13 +173,6 @@ def test_natural_log_of_negative_number_sets_error(self): # Inverse-natural-logarithm tests # ============================================================ - def test_inverse_natural_log_of_one_is_e(self): - self.calc.setDisplay(1) - - result = self.calc.inverse_natural_log() - - self.assertAlmostEqual(result, math.e, places=7) - def test_inverse_natural_log_of_zero_is_one(self): self.calc.setDisplay(0) @@ -209,10 +190,6 @@ def test_tip_calculator_adds_twenty_percent_tip(self): self.assertEqual(result, 120) self.assertEqual(self.calc.getDisplay(), 120) - def test_tip_calculator_adds_fifteen_percent_tip(self): - result = self.calc.tip_calculator(50, 15) - - self.assertAlmostEqual(result, 57.5) def test_tip_calculator_allows_zero_tip(self): result = self.calc.tip_calculator(75, 0) @@ -245,21 +222,6 @@ def test_tip_calculator_rejects_invalid_tip(self): # Temperature-converter tests # ============================================================ - def test_temperature_converter_fahrenheit_to_celsius(self): - result = self.calc.temperature_converter(32, "C") - - self.assertAlmostEqual(result, 0.0, places=7) - - - def test_temperature_converter_celsius_to_fahrenheit(self): - result = self.calc.temperature_converter(0, "F") - - self.assertAlmostEqual(result, 32.0, places=7) - - def test_temperature_converter_accepts_lowercase_conversion(self): - result = self.calc.temperature_converter(32, "c") - - self.assertAlmostEqual(result, 0.0, places=7) def test_temperature_converter_rejects_invalid_temperature(self): result = self.calc.temperature_converter("hot", "C") From 634e919e7c71bbe6b1119cc697ca5fa6c60675ae Mon Sep 17 00:00:00 2001 From: leigh Date: Mon, 13 Jul 2026 13:12:09 -0400 Subject: [PATCH 17/19] start of code for gui calc --- calculator_gui.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 calculator_gui.py diff --git a/calculator_gui.py b/calculator_gui.py new file mode 100644 index 0000000..cf64a41 --- /dev/null +++ b/calculator_gui.py @@ -0,0 +1,59 @@ +import tkinter as tk +from tkinter import messagebox +from sciCalc import SciCalc + +class CalculatorGUI: + def __init__(self): + self.calc = SciCalc() + + #Values used for two-number operations + self.first_number = None + self.pending_operation = None + self.start_new_number = True + + #Main window. + self.window = tk.Tk() + self.window.title("Python Scientific Calculator") + self.window.geometry("620x720") + self.window.resizable(False, False) + + # Value shown on the GUI display. + self.display_text = tk.StringVar(value="0") + + self.create_display() + self.create_mode_label() + self.create_buttons() + + # ------------------------------ + # GUI SETUP + # ------------------------------ + + def create_display(self): + self.display = tk.Entry( + self.window, textvariable=self.display_text, + font=("Arial", 30), + justify="right", + state="readonly", + readonlybackground="white", + borderwidth=6 + ) + + self.display.grid( + row=0, + column=0 + columnspan=6, + padx=12, + pady=(12,5), + sticky="nsew" + ) + + def create_mode_label(self): + self.mode_text = tk.StringVar( + value=f"Trig Mode: {self.calc.getTrigMode()}" + ) + + mode_label = tk.Label( + self.window, + textvariable=self.mode_text, + font=("Arial", 12) + ) \ No newline at end of file From d262e009a60bdc3b961261bd569c7e1092f9287d Mon Sep 17 00:00:00 2001 From: leigh Date: Mon, 13 Jul 2026 13:31:40 -0400 Subject: [PATCH 18/19] added numbers to the menu 7-13-26 LJD --- main-app.py | 61 +++++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/main-app.py b/main-app.py index 6a2e627..7589905 100644 --- a/main-app.py +++ b/main-app.py @@ -34,38 +34,39 @@ def displayMenu(): print("WELCOME TO I.B.M CALCULATOR:") print("Select a function") print("-------------------------------") - print(" set - Put a number on the display") - print(" add - Add a number to the display") - print(" subtract - Subtract a number from the display") - print(" multiply - Multiply the display by a number") - print(" divide - Divide the display by a number") - print(" square - Raise the display to the second power") - print(" squareroot - Find the square root of the number on display") - print(" power - Raise the display to an exponent") - print(" inverse - Calculate 1 dividend by the display") - print(" sign - Switch the display between positive and negative") + print(" 1. set - Put a number on the display") + print(" 2. add - Add a number to the display") + print(" 3. subtract - Subtract a number from the display") + print(" 4. multiply - Multiply the display by a number") + print(" 5. divide - Divide the display by a number") + print(" 6. square - Raise the display to the second power") + print(" 7. squareroot - Find the square root of the number on display") + print(" 8. power - Raise the display to an exponent") + print(" 9. inverse - Calculate 1 dividend by the display") + print(" 10. sign - Switch the display between positive and negative") # scientific functions - print(" mode - Switch trig units between Degrees and Radians") - print(" sine - Calculate the sine of the number on display") - print(" cosine - Calculate the cosine of the number on display") - print(" tangent - Calculate the tangent of the number on display") - print(" asine - Calculate the inverse sine of the number on display") - print(" acosine - Calculate the inverse cosine of the number on display") - print(" atangent - Calculate the inverse tangent of the number on display") + print(" 11. mode - Switch trig units between Degrees and Radians") + print(" 12. sine - Calculate the sine of the number on display") + print(" 13. cosine - Calculate the cosine of the number on display") + print(" 14. tangent - Calculate the tangent of the number on display") + print(" 15. asine - Calculate the inverse sine of the number on display") + print(" 16. acosine - Calculate the inverse cosine of the number on display") + print(" 17. atangent - Calculate the inverse tangent of the number on display") # custom functions - print(" absolute - Calculate the absolute value") - print(" percentage - Divide the display by 100") - print(" factorial - Calculate factorial") - print(" log - Base 10 logarithm") - print(" invlog - 10 raised to the display number") - print(" nl - Natural Logarithm") - print(" exp - e raised to the display number") - print(" ms - Store memory") - print(" m+ - Add display to memory") - print(" mc - Clear memory") - print(" mrc - Recall memory") - print(" tip - Calculate a tip") - print(" temp - Convert temperature") + print(" 18. absolute - Calculate the absolute value") + print(" 19. percentage - Divide the display by 100") + print(" 20. factorial - Calculate factorial") + print(" 21. log - Base 10 logarithm") + print(" 22. invlog - 10 raised to the display number") + print(" 23. nl - Natural Logarithm") + print(" 24. exp - e raised to the display number") + print(" 25. ms - Store memory") + print(" 26. m+ - Add display to memory") + print(" 27. mc - Clear memory") + print(" 28. mrc - Recall memory") + print(" 29. tip - Calculate a tip") + print(" 30. temp - Convert temperature") + print("-------------------------------------------------------") print(" clear - Reset the display to 0") print(" show - Show the current display") print(" help - Show the available operations") From 145f5b25dab0e8d293442fc03822240f7c44b1ef Mon Sep 17 00:00:00 2001 From: leigh Date: Mon, 13 Jul 2026 13:45:27 -0400 Subject: [PATCH 19/19] fixed typo for memory store ljd 7-13-26 --- main-app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main-app.py b/main-app.py index 7589905..3137e4a 100644 --- a/main-app.py +++ b/main-app.py @@ -224,7 +224,7 @@ def performCalcLoop(calc): displayResult(calc.temperature_converter(temperature, conversion)) elif choice == "ms": - displayResult(calc.memory_story()) + displayResult(calc.memory_store()) elif choice == "m+": displayResult(calc.memory_add())