Search code examples
pythontkintercalculatortrigonometry

Trigonometry in python calculator


I'm writing a scientific calculator as part of a project, but when I click the "=" sign, 6 functions (coth, acot, sec, sech, asec and acsc) display an error message in the calculation field instead of the result. The rest displays the correct result in radians. I can only use the math module, so I tried to create equivalents of the given functions using inversions of other running ones. Unfortunately, this solution only worked in my case 1/3 of the time. I am attaching a code snippet containing trigonometry, numbers and basic mathematical operations.

import tkinter as tk
import math

class ScientificCalculator:
    def __init__(self, root):
        self.root = root
        root.title("Kalkulator Naukowy")
        self.expression = ""
        self.max_history_lines = 10
        self.memory = 0

        self.display = tk.Entry(root, width=40, font=('Arial', 20), borderwidth=5)
        self.display.grid(row=0, column=0, columnspan=7, pady=(20, 10))

        buttons = [
            "7", "8", "9", "+", "sin(", "sinh(", "asin(",
            "4", "5", "6", "-", "cos(", "cosh(", "acos(",
            "1", "2", "3", "*", "tan(", "tanh(", "atan(",
            "0", ".", "(", ")", "=", "AC", "C", "cot(", 
            "coth(", "acot(", "sec(", "sech(", "asec(", "csc(", "csch(",
            "acsc("
        ]

        row_val = 1
        col_val = 0

        for button in buttons:
            if button == "=":
                tk.Button(root, text=button, padx=30, pady=20, command=lambda b=button: self.button_click(b), font=('Arial', 14, 'bold'), bg='#082e79', fg='white').grid(row=row_val, column=col_val, columnspan=2, sticky='nsew')
                col_val += 2
            elif button in ["C", "AC"]:
                tk.Button(root, text=button, padx=30, pady=20, command=lambda b=button: self.button_click(b), font=('Arial', 14, 'bold'), bg='#082e79', fg='white').grid(row=row_val, column=col_val, columnspan=2, sticky='nsew')
                col_val += 2
            else:
                tk.Button(root, text=button, padx=20, pady=20, command=lambda b=button: self.button_click(b), font=('Arial', 14, 'bold'), bg='#666666', fg='white').grid(row=row_val, column=col_val, padx=1, pady=1, sticky='nsew')
                col_val += 1

            if col_val > 6:
                col_val = 0
                row_val += 1

        self.history_text = tk.Text(root, height=10, width=40)
        self.history_text.grid(row=10, column=0, columnspan=7, padx=2, pady=2, sticky='nsew')

        self.history = []

    def button_click(self, button):
        if button == "=":
            try:
                self.expression = self.expression.replace("cot(", "1/math.tan(")  
                self.expression = self.expression.replace("coth(", "1/math.tanh(")  
                self.expression = self.expression.replace("acot(", "1/math.atan(")  
                self.expression = self.expression.replace("sec(", "1/math.cos(")
                self.expression = self.expression.replace("sech(", "1/math.cosh(")
                self.expression = self.expression.replace("asec(", "1/math.acos(")
                self.expression = self.expression.replace("csc(", "1/math.sin(")
                self.expression = self.expression.replace("csch(", "1/math.sinh(")
                self.expression = self.expression.replace("acsc(", "1/math.asin(")
                result = str(eval(self.expression))
                history_expression = self.expression
                self.history.insert(0, history_expression + " = " + result)
                self.display.delete(0, tk.END)
                self.display.insert(0, result)
                self.update_history()
                self.expression = result
            except Exception:
                self.display.delete(0, tk.END)
                self.display.insert(0, "Błąd")
        elif button == "C":
            self.expression = ""
            self.display.delete(0, tk.END)
        elif button == "AC":
            self.memory = 0
            self.expression = ""
            self.display.delete(0, tk.END)
            self.history = [] 
            self.update_history()
        else:
            if button in ["sin(", "cos(", "tan(", "sinh(", "cosh(", "tanh(", "asin(", "acos(", "atan("]:
                self.expression += "math." + button
            else:
                self.expression += button
            self.display.insert(tk.END, button)

    def update_history(self):
        if len(self.history) > self.max_history_lines:
            self.history = self.history[:self.max_history_lines]
        self.history_text.delete(1.0, tk.END)
        for item in self.history:
            self.history_text.insert(tk.END, item + "\n")

if __name__ == "__main__":
    root = tk.Tk()
    calc = ScientificCalculator(root)
    root.geometry("750x730")
    root.configure(bg="#bfbfbf")
    root.mainloop()

Solution

  • The first steps to solving this issue are printing a more detailed error message. Catching the Exception tells us there is a problem, not what problem is happening.

    We can know that exactly by making a modification like so:

            # ...
            self.expression = result
        except Exception as e:
            self.display.delete(0, tk.END)
            self.display.insert(0, "Błąd")
            print("Exception:", e)
            # ...
    

    Adding this, and trying to evaluate acsc(-0.5), I get an error message:

    Exception: name 'a1' is not defined
    

    That name, a1 is not present anywhere in the given snippet, so let's dig deeper:

    I add a line inside button_click that prints the exact expression that's being evaluated:

        # ...
        self.expression = self.expression.replace("acsc(", "1/math.asin(")
        print("Expression before eval:",self.expression)
        result = str(eval(self.expression))
        # ...
    

    Running the code again with the same input, the problem becomes clear immediately:

    Expression before eval: a1/math.sin(-0.5)
    Exception: name 'a1' is not defined
    

    acsc(-0.5) should have become 1/math.asin(-0.5). Instead, it became a1/math.sin(-0.5). This is the hint we need to finally solve the problem. Let's look at the part of the code that is supposed to do the replacement:

        # ...
        self.expression = self.expression.replace("csc(", "1/math.sin(")
        self.expression = self.expression.replace("csch(", "1/math.sinh(")
        self.expression = self.expression.replace("acsc(", "1/math.asin(")
    

    Python executes things from top to bottom. The first replacement happens before the last one.

    Thus, the csc(-0.5) part from acsc(-0.5) gets substituted, and the rest of the replacements don't do anything.

    We can finally solve the problem by changing the order.

        self.expression = self.expression.replace("acot(", "1/math.atan(")  
        self.expression = self.expression.replace("cot(", "1/math.tan(")  
        self.expression = self.expression.replace("coth(", "1/math.tanh(")  
        self.expression = self.expression.replace("asec(", "1/math.acos(")
        self.expression = self.expression.replace("sec(", "1/math.cos(")
        self.expression = self.expression.replace("sech(", "1/math.cosh(")
        self.expression = self.expression.replace("acsc(", "1/math.asin(")
        self.expression = self.expression.replace("csc(", "1/math.sin(")
        self.expression = self.expression.replace("csch(", "1/math.sinh(")
    

    Making this change, when I enter acsc(-0.5), into the new program, I get the value as -1.9098593171027438 which is the correct and expected output.

    I hope this was helpful for you to understand about the issue you faced, and how you can approach issues in the future.