// Utility functions module const Utils = (function() { // Format a number for display function formatNumber(num) { if (typeof num === 'number') { // Check if it's an integer or a simple decimal if (Number.isInteger(num)) { return num.toString(); } else { // Format to avoid floating point precision issues return parseFloat(num.toFixed(10)).toString(); } } return num.toString(); } // Check if a value is a number function isNumber(value) { return !isNaN(parseFloat(value)) && isFinite(value); } // Parse a string expression safely function parseExpression(expr) { // Remove any whitespace expr = expr.replace(/\s/g, ''); // Check if the expression contains only allowed characters if (!/^[0-9+\-*/().%]+$/.test(expr)) { return null; // Invalid expression } return expr; } // Calculate expression with error handling function calculateExpression(expr) { try { // For security, avoid using eval directly // Instead, use the Function constructor with validation const validatedExpr = parseExpression(expr); if (!validatedExpr) { return 'Error'; } // Replace % with proper calculation let processedExpr = validatedExpr; if (processedExpr.includes('%')) { // Handle percentage expressions - this is a simplified approach // In a real implementation, you'd want more sophisticated percentage handling processedExpr = processedExpr.replace(/(\d+(\.\d+)?)%/g, (match, number) => { return `*${number}/100`; }); } const result = new Function(`"use strict"; return (${processedExpr})`)(); if (isNaN(result) || !isFinite(result)) { return 'Error'; } return result; } catch (error) { return 'Error'; } } // Get expression precedence for operator function getPrecedence(operator) { switch (operator) { case '+': case '-': return 1; case '*': case '/': return 2; case '^': return 3; default: return 0; } } // Check if character is an operator function isOperator(char) { return ['+', '-', '*', '/', '^'].includes(char); } // Convert infix expression to postfix (for more complex expression evaluation) function infixToPostfix(expression) { const output = []; const operators = []; // Simple tokenization (in a real implementation, you'd want more robust parsing) const tokens = expression.match(/\d+(\.\d+)?|[+\-*/()]/g) || []; for (const token of tokens) { if (isNumber(token)) { output.push(token); } else if (token === '(') { operators.push(token); } else if (token === ')') { while (operators.length && operators[operators.length - 1] !== '(') { output.push(operators.pop()); } operators.pop(); // Remove the '(' } else if (isOperator(token)) { while ( operators.length && operators[operators.length - 1] !== '(' && getPrecedence(operators[operators.length - 1]) >= getPrecedence(token) ) { output.push(operators.pop()); } operators.push(token); } } while (operators.length) { output.push(operators.pop()); } return output.join(' '); } // Debounce function to limit execution frequency function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // Serialize state for storage function serializeState(state) { return JSON.stringify(state, (key, value) => { // Handle dates properly if (value instanceof Date) { return { __type: 'Date', value: value.toISOString() }; } return value; }); } // Deserialize state from storage function deserializeState(str) { return JSON.parse(str, (key, value) => { // Restore dates properly if (value && typeof value === 'object' && value.__type === 'Date') { return new Date(value.value); } return value; }); } // Update page title dynamically function updatePageTitle(title) { document.title = title || 'Geek Calculator'; } // Check if running in offline mode function isOffline() { return !navigator.onLine; } // Format file size for display function formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } return { formatNumber, isNumber, parseExpression, calculateExpression, getPrecedence, isOperator, infixToPostfix, debounce, serializeState, deserializeState, updatePageTitle, isOffline, formatFileSize }; })();