Spaces:
Running
Running
| class ScientificCalculator { | |
| constructor(previousOperandTextElement, currentOperandTextElement) { | |
| this.previousOperandTextElement = previousOperandTextElement; | |
| this.currentOperandTextElement = currentOperandTextElement; | |
| this.angleMode = 'DEG'; // DEG or RAD | |
| this.shiftMode = false; | |
| this.alphaMode = false; | |
| this.lastAnswer = null; | |
| this.historyListElement = document.getElementById('history-list'); | |
| this.clear(); | |
| } | |
| clear() { | |
| this.currentOperand = '0'; | |
| this.expression = ''; | |
| this.resetScreen = false; | |
| this.updateDisplay(); | |
| } | |
| delete() { | |
| if (this.currentOperand === '0' || this.resetScreen) { | |
| this.currentOperand = '0'; | |
| this.resetScreen = false; | |
| return; | |
| } | |
| this.currentOperand = this.currentOperand.toString().slice(0, -1); | |
| if (this.currentOperand === '') this.currentOperand = '0'; | |
| this.updateDisplay(); | |
| } | |
| appendNumber(number) { | |
| if (this.resetScreen) { | |
| this.currentOperand = number.toString(); | |
| this.resetScreen = false; | |
| } else { | |
| if (this.currentOperand === '0' && number !== '.') { | |
| this.currentOperand = number.toString(); | |
| } else { | |
| this.currentOperand = this.currentOperand.toString() + number.toString(); | |
| } | |
| } | |
| this.updateDisplay(); | |
| } | |
| appendOperator(operator) { | |
| if (this.currentOperand === '' && this.expression === '') return; | |
| // Convert current to expression if we have one | |
| if (this.currentOperand !== '') { | |
| if (this.expression !== '') { | |
| this.expression += this.currentOperand; | |
| } else { | |
| this.expression = this.currentOperand; | |
| } | |
| this.currentOperand = ''; | |
| } | |
| // Add operator | |
| this.expression += ' ' + operator + ' '; | |
| this.updateDisplay(); | |
| } | |
| appendFunction(func) { | |
| if (this.resetScreen) { | |
| this.currentOperand = ''; | |
| this.resetScreen = false; | |
| } | |
| if (this.currentOperand === '0') { | |
| this.currentOperand = func + '('; | |
| } else { | |
| this.currentOperand += func + '('; | |
| } | |
| this.updateDisplay(); | |
| } | |
| appendConstant(constant) { | |
| if (this.resetScreen) { | |
| this.currentOperand = ''; | |
| this.resetScreen = false; | |
| } | |
| let value; | |
| switch(constant) { | |
| case 'pi': | |
| value = Math.PI; | |
| break; | |
| case 'e': | |
| value = Math.E; | |
| break; | |
| default: | |
| return; | |
| } | |
| if (this.currentOperand === '0') { | |
| this.currentOperand = value.toString(); | |
| } else { | |
| this.currentOperand += value.toString(); | |
| } | |
| this.updateDisplay(); | |
| } | |
| negate() { | |
| if (this.currentOperand === '0') return; | |
| if (this.currentOperand.startsWith('-')) { | |
| this.currentOperand = this.currentOperand.substring(1); | |
| } else { | |
| this.currentOperand = '-' + this.currentOperand; | |
| } | |
| this.updateDisplay(); | |
| } | |
| toRadians(degrees) { | |
| return degrees * (Math.PI / 180); | |
| } | |
| toDegrees(radians) { | |
| return radians * (180 / Math.PI); | |
| } | |
| computeTrig(func, value) { | |
| const radians = this.angleMode === 'DEG' ? this.toRadians(value) : value; | |
| switch(func) { | |
| case 'sin': | |
| return Math.sin(radians); | |
| case 'cos': | |
| return Math.cos(radians); | |
| case 'tan': | |
| return Math.tan(radians); | |
| default: | |
| return NaN; | |
| } | |
| } | |
| computeInverseTrig(func, value) { | |
| let result; | |
| switch(func) { | |
| case 'asin': | |
| result = Math.asin(value); | |
| break; | |
| case 'acos': | |
| result = Math.acos(value); | |
| break; | |
| case 'atan': | |
| result = Math.atan(value); | |
| break; | |
| default: | |
| return NaN; | |
| } | |
| return this.angleMode === 'DEG' ? this.toDegrees(result) : result; | |
| } | |
| factorial(n) { | |
| if (n < 0) return NaN; | |
| if (n === 0 || n === 1) return 1; | |
| if (n > 170) return Infinity; // Prevent overflow | |
| let result = 1; | |
| for (let i = 2; i <= n; i++) { | |
| result *= i; | |
| } | |
| return result; | |
| } | |
| evaluateExpression(expr) { | |
| try { | |
| // Replace mathematical symbols with JS equivalents | |
| let evalExpr = expr | |
| .replace(/×/g, '*') | |
| .replace(/÷/g, '/') | |
| .replace(/−/g, '-') | |
| .replace(/sin\(/g, 'Math.sin(') | |
| .replace(/cos\(/g, 'Math.cos(') | |
| .replace(/tan\(/g, 'Math.tan(') | |
| .replace(/log\(/g, 'Math.log10(') | |
| .replace(/ln\(/g, 'Math.log(') | |
| .replace(/√\(/g, 'Math.sqrt(') | |
| .replace(/π/g, Math.PI) | |
| .replace(/e(?![x])/g, Math.E); | |
| return eval(evalExpr); | |
| } catch (e) { | |
| return NaN; | |
| } | |
| } | |
| compute() { | |
| let fullExpression = this.expression; | |
| // Add current operand if exists | |
| if (this.currentOperand !== '') { | |
| fullExpression += this.currentOperand; | |
| } | |
| if (fullExpression === '') return; | |
| // Handle special functions | |
| let result; | |
| // Check for standalone trig functions | |
| const trigMatch = fullExpression.match(/^(sin|cos|tan)\(([^)]+)\)$/); | |
| if (trigMatch) { | |
| const value = parseFloat(trigMatch[2]); | |
| result = this.computeTrig(trigMatch[1], value); | |
| } | |
| // Check for square | |
| else if (fullExpression.startsWith('x²(')) { | |
| const value = parseFloat(fullExpression.slice(4, -1)); | |
| result = value * value; | |
| } | |
| // Check for cube | |
| else if (fullExpression.startsWith('x³(')) { | |
| const value = parseFloat(fullExpression.slice(4, -1)); | |
| result = value * value * value; | |
| } | |
| // Check for square root | |
| else if (fullExpression.startsWith('√(')) { | |
| const value = parseFloat(fullExpression.slice(3, -1)); | |
| result = Math.sqrt(value); | |
| } | |
| // Check for power (x^y format) | |
| else if (fullExpression.includes('^')) { | |
| const parts = fullExpression.split('^'); | |
| const base = parseFloat(parts[0]); | |
| const exponent = parseFloat(parts[1]); | |
| result = Math.pow(base, exponent); | |
| } | |
| // Check for factorial | |
| else if (fullExpression.startsWith('x!(')) { | |
| const value = parseFloat(fullExpression.slice(4, -1)); | |
| result = this.factorial(value); | |
| } | |
| // Check for log | |
| else if (fullExpression.startsWith('log(')) { | |
| const value = parseFloat(fullExpression.slice(5, -1)); | |
| result = Math.log10(value); | |
| } | |
| // Check for natural log | |
| else if (fullExpression.startsWith('ln(')) { | |
| const value = parseFloat(fullExpression.slice(4, -1)); | |
| result = Math.log(value); | |
| } | |
| // Check for absolute value | |
| else if (fullExpression.startsWith('Abs(')) { | |
| const value = parseFloat(fullExpression.slice(5, -1)); | |
| result = Math.abs(value); | |
| } | |
| // Check for power of 10 | |
| else if (fullExpression.startsWith('10^')) { | |
| const value = parseFloat(fullExpression.slice(4)); | |
| result = Math.pow(10, value); | |
| } | |
| // Check for exponential (e^x) | |
| else if (fullExpression.startsWith('e^')) { | |
| const value = parseFloat(fullExpression.slice(3)); | |
| result = Math.exp(value); | |
| } | |
| // Regular expression evaluation | |
| else { | |
| result = this.evaluateExpression(fullExpression); | |
| } | |
| // Handle precision | |
| if (!isNaN(result) && isFinite(result)) { | |
| result = Math.round(result * 1000000000000) / 1000000000000; | |
| // Add to history | |
| this.addToHistory(fullExpression, result); | |
| this.lastAnswer = result; | |
| this.currentOperand = result.toString(); | |
| this.expression = ''; | |
| this.resetScreen = true; | |
| } else { | |
| this.currentOperand = 'Error'; | |
| this.resetScreen = true; | |
| } | |
| this.updateDisplay(); | |
| } | |
| updateDisplay() { | |
| let displayValue = this.currentOperand || '0'; | |
| // Truncate if too long | |
| if (displayValue.length > 15) { | |
| const num = parseFloat(displayValue); | |
| if (!isNaN(num)) { | |
| displayValue = num.toExponential(6); | |
| } else { | |
| displayValue = displayValue.substring(0, 15); | |
| } | |
| } | |
| this.currentOperandTextElement.innerText = displayValue; | |
| this.previousOperandTextElement.innerText = this.expression; | |
| } | |
| addToHistory(expression, result) { | |
| const historyContainer = document.getElementById('history-list'); | |
| if (historyContainer.querySelector('.italic')) { | |
| historyContainer.innerHTML = ''; | |
| } | |
| const historyItem = document.createElement('div'); | |
| historyItem.className = 'history-item'; | |
| historyItem.innerHTML = ` | |
| <div class="history-expression">${expression} =</div> | |
| <div class="history-result">${result}</div> | |
| `; | |
| historyItem.addEventListener('click', () => { | |
| this.currentOperand = result.toString(); | |
| this.resetScreen = true; | |
| this.updateDisplay(); | |
| }); | |
| historyContainer.prepend(historyItem); | |
| if (historyContainer.children.length > 20) { | |
| historyContainer.lastElementChild.remove(); | |
| } | |
| } | |
| clearHistory() { | |
| const historyContainer = document.getElementById('history-list'); | |
| historyContainer.innerHTML = '<div class="text-center text-gray-600 text-xs italic">No calculations</div>'; | |
| } | |
| toggleAngleMode() { | |
| this.angleMode = this.angleMode === 'DEG' ? 'RAD' : 'DEG'; | |
| document.getElementById('angle-mode').innerText = this.angleMode; | |
| document.getElementById('toggle-angle').innerText = | |
| this.angleMode === 'DEG' ? 'Switch to RAD' : 'Switch to DEG'; | |
| } | |
| toggleShift() { | |
| this.shiftMode = !this.shiftMode; | |
| document.querySelector('.shift-btn').classList.toggle('pressed', this.shiftMode); | |
| } | |
| } | |
| // DOM Elements | |
| const previousOperandTextElement = document.getElementById('previous-operand'); | |
| const currentOperandTextElement = document.getElementById('current-operand'); | |
| const calculator = new ScientificCalculator(previousOperandTextElement, currentOperandTextElement); | |
| // Number buttons | |
| document.querySelectorAll('[data-number]').forEach(button => { | |
| button.addEventListener('click', () => { | |
| calculator.appendNumber(button.innerText); | |
| }); | |
| }); | |
| // Operator buttons | |
| document.querySelectorAll('[data-operation]').forEach(button => { | |
| button.addEventListener('click', () => { | |
| calculator.appendOperator(button.innerText); | |
| }); | |
| }); | |
| // Function buttons | |
| document.querySelectorAll('[data-function]').forEach(button => { | |
| button.addEventListener('click', () => { | |
| const func = button.getAttribute('data-function'); | |
| switch(func) { | |
| case 'sin': | |
| case 'cos': | |
| case 'tan': | |
| calculator.appendFunction(func); | |
| break; | |
| case 'pi': | |
| calculator.appendConstant('pi'); | |
| break; | |
| case 'e': | |
| calculator.appendConstant('e'); | |
| break; | |
| case 'square': | |
| calculator.appendFunction('x²'); | |
| break; | |
| case 'cube': | |
| calculator.appendFunction('x³'); | |
| break; | |
| case 'power': | |
| calculator.appendOperator('^'); | |
| break; | |
| case 'log': | |
| calculator.appendFunction('log'); | |
| break; | |
| case 'ln': | |
| calculator.appendFunction('ln'); | |
| break; | |
| case 'sqrt': | |
| calculator.appendFunction('√'); | |
| break; | |
| case 'factorial': | |
| calculator.appendFunction('x!'); | |
| break; | |
| case 'abs': | |
| calculator.appendFunction('Abs'); | |
| break; | |
| case 'exp': | |
| calculator.appendOperator('10^'); | |
| break; | |
| case 'paren-left': | |
| calculator.appendOperator('('); | |
| break; | |
| case 'paren-right': | |
| calculator.appendOperator(')'); | |
| break; | |
| } | |
| }); | |
| }); | |
| // Action buttons | |
| document.querySelector('[data-equals]').addEventListener('click', () => { | |
| calculator.compute(); | |
| }); | |
| document.querySelector('[data-all-clear]').addEventListener('click', () => { | |
| calculator.clear(); | |
| }); | |
| document.querySelectorAll('[data-delete], [data-action="delete-del"]').forEach(button => { | |
| button.addEventListener('click', () => { | |
| calculator.delete(); | |
| }); | |
| }); | |
| document.querySelector('[data-action="negate"]').addEventListener('click', () => { | |
| calculator.negate(); | |
| }); | |
| document.querySelector('#toggle-angle').addEventListener('click', () => { | |
| calculator.toggleAngleMode(); | |
| }); | |
| document.querySelector('#clear-history').addEventListener('click', () => { | |
| calculator.clearHistory(); | |
| }); | |
| document.querySelector('[data-action="shift"]').addEventListener('click', () => { | |
| calculator.toggleShift(); | |
| }); | |
| document.querySelector('[data-action="mode"]').addEventListener('click', () => { | |
| calculator.toggleAngleMode(); | |
| }); | |
| // Keyboard Support | |
| document.addEventListener('keydown', (e) => { | |
| if (e.key >= '0' && e.key <= '9') { | |
| calculator.appendNumber(e.key); | |
| highlightButton(e.key); | |
| } | |
| if (e.key === '.') { | |
| calculator.appendNumber('.'); | |
| highlightButton('.'); | |
| } | |
| if (e.key === '=' || e.key === 'Enter') { | |
| e.preventDefault(); | |
| calculator.compute(); | |
| highlightButton('='); | |
| } | |
| if (e.key === 'Backspace') { | |
| calculator.delete(); | |
| highlightButton('DEL'); | |
| } | |
| if (e.key === 'Escape') { | |
| calculator.clear(); | |
| highlightButton('AC'); | |
| } | |
| if (e.key === '+' || e.key === '-' || e.key === '*' || e.key === '/') { | |
| let op = e.key; | |
| if(op === '/') op = '÷'; | |
| if(op === '*') op = '×'; | |
| calculator.appendOperator(op); | |
| highlightButton(op); | |
| } | |
| if (e.key === '(' || e.key === ')') { | |
| calculator.appendOperator(e.key); | |
| highlightButton(e.key); | |
| } | |
| if (e.key === '^') { | |
| calculator.appendOperator('^'); | |
| highlightButton('^'); | |
| } | |
| if (e.key === 's') { | |
| calculator.appendFunction('sin'); | |
| highlightButtonByFunc('sin'); | |
| } | |
| if (e.key === 'c') { | |
| calculator.appendFunction('cos'); | |
| highlightButtonByFunc('cos'); | |
| } | |
| if (e.key === 't') { | |
| calculator.appendFunction('tan'); | |
| highlightButtonByFunc('tan'); | |
| } | |
| if (e.key === 'r') { | |
| calculator.appendFunction('√'); | |
| highlightButtonByFunc('sqrt'); | |
| } | |
| if (e.key === 'l') { | |
| calculator.appendFunction('log'); | |
| highlightButtonByFunc('log'); | |
| } | |
| if (e.key === 'n') { | |
| calculator.appendFunction('ln'); | |
| highlightButtonByFunc('ln'); | |
| } | |
| }); | |
| function highlightButton(key) { | |
| const buttons = Array.from(document.querySelectorAll('.casio-btn')); | |
| const button = buttons.find(btn => btn.querySelector('.main-label')?.textContent === key); | |
| if (button) { | |
| button.classList.add('pressed'); | |
| setTimeout(() => { | |
| button.classList.remove('pressed'); | |
| }, 100); | |
| } | |
| } | |
| function highlightButtonByFunc(func) { | |
| const buttons = Array.from(document.querySelectorAll('.casio-btn')); | |
| const button = buttons.find(btn => btn.getAttribute('data-function') === func); | |
| if (button) { | |
| button.classList.add('pressed'); | |
| setTimeout(() => { | |
| button.classList.remove('pressed'); | |
| }, 100); | |
| } | |
| } | |