-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCalcPro.py
More file actions
343 lines (317 loc) · 14.1 KB
/
CalcPro.py
File metadata and controls
343 lines (317 loc) · 14.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
import re
import math
import os
import sys
# Global configuration
angle_mode = 'radians'
sci_mode = False
calc_history = []
# Color constants
GREEN = "\033[92m"
RED = "\033[91m"
BLUE = "\033[94m"
YELLOW = "\033[93m"
CYAN = "\033[96m"
MAGENTA = "\033[95m"
RESET = "\033[0m"
# Function suggestions and help
suggestions = {
"sin": "sin(angle) — sine of angle",
"cos": "cos(angle) — cosine of angle",
"tan": "tan(angle) — tangent of angle",
"asin": "asin(x) — arc sine of x",
"acos": "acos(x) — arc cosine of x",
"atan": "atan(x) — arc tangent of x",
"sinh": "sinh(x) — hyperbolic sine",
"cosh": "cosh(x) — hyperbolic cosine",
"tanh": "tanh(x) — hyperbolic tangent",
"sqrt": "sqrt(x) — square root of x",
"cbrt": "cbrt(x) — cube root of x (x**(1/3))",
"log": "log(x) — natural logarithm",
"log10": "log10(x) — base-10 logarithm",
"log2": "log2(x) — base-2 logarithm",
"exp": "exp(x) — e raised to power x",
"pi": "pi — constant π (3.14159...)",
"e": "e — Euler's constant (2.71828...)",
"pow": "pow(x,y) — x raised to power y",
"abs": "abs(x) — absolute value of x",
"ceil": "ceil(x) — ceiling function",
"floor": "floor(x) — floor function",
"round": "round(x) — round to nearest integer",
"^": "a ^ b — exponentiation (same as a**b)",
"factorial": "factorial(n) — factorial of n"
}
def print_banner():
"""Display the main calculator banner with options."""
print(f"""
{YELLOW}█▀██████▀█▄▀▀█▀▀▀▀█▀▀▀▄▀█▀▀▄▀█▄▀▀▄▀█▀▀▄▀▀▄▀██▀██▀███████{RESET}
{YELLOW}█▀███████████████▀▀██████████▄▀▀▄▀████████▄▄████████████{RESET}
{CYAN}
░█████╗░░█████╗░██╗░░░░░░█████╗░██████╗░██████╗░░█████╗░
██╔══██╗██╔══██╗██║░░░░░██╔══██╗██╔══██╗██╔══██╗██╔══██╗
██║░░╚═╝███████║██║░░░░░██║░░╚═╝██████╔╝██████╔╝██║░░██║
██║░░██╗██╔══██║██║░░░░░██║░░██╗██╔═══╝░██╔══██╗██║░░██║
╚█████╔╝██║░░██║███████╗╚█████╔╝██║░░░░░██║░░██║╚█████╔╝
░╚════╝░╚═╝░░╚═╝╚══════╝░╚════╝░╚═╝░░░░░╚═╝░░╚═╝░╚════╝░
{RESET}
{YELLOW}█▀███████████████▀▀█▀▄█▀███▀▀▀█▀▄█▀█▀█▄▀▄▀█▄▀▀█▀▀▀▀█▀▄▄▀{RESET}
{YELLOW}█▀█████████████▀▀█▀▄▀▄▀█▀▀▀▀███████████▀▀▀█▀▄█▀▄████████{RESET}
{BLUE}# OPERATION MODES:{RESET}
{CYAN}[1] Addition [2] Subtraction [3] Multiplication [4] Division [5] Exponentiation
[6] Basic Mode [7] Expression Mode [8] Scientific Mode{RESET}
{BLUE}# ADVANCED OPTIONS:{RESET}
{CYAN}[h] History [c] Clear History [?] Help/Functions [m] Toggle Deg/Rad [s] Sci-Notation
[cl] Clear Screen [demo] Demo Mode [x] Exit{RESET}
{GREEN}# Expression Examples:{RESET} 3+5*2, 2^3, sqrt(16), sin(90), log(100), pi*2
{GREEN}Enter expressions directly or use numbered options above^^{RESET}
""")
def get_input():
"""Get two numbers from user with error handling."""
try:
a = float(input(f"{YELLOW}# Enter First Number: {RESET}"))
b = float(input(f"{YELLOW}# Enter Second Number: {RESET}"))
return a, b
except ValueError:
print(f"{RED}# Invalid input. Please enter numeric values!{RESET}")
return None, None
def wrap_trig(fn):
"""Wrap trigonometric functions to handle degree/radian mode."""
return lambda x: fn(math.radians(x)) if angle_mode == 'degrees' else fn(x)
def format_result(value):
"""Format result based on scientific notation setting."""
if sci_mode:
return f"{value:.6e}"
elif isinstance(value, float) and value.is_integer():
return str(int(value))
else:
return str(value)
def is_parentheses_balanced(expr):
"""Check if parentheses are balanced in expression."""
stack = []
for char in expr:
if char == '(': stack.append(char)
elif char == ')':
if not stack:
return False
stack.pop()
return not stack
def factorial(n):
"""Calculate factorial of n."""
if n < 0:
raise ValueError("# Factorial not defined for negative numbers!")
return math.factorial(int(n))
def cbrt(x):
"""Calculate cube root of x."""
return x ** (1/3) if x >= 0 else -((-x) ** (1/3))
def safe_eval(expr, allowed_names, max_depth=100):
"""Safely evaluate basic mathematical expressions with custom function support."""
import ast
# Set recursion limit to prevent DoS attacks
old_limit = sys.getrecursionlimit()
sys.setrecursionlimit(max_depth)
try:
# Explicitly list all allowed AST node types
allowed_nodes = (
ast.Expression, ast.BinOp, ast.UnaryOp, ast.Load, ast.Call, ast.Name,
# Explicitly list operators
ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Pow, ast.Mod, ast.USub, ast.UAdd,
ast.FloorDiv, # Floor division
# For Python 3.8+
ast.Constant,
# For Python < 3.8
ast.Num
)
class SafeEval(ast.NodeVisitor):
def visit(self, node):
if not isinstance(node, allowed_nodes):
raise ValueError(f"Unsafe node: {type(node).__name__}")
return super().visit(node)
def visit_Name(self, node):
if node.id not in allowed_names:
raise NameError(f"Unknown function or constant: {node.id}")
def visit_Call(self, node):
if not isinstance(node.func, ast.Name) or node.func.id not in allowed_names:
raise NameError(f"Unknown function: {getattr(node.func,'id',None)}")
self.generic_visit(node)
tree = ast.parse(expr, mode='eval')
SafeEval().visit(tree)
return eval(compile(tree, filename="<ast>", mode="eval"), {"__builtins__": None}, allowed_names)
finally:
# Restore original recursion limit
sys.setrecursionlimit(old_limit)
def evaluate_expression(expr):
"""Evaluate mathematical expression safely with extended functions."""
# Pre-process 'x' to '*' for validation
validation_expr = expr.replace('x', '*')
# Tightened regex - only allow safe mathematical characters
if not re.match(r'^[0-9a-zA-Z+\-*/.^() ]*$', validation_expr):
print(f"{RED}# Error: Invalid characters in expression!{RESET}")
return
if not is_parentheses_balanced(expr):
print(f"{RED}# Error: Unmatched parentheses in expression!{RESET}")
return
try:
# Replace common operators
expr = expr.replace('x', '*').replace('^', '**')
# Extended allowed functions
allowed_names = {
# Basic math
"sqrt": math.sqrt, "pow": pow, "abs": abs,
"ceil": math.ceil, "floor": math.floor, "round": round,
"factorial": factorial, "cbrt": cbrt,
# Trigonometric functions
"sin": wrap_trig(math.sin), "cos": wrap_trig(math.cos), "tan": wrap_trig(math.tan),
"asin": wrap_trig(math.asin), "acos": wrap_trig(math.acos), "atan": wrap_trig(math.atan),
"sinh": math.sinh, "cosh": math.cosh, "tanh": math.tanh,
# Logarithmic and exponential
"log": math.log, "log10": math.log10, "log2": math.log2, "exp": math.exp,
# Constants
"pi": math.pi, "e": math.e
}
result = safe_eval(expr, allowed_names)
display = format_result(result)
print(f"{GREEN}# Result: {display}{RESET}")
calc_history.append(f"{expr} = {display}")
except ZeroDivisionError:
print(f"{RED}# Error: Cannot divide by zero!{RESET}")
except ValueError as e:
print(f"{RED}# Error: {e}{RESET}")
except NameError as e:
print(f"{RED}# Error: Unknown function or variable! {e}{RESET}")
except SyntaxError:
print(f"{RED}# Error: Invalid syntax in expression!{RESET}")
except RecursionError:
print(f"{RED}# Error: Expression too complex (recursion depth exceeded)!{RESET}")
except Exception as e:
print(f"{RED}# Error: {e}{RESET}")
def basic_operation(op_name, a, b, operation):
"""Perform basic operation with formatting."""
try:
result = operation()
print(f"{GREEN}# Result: {a} {op_name} {b} = {format_result(result)}{RESET}")
calc_history.append(f"{a} {op_name} {b} = {format_result(result)}")
except ZeroDivisionError:
print(f"{RED}# Error: Cannot divide by zero!{RESET}")
except Exception as e:
print(f"{RED}# Error: {e}{RESET}")
def demo_mode():
"""Run demonstration of calculator features."""
print(f"{CYAN}=== CALCULATOR DEMO MODE ==={RESET}")
demo_expressions = [
"2 + 3 * 4",
"sqrt(16) + 2^3",
"sin(90) + cos(0)" if angle_mode == 'degrees' else "sin(pi/2) + cos(0)",
"log10(100) + log(e)",
"factorial(5) / 10",
"pi * 2"
]
for expr in demo_expressions:
print(f"{YELLOW}# Demo: {expr}{RESET}")
evaluate_expression(expr)
print()
def handle_command(cmd):
"""Handle user commands and operations."""
global angle_mode, sci_mode
if cmd == '1': # Addition
a, b = get_input()
if a is not None:
basic_operation('+', a, b, lambda: a + b)
elif cmd == '2': # Subtraction
a, b = get_input()
if a is not None:
basic_operation('-', a, b, lambda: a - b)
elif cmd == '3': # Multiplication
a, b = get_input()
if a is not None:
basic_operation('*', a, b, lambda: a * b)
elif cmd == '4': # Division
a, b = get_input()
if a is not None:
basic_operation('/', a, b, lambda: a / b)
elif cmd == '5': # Exponentiation
a, b = get_input()
if a is not None:
basic_operation('^', a, b, lambda: a ** b)
elif cmd == '6': # Basic Mode
print(f"{CYAN}=== BASIC CALCULATOR MODE ==={RESET}")
print("# Select operation: [1]+ [2]- [3]* [4]/ [5]^ [back] Return :")
while True:
sub_cmd = input(f"{YELLOW}# Basic: {RESET}").strip()
if sub_cmd == 'back':
break
elif sub_cmd in ['1', '2', '3', '4', '5']:
handle_command(sub_cmd)
else:
print(f"{RED}# Invalid option. Use 1-5 or 'back'{RESET}")
elif cmd == '7': # Expression Mode
print(f"{CYAN}=== EXPRESSION CALCULATOR MODE ==={RESET}")
print("# Enter mathematical expressions (type 'back' to return):")
while True:
expr = input(f"{YELLOW}Expression>> {RESET}").strip()
if expr.lower() == 'back':
break
evaluate_expression(expr)
elif cmd == '8': # Scientific Mode
print(f"{CYAN}=== SCIENTIFIC CALCULATOR MODE ==={RESET}")
print("# Advanced functions available. Type '?' for help or 'back' to return:")
while True:
expr = input(f"{YELLOW}Scientific>>> {RESET}").strip()
if expr.lower() == 'back':
break
elif expr == '?':
print(f"{GREEN}# Scientific Functions:{RESET}")
for k, v in suggestions.items():
print(f"{CYAN}- {v}{RESET}")
else:
evaluate_expression(expr)
elif cmd == 'h': # History
if not calc_history:
print(f"{YELLOW}# No calculation history yet.{RESET}")
else:
print(f"{GREEN}=== CALCULATION HISTORY ==={RESET}")
for i, h in enumerate(calc_history, 1):
print(f"{CYAN}{i:2d}. {h}{RESET}")
elif cmd == 'c': # Clear History
calc_history.clear()
print(f"{GREEN}# History cleared.{RESET}")
elif cmd == 'cl': # Clear Screen
os.system('cls' if os.name == 'nt' else 'clear')
print_banner()
elif cmd == '?': # Help
print(f"{GREEN}=== AVAILABLE FUNCTIONS ==={RESET}")
for k, v in suggestions.items():
print(f"{CYAN}- {v}{RESET}")
elif cmd == 'm': # Toggle Angle Mode
angle_mode = 'degrees' if angle_mode == 'radians' else 'radians'
print(f"{GREEN}# Angle mode set to: {YELLOW}{angle_mode}{RESET}")
elif cmd == 's': # Toggle Scientific Notation
sci_mode = not sci_mode
print(f"{GREEN}# Scientific notation: {YELLOW}{'ON' if sci_mode else 'OFF'}{RESET}")
elif cmd == 'demo': # Demo Mode
demo_mode()
elif cmd == 'x': # Exit
print(f"{CYAN}# Exiting Calculator. See you next time. Goodbye! :){RESET}")
return False
else: # Treat as expression
evaluate_expression(cmd)
return True
def main():
"""Main calculator function."""
os.system('cls' if os.name == 'nt' else 'clear')
print_banner()
print(f"{GREEN}# Calculator started successfully! All features available.{RESET}")
print(f"{YELLOW}# Current settings: Angle mode = {angle_mode}, Scientific notation = {'ON' if sci_mode else 'OFF'}{RESET}\n")
while True:
try:
cmd = input(f"{BLUE}Calculator> {RESET}").strip()
if not cmd:
continue
if not handle_command(cmd):
break
except KeyboardInterrupt:
print(f"\n{CYAN}# Exiting Calculator. Goodbye! :){RESET}")
break
except Exception as e:
print(f"{RED}# Unexpected error: {e}{RESET}")
if __name__ == '__main__':
main()