Skip to content

Instantly share code, notes, and snippets.

@lhotakj
Created February 20, 2026 21:02
Show Gist options
  • Select an option

  • Save lhotakj/6931b62a6e1a1125edfaa5026cc97e88 to your computer and use it in GitHub Desktop.

Select an option

Save lhotakj/6931b62a6e1a1125edfaa5026cc97e88 to your computer and use it in GitHub Desktop.
python_math_eval.py
import ast
import operator as op
import math
OPS = {
ast.Add: op.add,
ast.Sub: op.sub,
ast.Mult: op.mul,
ast.Div: op.truediv,
ast.FloorDiv: op.floordiv,
ast.Mod: op.mod,
ast.Pow: op.pow,
ast.USub: op.neg,
ast.UAdd: op.pos,
}
CONSTANTS = {
'pi': math.pi,
'e': math.e,
'tau': math.tau,
}
FUNCTIONS = {
'sin': math.sin,
'cos': math.cos,
'tan': math.tan,
'asin': math.asin,
'acos': math.acos,
'atan': math.atan,
'sqrt': math.sqrt,
'pow': math.pow,
'exp': math.exp,
'log': math.log,
'factorial': math.factorial,
'degrees': math.degrees,
'radians': math.radians,
'abs': abs,
'square': lambda x: x * x,
}
def _unparse(node):
if isinstance(node, ast.Constant):
return str(node.value)
if isinstance(node, ast.Name):
return node.id
if isinstance(node, ast.BinOp):
op_map = {
ast.Add: '+', ast.Sub: '-', ast.Mult: '*', ast.Div: '/',
ast.Pow: '**', ast.Mod: '%'
}
if type(node.op) in op_map:
return f"({_unparse(node.left)} {op_map[type(node.op)]} {_unparse(node.right)})"
if isinstance(node, ast.Call):
args = ", ".join([_unparse(arg) for arg in node.args])
func = node.func.id if isinstance(node.func, ast.Name) else str(node.func)
return f"{func}({args})"
return str(node)
def _derive(node, var):
if isinstance(node, ast.Constant):
return "0"
if isinstance(node, ast.Name):
return "1" if node.id == var else "0"
if isinstance(node, ast.BinOp):
u, v = _unparse(node.left), _unparse(node.right)
du, dv = _derive(node.left, var), _derive(node.right, var)
if isinstance(node.op, ast.Add): return f"({du} + {dv})"
if isinstance(node.op, ast.Sub): return f"({du} - {dv})"
if isinstance(node.op, ast.Mult): return f"({du} * {v} + {u} * {dv})"
if isinstance(node.op, ast.Div): return f"(({du} * {v} - {u} * {dv}) / ({v} ** 2))"
if isinstance(node.op, ast.Pow) and isinstance(node.right, ast.Constant):
n = node.right.value
return f"({n} * ({u}) ** ({n-1}) * {du})"
if isinstance(node, ast.Call):
func = node.func.id if isinstance(node.func, ast.Name) else ""
if len(node.args) == 1:
u, du = _unparse(node.args[0]), _derive(node.args[0], var)
if func == 'sin': return f"(cos({u}) * {du})"
if func == 'cos': return f"(-sin({u}) * {du})"
if func == 'exp': return f"(exp({u}) * {du})"
if func == 'sqrt': return f"(0.5 / sqrt({u}) * {du})"
return f"diff({_unparse(node)}, {var})"
def _eval_node(node):
if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
return node.value
if isinstance(node, ast.BinOp) and type(node.op) in OPS:
return OPS[type(node.op)](_eval_node(node.left), _eval_node(node.right))
if isinstance(node, ast.UnaryOp) and type(node.op) in OPS:
return OPS[type(node.op)](_eval_node(node.operand))
if isinstance(node, ast.Name):
if node.id in CONSTANTS: return CONSTANTS[node.id]
raise ValueError(f"Unknown constant: {node.id}")
if isinstance(node, ast.Call):
func = node.func.id if isinstance(node.func, ast.Name) else ""
if func == 'diff' and len(node.args) == 2 and isinstance(node.args[1], ast.Name):
return _derive(node.args[0], node.args[1].id)
if func in FUNCTIONS:
return FUNCTIONS[func](*[_eval_node(a) for a in node.args])
raise ValueError("Unsupported expression")
def safe_eval(expr: str):
try:
node = ast.parse(expr, mode="eval").body
return _eval_node(node)
except Exception as e:
return f"Error: {e}"
if __name__ == "__main__":
expr = input("Enter a numeric expression: ")
print("Result:", safe_eval(expr))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment