Some checks failed
Deploy to GitHub Pages / build-and-deploy (push) Has been cancelled
184 lines
6.3 KiB
JavaScript
184 lines
6.3 KiB
JavaScript
// Calculator core module
|
|
const Calculator = (function() {
|
|
class Calculator {
|
|
constructor() {
|
|
this.clear();
|
|
}
|
|
|
|
clear() {
|
|
this.currentValue = '0';
|
|
this.operator = null;
|
|
this.previousValue = null;
|
|
this.shouldResetDisplay = false;
|
|
}
|
|
|
|
getCurrentDisplay() {
|
|
return this.currentValue;
|
|
}
|
|
|
|
// Add two numbers
|
|
add(a, b) {
|
|
return a + b;
|
|
}
|
|
|
|
// Subtract two numbers
|
|
subtract(a, b) {
|
|
return a - b;
|
|
}
|
|
|
|
// Multiply two numbers
|
|
multiply(a, b) {
|
|
return a * b;
|
|
}
|
|
|
|
// Divide two numbers (handles division by zero)
|
|
divide(a, b) {
|
|
if (b === 0) {
|
|
return Infinity;
|
|
}
|
|
return a / b;
|
|
}
|
|
|
|
// Handle percentage operations
|
|
percentage(value, percent = null) {
|
|
if (percent !== null) {
|
|
// If two arguments, calculate percentage of value
|
|
return value * (percent / 100);
|
|
} else {
|
|
// If single argument, convert to decimal (e.g., 50% = 0.5)
|
|
return value / 100;
|
|
}
|
|
}
|
|
|
|
// Toggle sign of a number
|
|
toggleSign(value) {
|
|
return -value;
|
|
}
|
|
|
|
// Evaluate an arithmetic expression in standard notation
|
|
evaluate(expression) {
|
|
try {
|
|
// Basic validation to prevent dangerous eval usage
|
|
// Only allow numbers, operators, parentheses, and decimal points
|
|
if (!/^[0-9+\-*/(). %]+$/.test(expression)) {
|
|
return 'Error';
|
|
}
|
|
|
|
// Handle percentage expressions by evaluating them properly
|
|
// Replace 'X%' with '(X/100)' for proper evaluation
|
|
let processedExpression = expression.replace(/(\d+(\.\d+)?)%/g, (match, number) => {
|
|
return `(${number}/100)`;
|
|
});
|
|
|
|
// Handle expressions like "100 + 10%" by replacing with "100 + (100 * 0.1)"
|
|
processedExpression = processedExpression.replace(/(\d+(?:\.\d+)?)\s*\+\s*(\d+(?:\.\d+)?)%/g, (match, base, percent) => {
|
|
return `${base} + (${base} * ${percent}/100)`;
|
|
});
|
|
|
|
processedExpression = processedExpression.replace(/(\d+(?:\.\d+)?)\s*-\s*(\d+(?:\.\d+)?)%/g, (match, base, percent) => {
|
|
return `${base} - (${base} * ${percent}/100)`;
|
|
});
|
|
|
|
processedExpression = processedExpression.replace(/(\d+(?:\.\d+)?)\s*\*\s*(\d+(?:\.\d+)?)%/g, (match, base, percent) => {
|
|
return `${base} * (${percent}/100)`;
|
|
});
|
|
|
|
// Use Function constructor instead of eval for safety
|
|
const result = new Function('return ' + processedExpression)();
|
|
|
|
// Check for invalid results
|
|
if (isNaN(result) || !isFinite(result)) {
|
|
return 'Error';
|
|
}
|
|
|
|
return result;
|
|
} catch (error) {
|
|
return 'Error';
|
|
}
|
|
}
|
|
|
|
// Process input for UI interactions
|
|
processInput(value) {
|
|
if (['+', '-', '*', '/'].includes(value)) {
|
|
// Handle operator input
|
|
if (this.operator && !this.shouldResetDisplay) {
|
|
// If there's already an operator, evaluate the current expression
|
|
this.calculate();
|
|
}
|
|
|
|
this.operator = value;
|
|
this.previousValue = parseFloat(this.currentValue);
|
|
this.shouldResetDisplay = true;
|
|
} else if (value === '=') {
|
|
this.calculate();
|
|
} else if (value === 'C' || value === 'CE') {
|
|
this.clear();
|
|
} else if (value === '±') {
|
|
// Toggle sign
|
|
this.currentValue = this.toggleSign(parseFloat(this.currentValue)).toString();
|
|
} else if (value === '.') {
|
|
// Handle decimal point
|
|
if (this.shouldResetDisplay) {
|
|
this.currentValue = '0.';
|
|
this.shouldResetDisplay = false;
|
|
} else if (!this.currentValue.includes('.')) {
|
|
this.currentValue += '.';
|
|
}
|
|
} else if (['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].includes(value)) {
|
|
// Handle number input
|
|
if (this.shouldResetDisplay) {
|
|
this.currentValue = value;
|
|
this.shouldResetDisplay = false;
|
|
} else {
|
|
if (this.currentValue === '0') {
|
|
this.currentValue = value;
|
|
} else {
|
|
this.currentValue += value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Perform calculation based on current values
|
|
calculate() {
|
|
if (this.operator === null || this.previousValue === null) {
|
|
return;
|
|
}
|
|
|
|
const current = parseFloat(this.currentValue);
|
|
let result;
|
|
|
|
switch (this.operator) {
|
|
case '+':
|
|
result = this.add(this.previousValue, current);
|
|
break;
|
|
case '-':
|
|
result = this.subtract(this.previousValue, current);
|
|
break;
|
|
case '*':
|
|
result = this.multiply(this.previousValue, current);
|
|
break;
|
|
case '/':
|
|
result = this.divide(this.previousValue, current);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
// Check for invalid results
|
|
if (isNaN(result) || !isFinite(result)) {
|
|
this.currentValue = 'Error';
|
|
} else {
|
|
// Round to avoid floating point precision issues
|
|
this.currentValue = Math.round(result * 100000000) / 100000000;
|
|
this.currentValue = this.currentValue.toString();
|
|
}
|
|
|
|
this.operator = null;
|
|
this.previousValue = null;
|
|
this.shouldResetDisplay = true;
|
|
}
|
|
}
|
|
|
|
return { Calculator };
|
|
})(); |