Gateprep / frontend /src /utils /calculatorEngine.js
banu4prasad's picture
refactor: math, result, calci
a64ef1f
Raw
History Blame Contribute Delete
3.94 kB
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 }