import tkinter as tk
from tkinter import ttk
import math
class Calculator(tk.Tk):
def __init__(self):
super().__init__()
self.title("Calculator")
self.resizable(False, False)
# ===== State (otak kalkulator) =====
self.acc = None # nilai yang disimpan sementara (accumulator)
self.pending_op = None # operator yang menunggu: '+', '-', '*', '/'
self.new_entry = True # True = digit berikutnya mulai angka baru
self.last_op = None # untuk perilaku menekan '=' berulang
self.last_operand = None
# Memory
self.memory = 0.0
# ===== UI variables =====
self.expr_var = tk.StringVar(value="")
self.display_var = tk.StringVar(value="0")
self._build_ui()
self._bind_keys()
# ---------- UI ----------
def _build_ui(self):
root = ttk.Frame(self, padding=12)
root.grid(row=0, column=0)
# baris atas (ekspresi kecil)
expr = ttk.Label(root, textvariable=self.expr_var, anchor="e", width=26, font=("Segoe UI", 10))
expr.grid(row=0, column=0, columnspan=4, sticky="ew", pady=(0, 4))
# display besar
disp = ttk.Label(root, textvariable=self.display_var, anchor="e", width=26, font=("Segoe UI", 22))
disp.grid(row=1, column=0, columnspan=4, sticky="ew", pady=(0, 8))
# tombol memory: MC MR M+ M- (MS menyusul di kanan)
self._btn(root, "MC", lambda: self.mem_clear(), row=2, col=0)
self._btn(root, "MR", lambda: self.mem_recall(), row=2, col=1)
self._btn(root, "M+", lambda: self.mem_add(), row=2, col=2)
self._btn(root, "M−", lambda: self.mem_sub(), row=2, col=3)
# baris: % CE C ⌫
self._btn(root, "%", lambda: self.percent(), row=3, col=0)
self._btn(root, "CE", lambda: self.clear_entry(), row=3, col=1)
self._btn(root, "C", lambda: self.clear_all(), row=3, col=2)
self._btn(root, "⌫", lambda: self.backspace(), row=3, col=3)
# baris: 1/x x² √ ÷
self._btn(root, "1/x", lambda: self.unary("inv"), row=4, col=0)
self._btn(root, "x²", lambda: self.unary("sqr"), row=4, col=1)
self._btn(root, "√x", lambda: self.unary("sqrt"), row=4, col=2)
self._btn(root, "÷", lambda: self.op_press("/"), row=4, col=3)
# 7 8 9 ×
self._btn(root, "7", lambda: self.digit("7"), row=5, col=0)
self._btn(root, "8", lambda: self.digit("8"), row=5, col=1)
self._btn(root, "9", lambda: self.digit("9"), row=5, col=2)
self._btn(root, "×", lambda: self.op_press("*"), row=5, col=3)
# 4 5 6 −
self._btn(root, "4", lambda: self.digit("4"), row=6, col=0)
self._btn(root, "5", lambda: self.digit("5"), row=6, col=1)
self._btn(root, "6", lambda: self.digit("6"), row=6, col=2)
self._btn(root, "−", lambda: self.op_press("-"), row=6, col=3)
# 1 2 3 +
self._btn(root, "1", lambda: self.digit("1"), row=7, col=0)
self._btn(root, "2", lambda: self.digit("2"), row=7, col=1)
self._btn(root, "3", lambda: self.digit("3"), row=7, col=2)
self._btn(root, "+", lambda: self.op_press("+"), row=7, col=3)
# +/- 0 . = (besar)
self._btn(root, "±", lambda: self.toggle_sign(), row=8, col=0)
self._btn(root, "0", lambda: self.digit("0"), row=8, col=1)
self._btn(root, ".", lambda: self.dot(), row=8, col=2)
btn_eq = self._btn(root, "=", lambda: self.equals(), row=8, col=3)
btn_eq.configure(style="Accent.TButton")
# style sederhana
style = ttk.Style(self)
try:
style.theme_use("clam")
except:
pass
style.configure("TButton", width=8, padding=8)
style.configure("Accent.TButton", width=8, padding=8)
def _btn(self, parent, text, cmd, row, col):
b = ttk.Button(parent, text=text, command=cmd)
b.grid(row=row, column=col, padx=3, pady=3, sticky="nsew")
return b
def _bind_keys(self):
self.bind("<Return>", lambda e: self.equals())
self.bind("<KP_Enter>", lambda e: self.equals())
self.bind("<BackSpace>", lambda e: self.backspace())
self.bind("<Escape>", lambda e: self.clear_all())
for k in "0123456789":
self.bind(k, lambda e, d=k: self.digit(d))
for k, op in [("+", "+"), ("-", "-"), ("*", "*"), ("/", "/")]:
self.bind(k, lambda e, o=op: self.op_press(o))
self.bind(".", lambda e: self.dot())
# ---------- Helpers ----------
def _get(self) -> float:
s = self.display_var.get().strip()
if s in ("", "-", "."):
return 0.0
return float(s)
def _set(self, value: float):
# format ringkas: hilangkan .0
if math.isfinite(value):
if abs(value - int(value)) < 1e-12:
self.display_var.set(str(int(value)))
else:
self.display_var.set(str(value))
else:
self.display_var.set("Error")
def _compute(self, a: float, b: float, op: str) -> float:
if op == "+": return a + b
if op == "-": return a - b
if op == "*": return a * b
if op == "/":
if b == 0:
raise ZeroDivisionError
return a / b
return b
def _update_expr(self):
if self.acc is None or self.pending_op is None:
self.expr_var.set("")
else:
# contoh: "12 +"
op_map = {"+": "+", "-": "−", "*": "×", "/": "÷"}
self.expr_var.set(f"{self._fmt(self.acc)} {op_map.get(self.pending_op, self.pending_op)}")
def _fmt(self, x: float) -> str:
if abs(x - int(x)) < 1e-12:
return str(int(x))
return str(x)
def _error(self):
self.display_var.set("Error")
self.acc = None
self.pending_op = None
self.new_entry = True
self.last_op = None
self.last_operand = None
self.expr_var.set("")
# ---------- Input angka ----------
def digit(self, d: str):
cur = self.display_var.get()
if self.new_entry or cur == "0" or cur == "Error":
self.display_var.set(d)
self.new_entry = False
else:
self.display_var.set(cur + d)
def dot(self):
cur = self.display_var.get()
if self.new_entry or cur == "Error":
self.display_var.set("0.")
self.new_entry = False
return
if "." not in cur:
self.display_var.set(cur + ".")
def toggle_sign(self):
cur = self.display_var.get()
if cur == "Error":
return
if cur.startswith("-"):
self.display_var.set(cur[1:])
else:
if cur != "0":
self.display_var.set("-" + cur)
# ---------- Operator & equals ----------
def op_press(self, op: str):
if self.display_var.get() == "Error":
return
x = self._get()
if self.acc is None:
self.acc = x
else:
# jika sebelumnya sudah pilih operator dan user sudah input angka baru, hitung dulu
if self.pending_op is not None and not self.new_entry:
try:
self.acc = self._compute(self.acc, x, self.pending_op)
self._set(self.acc)
except Exception:
self._error()
return
self.pending_op = op
self.new_entry = True
self.last_op = None
self.last_operand = None
self._update_expr()
def equals(self):
if self.display_var.get() == "Error":
return
x = self._get()
# Case 1: ada operasi yang menunggu
if self.pending_op is not None and self.acc is not None:
try:
result = self._compute(self.acc, x, self.pending_op)
except Exception:
self._error()
return
# simpan untuk '=' berulang
self.last_op = self.pending_op
self.last_operand = x
self._set(result)
self.acc = result
self.pending_op = None
self.expr_var.set("")
self.new_entry = True
return
# Case 2: '=' berulang (mis. 10 + 2 = = =)
if self.last_op is not None and self.acc is not None and self.last_operand is not None:
try:
result = self._compute(self.acc, self.last_operand, self.last_op)
except Exception:
self._error()
return
self._set(result)
self.acc = result
self.new_entry = True
# ---------- Clear / backspace ----------
def clear_entry(self):
self.display_var.set("0")
self.new_entry = True
def clear_all(self):
self.display_var.set("0")
self.expr_var.set("")
self.acc = None
self.pending_op = None
self.new_entry = True
self.last_op = None
self.last_operand = None
def backspace(self):
cur = self.display_var.get()
if self.new_entry or cur == "Error":
self.display_var.set("0")
self.new_entry = True
return
if len(cur) <= 1 or (len(cur) == 2 and cur.startswith("-")):
self.display_var.set("0")
self.new_entry = True
else:
self.display_var.set(cur[:-1])
# ---------- Unary ops ----------
def unary(self, kind: str):
if self.display_var.get() == "Error":
return
x = self._get()
try:
if kind == "inv":
if x == 0:
raise ZeroDivisionError
y = 1 / x
elif kind == "sqr":
y = x * x
elif kind == "sqrt":
if x < 0:
raise ValueError
y = math.sqrt(x)
else:
return
self._set(y)
self.new_entry = True
except Exception:
self._error()
def percent(self):
# versi sederhana: ubah 50 menjadi 0.5
if self.display_var.get() == "Error":
return
x = self._get()
self._set(x / 100.0)
self.new_entry = True
# ---------- Memory ----------
def mem_clear(self):
self.memory = 0.0
def mem_recall(self):
self._set(self.memory)
self.new_entry = True
def mem_add(self):
if self.display_var.get() == "Error":
return
self.memory += self._get()
self.new_entry = True
def mem_sub(self):
if self.display_var.get() == "Error":
return
self.memory -= self._get()
self.new_entry = True
if __name__ == "__main__":
app = Calculator()
app.mainloop()