Spaces:
Running
Running
| const DEG_TO_RAD = Math.PI / 180 | |
| const OPERATORS = new Set(['+', '-', '×', '÷', 'xʸ', '%']) | |
| const OPERATOR_PRECEDENCE = { '+': 1, '-': 1, '×': 2, '÷': 2, '%': 2, 'xʸ': 3 } | |
| export const formatResult = (value) => { | |
| if (!Number.isFinite(value)) return 'Error' | |
| return String(parseFloat(value.toFixed(10))) | |
| } | |
| const isOperatorToken = (token) => OPERATORS.has(token) | |
| export const isOperatorTail = (expression) => isOperatorToken(expression.trim().split(/\s+/).at(-1)) | |
| export const tokenizeExpression = (expression) => expression.trim().split(/\s+/).filter(Boolean) | |
| const applyOperator = (op, left, right) => { | |
| switch (op) { | |
| case '+': return left + right | |
| case '-': return left - right | |
| case '×': return left * right | |
| case '÷': return right !== 0 ? left / right : NaN | |
| case 'xʸ': return Math.pow(left, right) | |
| case '%': return left % right | |
| default: return NaN | |
| } | |
| } | |
| function shouldPopOperator(operators, token) { | |
| const top = operators.at(-1) | |
| return ( | |
| operators.length > 0 && | |
| isOperatorToken(top) && | |
| OPERATOR_PRECEDENCE[top] >= OPERATOR_PRECEDENCE[token] | |
| ) | |
| } | |
| function handleOperatorToken(token, operators, output) { | |
| while (shouldPopOperator(operators, token)) { | |
| output.push(operators.pop()) | |
| } | |
| operators.push(token) | |
| } | |
| function handleCloseParen(operators, output) { | |
| while (operators.length > 0 && operators.at(-1) !== '(') { | |
| output.push(operators.pop()) | |
| } | |
| if (operators.at(-1) !== '(') throw new Error('Mismatched parentheses') | |
| operators.pop() | |
| } | |
| function drainRemainingOperators(operators, output) { | |
| while (operators.length > 0) { | |
| const op = operators.pop() | |
| if (op === '(' || op === ')') throw new Error('Mismatched parentheses') | |
| output.push(op) | |
| } | |
| } | |
| function toRPN(tokens) { | |
| const output = [] | |
| const operators = [] | |
| tokens.forEach((token) => { | |
| if (!Number.isNaN(Number(token))) { | |
| output.push(Number(token)) | |
| return | |
| } | |
| if (isOperatorToken(token)) { | |
| handleOperatorToken(token, operators, output) | |
| return | |
| } | |
| if (token === '(') { | |
| operators.push(token) | |
| return | |
| } | |
| if (token === ')') { | |
| handleCloseParen(operators, output) | |
| return | |
| } | |
| throw new Error('Invalid token') | |
| }) | |
| drainRemainingOperators(operators, output) | |
| return output | |
| } | |
| function evaluateRPN(rpn) { | |
| const stack = [] | |
| rpn.forEach((token) => { | |
| if (typeof token === 'number') { | |
| stack.push(token) | |
| return | |
| } | |
| const right = stack.pop() | |
| const left = stack.pop() | |
| if (left === undefined || right === undefined) throw new Error('Invalid expression') | |
| stack.push(applyOperator(token, left, right)) | |
| }) | |
| if (stack.length !== 1) throw new Error('Invalid expression') | |
| return stack[0] | |
| } | |
| export const evaluateExpression = (expression) => { | |
| const rpn = toRPN(tokenizeExpression(expression)) | |
| return evaluateRPN(rpn) | |
| } | |
| const factorial = (n) => { | |
| if (n < 0 || !Number.isInteger(n)) return NaN | |
| if (n === 0 || n === 1) return 1 | |
| let r = 1; for (let i = 2; i <= n; i++) r *= i; return r | |
| } | |
| export const SCIENTIFIC_FUNCTIONS = { | |
| 'sin': (v, deg) => Math.sin(deg ? v * DEG_TO_RAD : v), | |
| 'cos': (v, deg) => Math.cos(deg ? v * DEG_TO_RAD : v), | |
| 'tan': (v, deg) => Math.tan(deg ? v * DEG_TO_RAD : v), | |
| 'sin⁻¹': (v, deg) => (deg ? Math.asin(v) / DEG_TO_RAD : Math.asin(v)), | |
| 'cos⁻¹': (v, deg) => (deg ? Math.acos(v) / DEG_TO_RAD : Math.acos(v)), | |
| 'tan⁻¹': (v, deg) => (deg ? Math.atan(v) / DEG_TO_RAD : Math.atan(v)), | |
| 'log': (v) => Math.log10(v), | |
| 'ln': (v) => Math.log(v), | |
| 'log₂': (v) => Math.log2(v), | |
| '√': (v) => Math.sqrt(v), | |
| 'x²': (v) => v * v, | |
| 'x³': (v) => v * v * v, | |
| '1/x': (v) => 1 / v, | |
| 'n!': (v) => factorial(v), | |
| 'eˣ': (v) => Math.exp(v), | |
| '10ˣ': (v) => Math.pow(10, v), | |
| '|x|': (v) => Math.abs(v), | |
| } | |
| export const SCIENTIFIC_CONSTANTS = { | |
| 'π': Math.PI, | |
| 'e': Math.E, | |
| } | |
| export { DEG_TO_RAD } |