Guida Completa: Come Programmare una Calcolatrice Scientifica
Creare una calcolatrice scientifica da zero è un progetto eccellente per sviluppatori che vogliono approfondire la programmazione in JavaScript, l’interfaccia utente e la matematica computazionale. Questa guida ti condurrà attraverso tutti i passaggi necessari, dalle basi dell’HTML/CSS alla implementazione di funzioni matematiche avanzate.
1. Pianificazione del Progetto
Prima di scrivere una sola riga di codice, è essenziale pianificare la struttura della tua calcolatrice scientifica. Ecco gli elementi chiave da considerare:
- Interfaccia Utente: Decidi se vuoi un design classico con pulsanti o un’interfaccia moderna con input testuali.
- Funzionalità: Elenca tutte le operazioni che vuoi includere (aritmetica di base, trigonometria, logaritmi, ecc.).
- Tecnologie: Scegli se usare solo HTML/CSS/JS vanilla o framework come React/Vue.
- Responsività: Assicurati che la calcolatrice funzioni bene su tutti i dispositivi.
- Accessibilità: Progetta tenendo conto degli utenti con disabilità (contrasti, etichette ARIA, ecc.).
2. Struttura HTML di Base
La struttura HTML è la spina dorsale della tua calcolatrice. Ecco un esempio di base che puoi espandere:
<!DOCTYPE html>
<html lang=”it”>
<head>
<meta charset=”UTF-8″>
<meta name=”viewport” content=”width=device-width, initial-scale=1.0″>
<title>Calcolatrice Scientifica</title>
<link rel=”stylesheet” href=”styles.css”>
</head>
<body>
<div class=”calculator”>
<div class=”display”>
<div class=”previous-operand”></div>
<div class=”current-operand”>0</div>
</div>
<div class=”buttons”>
<!– Pulsanti numerici –>
<button class=”number”>7</button>
<button class=”number”>8</button>
<button class=”number”>9</button>
<button class=”operator”>÷</button>
<!– Altri pulsanti… –>
</div>
</div>
<script src=”script.js”></script>
</body>
</html>
Elementi Chiave:
- Display: Mostra l’operazione corrente e il risultato.
- Pulsanti Numerici: Da 0 a 9 più il punto decimale.
- Operatori: +, -, ×, ÷, =, ecc.
- Funzioni Scientifiche: sin, cos, tan, log, ln, √, x², ecc.
- Pulsanti Speciali: AC (azzera tutto), C (cancella), ± (cambia segno).
3. Stile CSS Premium
Il design della tua calcolatrice dovrebbe essere sia funzionale che esteticamente gradevole. Ecco alcune best practice:
.calculator {
max-width: 400px;
margin: 50px auto;
border: 1px solid #ccc;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
font-family: ‘Segoe UI’, Tahoma, Geneva, Verdana, sans-serif;
}
.display {
background-color: #2563eb;
color: white;
padding: 20px;
text-align: right;
min-height: 100px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.previous-operand {
font-size: 1.2rem;
opacity: 0.7;
height: 24px;
overflow: hidden;
}
.current-operand {
font-size: 2.5rem;
}
.buttons {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 1px;
background-color: #ddd;
}
button {
border: none;
padding: 20px;
font-size: 1.2rem;
cursor: pointer;
background-color: white;
transition: background-color 0.2s;
}
button:hover {
background-color: #f0f0f0;
}
button:active {
background-color: #e0e0e0;
}
.operator {
background-color: #f0f0f0;
}
.operator:active {
background-color: #d0d0d0;
}
.scientific {
background-color: #e3f2fd;
}
.scientific:active {
background-color: #bbdefb;
}
.equals {
background-color: #2563eb;
color: white;
}
.equals:active {
background-color: #1d4ed8;
}
Consigli per il Design:
- Usa una gerarchia visiva chiara per distinguere tra operazioni di base e funzioni scientifiche.
- Mantieni un contrasto sufficiente tra testo e sfondo per l’accessibilità.
- Implementa feedback visivo per i pulsanti (hover, active).
- Assicurati che i pulsanti siano abbastanza grandi per essere usati su touchscreen.
- Considera un tema scuro per ridurre l’affaticamento degli occhi.
4. Logica JavaScript per le Operazioni di Base
Il cuore della tua calcolatrice risiede nella logica JavaScript. Iniziamo con le operazioni aritmetiche di base.
class Calculator {
constructor(previousOperandTextElement, currentOperandTextElement) {
this.previousOperandTextElement = previousOperandTextElement;
this.currentOperandTextElement = currentOperandTextElement;
this.clear();
}
clear() {
this.currentOperand = ‘0’;
this.previousOperand = ”;
this.operation = undefined;
}
delete() {
this.currentOperand = this.currentOperand.toString().slice(0, -1);
if (this.currentOperand === ”) {
this.currentOperand = ‘0’;
}
}
appendNumber(number) {
if (number === ‘.’ && this.currentOperand.includes(‘.’)) return;
if (this.currentOperand === ‘0’ && number !== ‘.’) {
this.currentOperand = number;
} else {
this.currentOperand = this.currentOperand.toString() + number.toString();
}
}
chooseOperation(operation) {
if (this.currentOperand === ”) return;
if (this.previousOperand !== ”) {
this.compute();
}
this.operation = operation;
this.previousOperand = this.currentOperand;
this.currentOperand = ”;
}
compute() {
let computation;
const prev = parseFloat(this.previousOperand);
const current = parseFloat(this.currentOperand);
if (isNaN(prev) || isNaN(current)) return;
switch (this.operation) {
case ‘+’:
computation = prev + current;
break;
case ‘-‘:
computation = prev – current;
break;
case ‘×’:
computation = prev * current;
break;
case ‘÷’:
computation = prev / current;
break;
default:
return;
}
this.currentOperand = computation;
this.operation = undefined;
this.previousOperand = ”;
}
updateDisplay() {
this.currentOperandTextElement.innerText = this.currentOperand;
if (this.operation != null) {
this.previousOperandTextElement.innerText =
`${this.previousOperand} ${this.operation}`;
} else {
this.previousOperandTextElement.innerText = ”;
}
}
}
// Inizializzazione
const numberButtons = document.querySelectorAll(‘.number’);
const operationButtons = document.querySelectorAll(‘.operator’);
const equalsButton = document.querySelector(‘.equals’);
const deleteButton = document.querySelector(‘.delete’);
const allClearButton = document.querySelector(‘.all-clear’);
const previousOperandTextElement = document.querySelector(‘.previous-operand’);
const currentOperandTextElement = document.querySelector(‘.current-operand’);
const calculator = new Calculator(previousOperandTextElement, currentOperandTextElement);
numberButtons.forEach(button => {
button.addEventListener(‘click’, () => {
calculator.appendNumber(button.innerText);
calculator.updateDisplay();
});
});
operationButtons.forEach(button => {
button.addEventListener(‘click’, () => {
calculator.chooseOperation(button.innerText);
calculator.updateDisplay();
});
});
equalsButton.addEventListener(‘click’, () => {
calculator.compute();
calculator.updateDisplay();
});
allClearButton.addEventListener(‘click’, () => {
calculator.clear();
calculator.updateDisplay();
});
deleteButton.addEventListener(‘click’, () => {
calculator.delete();
calculator.updateDisplay();
});
Funzioni Aggiuntive Utili:
- Gestione degli errori: Aggiungi controlli per divisioni per zero, input non validi, ecc.
- Formattazione dei numeri: Usa
toLocaleString() per separare le migliaia.
- Storico delle operazioni: Mantieni una lista delle ultime operazioni eseguite.
- Tasti di scelta rapida: Implementa supporto per la tastiera.
5. Implementazione delle Funzioni Scientifiche
Per trasformare la tua calcolatrice in una versione scientifica, dovrai aggiungere funzioni matematiche avanzate. Ecco come implementare alcune delle più comuni:
// Aggiungi questi metodi alla classe Calculator
// Funzioni trigonometriche (gradi o radianti)
trigFunction(func, angle, inDegrees = true) {
if (inDegrees) {
angle = angle * Math.PI / 180;
}
let result;
switch(func) {
case ‘sin’:
result = Math.sin(angle);
break;
case ‘cos’:
result = Math.cos(angle);
break;
case ‘tan’:
result = Math.tan(angle);
break;
case ‘asin’:
result = Math.asin(angle);
if (inDegrees) result = result * 180 / Math.PI;
break;
case ‘acos’:
result = Math.acos(angle);
if (inDegrees) result = result * 180 / Math.PI;
break;
case ‘atan’:
result = Math.atan(angle);
if (inDegrees) result = result * 180 / Math.PI;
break;
default:
return NaN;
}
return parseFloat(result.toFixed(10));
}
// Logaritmi
logFunction(number, base = Math.E) {
if (number <= 0 || base <= 0 || base === 1) return NaN;
return Math.log(number) / Math.log(base);
}
// Esponenziali e radici
power(base, exponent) {
return Math.pow(base, exponent);
}
squareRoot(number) {
if (number < 0) return NaN;
return Math.sqrt(number);
}
nthRoot(number, root) {
if (root === 0) return NaN;
if (number < 0 && root % 2 === 0) return NaN;
return Math.pow(number, 1/root);
}
// Funzioni statistiche
mean(numbers) {
return numbers.reduce((a, b) => a + b, 0) / numbers.length;
}
median(numbers) {
const sorted = […numbers].sort((a, b) => a – b);
const middle = Math.floor(sorted.length / 2);
if (sorted.length % 2 === 0) {
return (sorted[middle – 1] + sorted[middle]) / 2;
}
return sorted[middle];
}
mode(numbers) {
const frequencyMap = {};
let maxFrequency = 0;
let modes = [];
numbers.forEach(num => {
frequencyMap[num] = (frequencyMap[num] || 0) + 1;
if (frequencyMap[num] > maxFrequency) {
maxFrequency = frequencyMap[num];
}
});
for (const num in frequencyMap) {
if (frequencyMap[num] === maxFrequency) {
modes.push(parseFloat(num));
}
}
return modes.length === Object.keys(frequencyMap).length ? [] : modes;
}
variance(numbers, sample = false) {
const avg = this.mean(numbers);
const squareDiffs = numbers.map(num => Math.pow(num – avg, 2));
const sumSquareDiffs = squareDiffs.reduce((a, b) => a + b, 0);
return sumSquareDiffs / (sample ? numbers.length – 1 : numbers.length);
}
standardDeviation(numbers, sample = false) {
return Math.sqrt(this.variance(numbers, sample));
}
Integrazione con l’Interfaccia:
Per collegare queste funzioni ai pulsanti della calcolatrice, puoi aggiungere event listener specifici:
// Esempio per le funzioni trigonometriche
document.querySelectorAll(‘.trig-function’).forEach(button => {
button.addEventListener(‘click’, () => {
const angle = parseFloat(this.currentOperand);
if (!isNaN(angle)) {
const result = this.trigFunction(button.dataset.func, angle);
if (!isNaN(result)) {
this.currentOperand = result.toString();
this.updateDisplay();
} else {
this.currentOperand = ‘Errore’;
this.updateDisplay();
}
}
});
});
// Esempio per il logaritmo
document.getElementById(‘log-button’).addEventListener(‘click’, () => {
const number = parseFloat(this.currentOperand);
if (number > 0) {
this.currentOperand = this.logFunction(number).toString();
this.updateDisplay();
} else {
this.currentOperand = ‘Errore’;
this.updateDisplay();
}
});
6. Gestione degli Errori e Validazione
Una calcolatrice robusta deve gestire correttamente gli errori e validare gli input. Ecco alcune situazioni da considerare:
| Tipo di Errore |
Causa |
Soluzione |
| Divisione per zero |
Utente tenta di dividere per zero |
Mostra “Errore” e impedisci il calcolo |
| Radice quadrata di numero negativo |
Utente inserisce numero negativo per √ |
Mostra “Errore” o restituisci NaN |
| Logaritmo di numero non positivo |
Utente inserisce ≤ 0 per log |
Mostra “Errore” e impedisci il calcolo |
| Input non numerico |
Utente inserisce lettere o simboli |
Filtra gli input per permettere solo numeri e operatori validi |
| Overflow del numero |
Risultato troppo grande per JavaScript |
Mostra “Infinito” o usa toExponential() |
| Operazione non valida |
Sequenza di operatori non valida (es. “5++3”) |
Ignora l’operatore aggiuntivo o mostra errore |
Ecco un esempio di come implementare la gestione degli errori:
class Calculator {
// … codice precedente …
compute() {
let computation;
const prev = parseFloat(this.previousOperand);
const current = parseFloat(this.currentOperand);
if (isNaN(prev) || isNaN(current)) {
this.currentOperand = ‘Errore’;
this.operation = undefined;
this.previousOperand = ”;
return;
}
switch (this.operation) {
case ‘+’:
computation = prev + current;
break;
case ‘-‘:
computation = prev – current;
break;
case ‘×’:
computation = prev * current;
break;
case ‘÷’:
if (current === 0) {
this.currentOperand = ‘Errore’;
this.operation = undefined;
this.previousOperand = ”;
return;
}
computation = prev / current;
break;
default:
return;
}
// Gestione overflow
if (!isFinite(computation)) {
this.currentOperand = computation > 0 ? ‘Infinito’ : ‘-Infinito’;
} else {
// Limita il numero di decimali per evitare notazione esponenziale non necessaria
this.currentOperand = parseFloat(computation.toFixed(12)).toString();
}
this.operation = undefined;
this.previousOperand = ”;
}
// Funzione per validare l’input
validateInput(input) {
// Permetti numeri, punto decimale, e operatori di base
const validChars = /^[0-9+\-×÷.eπ√^!%]*$/;
return validChars.test(input);
}
}
7. Funzionalità Avanzate
Per distinguere la tua calcolatrice scientifica, considera l’aggiunta di queste funzionalità avanzate:
-
Supporto per le costanti matematiche:
- π (Pi greco) – 3.141592653589793
- e (Numero di Eulero) – 2.718281828459045
- φ (Rapporto aureo) – 1.618033988749895
-
Memoria (M+, M-, MR, MC):
- M+ – Aggiungi al valore in memoria
- M- – Sottrai dal valore in memoria
- MR – Richiamare il valore in memoria
- MC – Azzera la memoria
-
Storico delle operazioni:
- Mantieni una lista delle ultime 10-20 operazioni
- Permetti all’utente di richiamare operazioni precedenti
- Salva lo storico in localStorage per persistenza
-
Conversione di unità:
- Lunghezza (metri, piedi, pollici)
- Peso (chilogrammi, libbre, once)
- Temperatura (Celsius, Fahrenheit, Kelvin)
- Valuta (con API esterne per tassi aggiornati)
-
Grafici delle funzioni:
- Usa Canvas API o librerie come Chart.js
- Permetti all’utente di tracciare funzioni matematiche
- Aggiungi zoom e pan per esplorare il grafico
-
Modalità avanzate:
- Modalità programmatore (binario, esadecimale)
- Calcoli con numeri complessi
- Matrici e determinanti
8. Ottimizzazione delle Prestazioni
Per calcolatrici scientifiche complesse, le prestazioni possono diventare un problema. Ecco alcune tecniche di ottimizzazione:
| Tecnica |
Descrizione |
Beneficio |
| Debouncing |
Limita la frequenza con cui alcune funzioni vengono eseguite (es. aggiornamento display durante input) |
Riduce il carico sulla CPU durante input rapidi |
| Memoization |
Cache dei risultati di funzioni costose (es. calcoli trigonometrici) |
Evita ricalcoli ridondanti per gli stessi input |
| Web Workers |
Esegui calcoli complessi in un thread separato |
Mantiene l’interfaccia utente reattiva durante calcoli intensivi |
| Lazy Loading |
Carica librerie matematiche avanzate solo quando necessario |
Riduce il tempo di caricamento iniziale |
| Virtualizzazione |
Per lo storico delle operazioni, rendi solo gli elementi visibili nel DOM |
Migliora le prestazioni con lunghe liste |
| Compressione |
Comprimi gli asset (CSS, JS) e usa caching |
Riduce la larghezza di banda e migliorare i tempi di caricamento |
Ecco un esempio di implementazione della memoization per funzioni trigonometriche:
class Calculator {
constructor() {
// Cache per le funzioni trigonometriche
this.trigCache = new Map();
// … altro codice …
}
// Funzione trigonometrica con memoization
trigFunction(func, angle, inDegrees = true) {
const key = `${func}|${angle}|${inDegrees}`;
// Controlla la cache
if (this.trigCache.has(key)) {
return this.trigCache.get(key);
}
// Calcola se non in cache
let result;
const angleRad = inDegrees ? angle * Math.PI / 180 : angle;
switch(func) {
case ‘sin’:
result = Math.sin(angleRad);
break;
case ‘cos’:
result = Math.cos(angleRad);
break;
case ‘tan’:
result = Math.tan(angleRad);
break;
// … altre funzioni …
default:
return NaN;
}
// Salva in cache e restituisci
const roundedResult = parseFloat(result.toFixed(10));
this.trigCache.set(key, roundedResult);
return roundedResult;
}
// Pulizia della cache (opzionale)
clearCache() {
this.trigCache.clear();
}
}
9. Testing e Debugging
Il testing è cruciale per garantire che la tua calcolatrice funzioni correttamente in tutte le situazioni. Ecco una strategia di testing completa:
Test Unitari
Scrivi test per ogni funzione matematica individualmente. Puoi usare framework come Jest o Mocha:
// Esempio con Jest
describe(‘Calculator’, () => {
let calculator;
beforeEach(() => {
calculator = new Calculator();
});
describe(‘basic operations’, () => {
test(‘adds 1 + 2 to equal 3’, () => {
calculator.previousOperand = ‘1’;
calculator.currentOperand = ‘2’;
calculator.operation = ‘+’;
calculator.compute();
expect(calculator.currentOperand).toBe(‘3’);
});
test(‘subtracts 5 – 3 to equal 2’, () => {
calculator.previousOperand = ‘5’;
calculator.currentOperand = ‘3’;
calculator.operation = ‘-‘;
calculator.compute();
expect(calculator.currentOperand).toBe(‘2’);
});
// … altri test …
});
describe(‘trigonometric functions’, () => {
test(‘sin(90°) equals 1’, () => {
expect(calculator.trigFunction(‘sin’, 90)).toBeCloseTo(1);
});
test(‘cos(0°) equals 1’, () => {
expect(calculator.trigFunction(‘cos’, 0)).toBeCloseTo(1);
});
test(‘tan(45°) equals 1’, () => {
expect(calculator.trigFunction(‘tan’, 45)).toBeCloseTo(1);
});
});
describe(‘logarithms’, () => {
test(‘log10(100) equals 2’, () => {
expect(calculator.logFunction(100, 10)).toBeCloseTo(2);
});
test(‘ln(e) equals 1’, () => {
expect(calculator.logFunction(Math.E)).toBeCloseTo(1);
});
});
describe(‘error handling’, () => {
test(‘division by zero returns Error’, () => {
calculator.previousOperand = ‘5’;
calculator.currentOperand = ‘0’;
calculator.operation = ‘÷’;
calculator.compute();
expect(calculator.currentOperand).toBe(‘Errore’);
});
test(‘sqrt(-1) returns Error’, () => {
calculator.currentOperand = ‘-1’;
const result = calculator.squareRoot(parseFloat(calculator.currentOperand));
expect(isNaN(result)).toBe(true);
});
});
});
Test di Integrazione
Verifica che tutti i componenti lavorino insieme correttamente:
- Testa il flusso completo: input → operazione → risultato → nuova operazione
- Verifica che lo storico delle operazioni venga aggiornato correttamente
- Controlla che la memoria (M+, M-, etc.) funzioni come previsto
- Testa la persistenza dei dati (se usi localStorage)
Test di Usabilità
Coinvolgi utenti reali per testare:
- La calcolatrice è intuitiva da usare?
- I pulsanti sono abbastanza grandi per essere premuti facilmente su mobile?
- Il testo è leggibile in tutte le condizioni di luce?
- Gli errori sono gestiti in modo chiaro per l’utente?
Debugging Tools
Alcuni strumenti utili per il debugging:
- Chrome DevTools: Per ispezionare elementi, debuggare JavaScript e analizzare le prestazioni.
- Firefox Developer Tools: Ottimo per il debugging CSS e JavaScript.
- ESLint: Per identificare potenziali errori nel codice JavaScript.
- Stylelint: Per mantenere il CSS pulito e coerente.
- Lighthouse: Per audit di prestazioni, accessibilità e SEO.
10. Distribuzione e Manutenzione
Una volta completata la tua calcolatrice scientifica, è il momento di distribuirla e mantenerla.
Opzioni di Distribuzione
| Metodo |
Vantaggi |
Svantaggi |
Costo |
| GitHub Pages |
Gratuito, facile da configurare, integrazione con GitHub |
Limitato a siti statici, URL con github.io |
Gratis |
| Netlify/Vercel |
Deploy continuo, prestazioni ottime, SSL automatico |
Limitazioni nel piano gratuito per progetti complessi |
Gratis (piani a pagamento disponibili) |
| Hosting condiviso |
Economico, supporto per backend se necessario |
Prestazioni variabili, configurazione più complessa |
$3-$10/mese |
| VPS |
Controllo completo, scalabile |
Richiede competenze di sistemistica, costo più alto |
$5-$50/mese |
| App mobile (Cordova/Capacitor) |
Distribuzione su app store, accesso a funzionalità native |
Processo di approvazione per gli store, sviluppo più complesso |
$99/anno (Apple), $25 una tantum (Google) |
| Estensione browser |
Accesso rapido dalla barra degli strumenti, integrazione con il browser |
Limitazioni delle API, processo di pubblicazione |
Gratis (costo per account sviluppatore per alcuni store) |
Manutenzione Continua
Dopo il lancio, la manutenzione è cruciale per mantenere la tua calcolatrice funzionante e sicura:
- Aggiornamenti di sicurezza: Mantieni aggiornate tutte le dipendenze.
- Feedback degli utenti: Raccogli e implementa suggerimenti dagli utenti.
- Usa strumenti come Google Analytics o Sentry per tracciare errori e prestazioni.
- Aggiornamenti delle funzionalità: Aggiungi nuove funzioni basate sulle richieste degli utenti.
- Backup: Mantieni backup regolari del tuo codice e database (se applicabile).
- Test di regressione: Esegui test completi dopo ogni aggiornamento per assicurarti che nulla si sia rotto.
Monetizzazione (Opzionale)
Se vuoi monetizzare la tua calcolatrice scientifica, ecco alcune opzioni:
-
Modello Freemium:
- Versione base gratuita con funzionalità limitate
- Versione premium con funzioni avanzate (es. grafici, storico illimitato)
-
Pubblicità:
- Banner pubblicitari non intrusivi
- Annunci interstiziali tra le sessioni di calcolo
- Usa network come Google AdSense
-
Donazioni:
- Aggiungi un pulsante “Dona” con PayPal o Ko-fi
- Offri funzionalità bonus ai donatori
-
Sponsorizzazioni:
- Collabora con aziende per promozioni mirate
- Es. calcolatrici scientifiche sponsorizzate da università o aziende tech
-
Vendita di dati (anonimi):
- Raccogli dati aggregati su quali funzioni vengono usate più spesso
- Vendi queste informazioni a ricercatori o educatori
- Assicurati di rispettare la privacy degli utenti (GDPR, etc.)
11. Risorse per Approfondire
Per continuare a migliorare le tue competenze nella creazione di calcolatrici scientifiche, ecco alcune risorse utili:
Libri Consigliati
- “JavaScript: The Definitive Guide” di David Flanagan – Per padronanza completa di JavaScript
- “Eloquent JavaScript” di Marijn Haverbeke – Ottimo per comprendere i concetti fondamentali
- “Mathematics for Computer Science” di Eric Lehman – Per approfondire la matematica dietro le operazioni
- “The Algorithm Design Manual” di Steven S. Skiena – Per ottimizzare le prestazioni dei calcoli
Corsi Online
Documentazione Ufficiale
Comunità e Forum
Strumenti Utili
12. Esempio Completo di Calcolatrice Scientifica
Per aiutarti a iniziare, ecco un esempio completo di implementazione di una calcolatrice scientifica con HTML, CSS e JavaScript:
<!DOCTYPE html>
<html lang=”it”>
<head>
<meta charset=”UTF-8″>
<meta name=”viewport” content=”width=device-width, initial-scale=1.0″>
<title>Calcolatrice Scientifica</title>
<style>
:root {
–bg-color: #f5f5f5;
–display-color: #2c3e50;
–button-color: #ecf0f1;
–operator-color: #f39c12;
–scientific-color: #3498db;
–equals-color: #2ecc71;
–text-color: #333;
–text-light: #fff;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: ‘Segoe UI’, Tahoma, Geneva, Verdana, sans-serif;
background-color: var(–bg-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 20px;
}
.calculator {
width: 100%;
max-width: 400px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.display {
background-color: var(–display-color);
color: var(–text-light);
padding: 20px;
text-align: right;
min-height: 120px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.previous-operand {
font-size: 1.2rem;
opacity: 0.7;
height: 24px;
overflow: hidden;
word-break: break-all;
}
.current-operand {
font-size: 2.5rem;
overflow: hidden;
word-break: break-all;
}
.buttons {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 1px;
background-color: #ddd;
}
button {
border: none;
padding: 20px;
font-size: 1.2rem;
cursor: pointer;
background-color: var(–button-color);
transition: all 0.2s;
}
button:hover {
opacity: 0.8;
}
button:active {
transform: scale(0.95);
}
.operator {
background-color: var(–operator-color);
color: var(–text-light);
}
.scientific {
background-color: var(–scientific-color);
color: var(–text-light);
font-size: 0.9rem;
}
.equals {
background-color: var(–equals-color);
color: var(–text-light);
grid-row: span 2;
}
.span-2 {
grid-column: span 2;
}
.history-btn {
background-color: #95a5a6;
color: var(–text-light);
}
.memory-btn {
background-color: #9b59b6;
color: var(–text-light);
}
/* Responsive design */
@media (max-width: 400px) {
.buttons {
grid-template-columns: repeat(4, 1fr);
}
.scientific {
font-size: 0.7rem;
padding: 15px 10px;
}
.span-2 {
grid-column: span 4;
}
.equals {
grid-row: span 1;
}
}
</style>
</head>
<body>
<div class=”calculator”>
<div class=”display”>
<div class=”previous-operand”></div>
<div class=”current-operand”>0</div>
</div>
<div class=”buttons”>
<!– Prima riga: funzioni scientifiche –>
<button class=”scientific” data-action=”clear-history”>Hist</button>
<button class=”scientific” data-action=”memory-clear”>MC</button>
<button class=”scientific” data-action=”memory-recall”>MR</button>
<button class=”scientific” data-action=”memory-add”>M+</button>
<button class=”scientific” data-action=”memory-subtract”>M-</button>
<!– Seconda riga: funzioni scientifiche –>
<button class=”scientific” data-action=”square”>x²</button>
<button class=”scientific” data-action=”power”>x^y</button>
<button class=”scientific” data-action=”sqrt”>√x</button>
<button class=”scientific” data-action=”nth-root”>y√x</button>
<button class=”scientific” data-action=”factorial”>x!</button>
<!– Terza riga: funzioni scientifiche –>
<button class=”scientific” data-action=”sin”>sin</button>
<button class=”scientific” data-action=”cos”>cos</button>
<button class=”scientific” data-action=”tan”>tan</button>
<button class=”scientific” data-action=”log”>log</button>
<button class=”scientific” data-action=”ln”>ln</button>
<!– Quarta riga: operatori e cancellazione –>
<button class=”operator” data-action=”clear”>AC</button>
<button class=”operator” data-action=”delete”>⌫</button>
<button class=”operator” data-action=”percent”>%</button>
<button class=”operator” data-action=”divide”>÷</button>
<button class=”operator” data-action=”multiply”>×</button>
<!– Quinta riga: numeri 7-9 e operatori –>
<button data-action=”number”>7</button>
<button data-action=”number”>8</button>
<button data-action=”number”>9</button>
<button class=”operator” data-action=”subtract”>-</button>
<button class=”operator” data-action=”add”>+</button>
<!– Sesta riga: numeri 4-6 e operatori –>
<button data-action=”number”>4</button>
<button data-action=”number”>5</button>
<button data-action=”number”>6</button>
<button data-action=”decimal”>.</button>
<button class=”scientific” data-action=”pi”>π</button>
<!– Settima riga: numeri 1-3 e uguale –>
<button data-action=”number”>1</button>
<button data-action=”number”>2</button>
<button data-action=”number”>3</button>
<button data-action=”negate”>±</button>
<button class=”scientific” data-action=”e”>e</button>
<!– Ottava riga: 0 e uguale –>
<button data-action=”number” class=”span-2″>0</button>
<button data-action=”decimal”>.</button>
<button class=”equals” data-action=”calculate”>=</button>
</div>
</div>
<script>
class Calculator {
constructor(previousOperandTextElement, currentOperandTextElement) {
this.previousOperandTextElement = previousOperandTextElement;
this.currentOperandTextElement = currentOperandTextElement;
this.memory = 0;
this.history = [];
this.clear();
}
clear() {
this.currentOperand = ‘0’;
this.previousOperand = ”;
this.operation = undefined;
}
delete() {
this.currentOperand = this.currentOperand.toString().slice(0, -1);
if (this.currentOperand === ”) {
this.currentOperand = ‘0’;
}
}
appendNumber(number) {
if (number === ‘.’ && this.currentOperand.includes(‘.’)) return;
if (this.currentOperand === ‘0’ && number !== ‘.’) {
this.currentOperand = number;
} else {
this.currentOperand = this.currentOperand.toString() + number.toString();
}
}
chooseOperation(operation) {
if (this.currentOperand === ”) return;
if (this.previousOperand !== ”) {
this.compute();
}
this.operation = operation;
this.previousOperand = this.currentOperand;
this.currentOperand = ”;
}
compute() {
let computation;
const prev = parseFloat(this.previousOperand);
const current = parseFloat(this.currentOperand);
if (isNaN(prev) || isNaN(current)) return;
switch (this.operation) {
case ‘+’:
computation = prev + current;
break;
case ‘-‘:
computation = prev – current;
break;
case ‘×’:
computation = prev * current;
break;
case ‘÷’:
if (current === 0) {
this.currentOperand = ‘Errore’;
this.operation = undefined;
this.previousOperand = ”;
return;
}
computation = prev / current;
break;
default:
return;
}
this.currentOperand = computation;
this.operation = undefined;
this.previousOperand = ”;
// Aggiungi all’history
this.history.unshift({
expression: `${prev} ${this.operation} ${current}`,
result: computation
});
// Mantieni solo gli ultimi 20 elementi
if (this.history.length > 20) {
this.history.pop();
}
}
// Funzioni scientifiche
square() {
const num = parseFloat(this.currentOperand);
this.currentOperand = Math.pow(num, 2);
}
power() {
this.operation = ‘^’;
this.previousOperand = this.currentOperand;
this.currentOperand = ”;
}
sqrt() {
const num = parseFloat(this.currentOperand);
if (num < 0) {
this.currentOperand = 'Errore';
return;
}
this.currentOperand = Math.sqrt(num);
}
nthRoot() {
this.operation = '√';
this.previousOperand = this.currentOperand;
this.currentOperand = '';
}
factorial() {
let num = parseFloat(this.currentOperand);
if (num < 0 || !Number.isInteger(num)) {
this.currentOperand = 'Errore';
return;
}
let result = 1;
for (let i = 2; i <= num; i++) {
result *= i;
}
this.currentOperand = result;
}
trigFunction(func) {
const angle = parseFloat(this.currentOperand);
let result;
// Converti da gradi a radianti
const angleRad = angle * Math.PI / 180;
switch(func) {
case 'sin':
result = Math.sin(angleRad);
break;
case 'cos':
result = Math.cos(angleRad);
break;
case 'tan':
result = Math.tan(angleRad);
break;
default:
return;
}
this.currentOperand = parseFloat(result.toFixed(10));
}
logFunction(base = 10) {
const num = parseFloat(this.currentOperand);
if (num <= 0) {
this.currentOperand = 'Errore';
return;
}
if (base === 10) {
this.currentOperand = Math.log10(num);
} else {
this.currentOperand = Math.log(num);
}
}
percent() {
const num = parseFloat(this.currentOperand);
this.currentOperand = num / 100;
}
negate() {
this.currentOperand = parseFloat(this.currentOperand) * -1;
}
// Funzioni memoria
memoryAdd() {
this.memory += parseFloat(this.currentOperand);
}
memorySubtract() {
this.memory -= parseFloat(this.currentOperand);
}
memoryRecall() {
this.currentOperand = this.memory;
}
memoryClear() {
this.memory = 0;
}
// Costanti
addPi() {
this.currentOperand = Math.PI.toFixed(10);
}
addE() {
this.currentOperand = Math.E.toFixed(10);
}
updateDisplay() {
this.currentOperandTextElement.innerText = this.currentOperand;
if (this.operation != null) {
this.previousOperandTextElement.innerText =
`${this.previousOperand} ${this.operation}`;
} else {
this.previousOperandTextElement.innerText = '';
}
}
clearHistory() {
this.history = [];
}
showHistory() {
alert("Storico delle operazioni:\n\n" +
this.history.map((item, index) =>
`${index + 1}. ${item.expression} = ${item.result}`
).join(‘\n’));
}
}
// Elementi DOM
const numberButtons = document.querySelectorAll(‘[data-action=”number”]’);
const operationButtons = document.querySelectorAll(‘[data-action=”add”], [data-action=”subtract”], [data-action=”multiply”], [data-action=”divide”]’);
const equalsButton = document.querySelector(‘[data-action=”calculate”]’);
const deleteButton = document.querySelector(‘[data-action=”delete”]’);
const allClearButton = document.querySelector(‘[data-action=”clear”]’);
const percentButton = document.querySelector(‘[data-action=”percent”]’);
const negateButton = document.querySelector(‘[data-action=”negate”]’);
const previousOperandTextElement = document.querySelector(‘.previous-operand’);
const currentOperandTextElement = document.querySelector(‘.current-operand’);
// Funzioni scientifiche
const squareButton = document.querySelector(‘[data-action=”square”]’);
const powerButton = document.querySelector(‘[data-action=”power”]’);
const sqrtButton = document.querySelector(‘[data-action=”sqrt”]’);
const nthRootButton = document.querySelector(‘[data-action=”nth-root”]’);
const factorialButton = document.querySelector(‘[data-action=”factorial”]’);
const sinButton = document.querySelector(‘[data-action=”sin”]’);
const cosButton = document.querySelector(‘[data-action=”cos”]’);
const tanButton = document.querySelector(‘[data-action=”tan”]’);
const logButton = document.querySelector(‘[data-action=”log”]’);
const lnButton = document.querySelector(‘[data-action=”ln”]’);
const piButton = document.querySelector(‘[data-action=”pi”]’);
const eButton = document.querySelector(‘[data-action=”e”]’);
// Funzioni memoria
const memoryAddButton = document.querySelector(‘[data-action=”memory-add”]’);
const memorySubtractButton = document.querySelector(‘[data-action=”memory-subtract”]’);
const memoryRecallButton = document.querySelector(‘[data-action=”memory-recall”]’);
const memoryClearButton = document.querySelector(‘[data-action=”memory-clear”]’);
const historyButton = document.querySelector(‘[data-action=”clear-history”]’);
// Inizializza la calcolatrice
const calculator = new Calculator(previousOperandTextElement, currentOperandTextElement);
// Aggiungi event listener per i numeri
numberButtons.forEach(button => {
button.addEventListener(‘click’, () => {
calculator.appendNumber(button.innerText);
calculator.updateDisplay();
});
});
// Aggiungi event listener per gli operatori
operationButtons.forEach(button => {
button.addEventListener(‘click’, () => {
calculator.chooseOperation(button.innerText);
calculator.updateDisplay();
});
});
// Pulsante uguale
equalsButton.addEventListener(‘click’, () => {
// Gestione speciale per operazioni come x^y e y√x
if (calculator.operation === ‘^’) {
const base = parseFloat(calculator.previousOperand);
const exponent = parseFloat(calculator.currentOperand);
calculator.currentOperand = Math.pow(base, exponent);
calculator.operation = undefined;
calculator.previousOperand = ”;
calculator.updateDisplay();
return;
}
if (calculator.operation === ‘√’) {
const number = parseFloat(calculator.previousOperand);
const root = parseFloat(calculator.currentOperand);
if (number < 0 && root % 2 === 0) {
calculator.currentOperand = 'Errore';
} else {
calculator.currentOperand = Math.pow(number, 1/root);
}
calculator.operation = undefined;
calculator.previousOperand = '';
calculator.updateDisplay();
return;
}
calculator.compute();
calculator.updateDisplay();
});
// Pulsante cancella tutto
allClearButton.addEventListener('click', () => {
calculator.clear();
calculator.updateDisplay();
});
// Pulsante cancella ultimo carattere
deleteButton.addEventListener(‘click’, () => {
calculator.delete();
calculator.updateDisplay();
});
// Pulsante percentuale
percentButton.addEventListener(‘click’, () => {
calculator.percent();
calculator.updateDisplay();
});
// Pulsante cambia segno
negateButton.addEventListener(‘click’, () => {
calculator.negate();
calculator.updateDisplay();
});
// Funzioni scientifiche
squareButton.addEventListener(‘click’, () => {
calculator.square();
calculator.updateDisplay();
});
powerButton.addEventListener(‘click’, () => {
calculator.power();
calculator.updateDisplay();
});
sqrtButton.addEventListener(‘click’, () => {
calculator.sqrt();
calculator.updateDisplay();
});
nthRootButton.addEventListener(‘click’, () => {
calculator.nthRoot();
calculator.updateDisplay();
});
factorialButton.addEventListener(‘click’, () => {
calculator.factorial();
calculator.updateDisplay();
});
sinButton.addEventListener(‘click’, () => {
calculator.trigFunction(‘sin’);
calculator.updateDisplay();
});
cosButton.addEventListener(‘click’, () => {
calculator.trigFunction(‘cos’);
calculator.updateDisplay();
});
tanButton.addEventListener(‘click’, () => {
calculator.trigFunction(‘tan’);
calculator.updateDisplay();
});
logButton.addEventListener(‘click’, () => {
calculator.logFunction(10);
calculator.updateDisplay();
});
lnButton.addEventListener(‘click’, () => {
calculator.logFunction();
calculator.updateDisplay();
});
piButton.addEventListener(‘click’, () => {
calculator.addPi();
calculator.updateDisplay();
});
eButton.addEventListener(‘click’, () => {
calculator.addE();
calculator.updateDisplay();
});
// Funzioni memoria
memoryAddButton.addEventListener(‘click’, () => {
calculator.memoryAdd();
});
memorySubtractButton.addEventListener(‘click’, () => {
calculator.memorySubtract();
});
memoryRecallButton.addEventListener(‘click’, () => {
calculator.memoryRecall();
calculator.updateDisplay();
});
memoryClearButton.addEventListener(‘click’, () => {
calculator.memoryClear();
});
historyButton.addEventListener(‘click’, () => {
calculator.showHistory();
});
// Supporto tastiera
document.addEventListener(‘keydown’, (e) => {
if (e.key >= 0 && e.key <= 9) {
calculator.appendNumber(e.key);
calculator.updateDisplay();
} else if (e.key === '.' || e.key === ',') {
calculator.appendNumber('.');
calculator.updateDisplay();
} else if (e.key === '+' || e.key === '-' || e.key === '*' || e.key === '/') {
const opMap = {
'+': '+',
'-': '-',
'*': '×',
'/': '÷'
};
calculator.chooseOperation(opMap[e.key]);
calculator.updateDisplay();
} else if (e.key === 'Enter' || e.key === '=') {
calculator.compute();
calculator.updateDisplay();
} else if (e.key === 'Escape') {
calculator.clear();
calculator.updateDisplay();
} else if (e.key === 'Backspace') {
calculator.delete();
calculator.updateDisplay();
}
});
</script>
</body>
</html>
13. Conclusione
Creare una calcolatrice scientifica è un progetto stimolante che combina competenze di sviluppo front-end con conoscenze matematiche. Seguendo questa guida, hai imparato:
- Come strutturare l’interfaccia utente con HTML e CSS
- Come implementare la logica di base e avanzata con JavaScript
- Come gestire gli errori e validare gli input
- Come ottimizzare le prestazioni della tua applicazione
- Come testare e distribuire il tuo progetto
Ricorda che la chiave per diventare un sviluppatore esperto è la pratica costante. Prova a espandere questa calcolatrice aggiungendo nuove funzionalità, migliorando il design o ottimizzando le prestazioni. Ogni progetto ti porterà un passo più vicino alla padronanza dello sviluppo web.
Se hai trovato utile questa guida, considera di condividerla con altri sviluppatori che potrebbero trarne beneficio. Buona programmazione!