Python & AI Tutorials Logo
Programmazione Python

4. Lavorare con i numeri

Nel Capitolo 3 hai imparato come creare variabili e lavorare con i tipi numerici di base di Python: numeri interi (integers) e numeri in virgola mobile (floating-point numbers). Ora è il momento di mettere al lavoro questi numeri. In questo capitolo imparerai come eseguire calcoli, combinare operazioni e usare gli strumenti built-in di Python per lavorare con dati numerici.

I numeri sono fondamentali nella programmazione. Che tu stia calcolando totali, elaborando misurazioni, gestendo un inventario o analizzando dati, avrai bisogno di eseguire operazioni aritmetiche. Python rende le operazioni numeriche semplici e intuitive, ma ci sono dettagli importanti da capire—soprattutto su come funzionano i diversi tipi di divisione, come interagiscono gli operatori e come si comportano i numeri in virgola mobile.

Al termine di questo capitolo ti sentirai a tuo agio nell’eseguire calcoli, comprendere il comportamento degli operatori e usare le funzioni numeriche di Python per risolvere problemi del mondo reale.

4.1) Aritmetica di base: addizione, sottrazione e moltiplicazione

Iniziamo con le operazioni aritmetiche più fondamentali. Python usa simboli familiari per la matematica di base, e queste operazioni funzionano esattamente come ti aspetteresti dall’aritmetica quotidiana.

4.1.1) Addizione con l’operatore +

L’operatore + somma due numeri. Funziona sia con interi che con numeri in virgola mobile:

python
# basic_addition.py
# Adding integers
total = 15 + 27
print(total)  # Output: 42
 
# Adding floats
price = 19.99 + 5.50
print(price)  # Output: 25.49
 
# You can add multiple numbers in one expression
sum_total = 10 + 20 + 30 + 40
print(sum_total)  # Output: 100

L’addizione è semplice, ma c’è un dettaglio importante: quando sommi un intero e un float, Python converte automaticamente il risultato in un float per preservare la precisione decimale:

python
# mixed_addition.py
result = 10 + 3.5
print(result)  # Output: 13.5
print(type(result))  # Output: <class 'float'>

Questa conversione automatica avviene perché un float può rappresentare sia numeri interi che decimali, mentre un intero non può rappresentare decimali. Python sceglie il tipo che non perde informazioni.

4.1.2) Sottrazione con l’operatore -

L’operatore - sottrae il secondo numero dal primo:

python
# basic_subtraction.py
# Subtracting integers
difference = 100 - 42
print(difference)  # Output: 58
 
# Subtracting floats
remaining = 50.75 - 12.25
print(remaining)  # Output: 38.5
 
# Subtraction can produce negative results
balance = 20 - 35
print(balance)  # Output: -15

Come per l’addizione, anche la sottrazione promuove gli interi a float quando mescoli i tipi:

python
# mixed_subtraction.py
result = 100 - 0.01
print(result)  # Output: 99.99
print(type(result))  # Output: <class 'float'>

4.1.3) Moltiplicazione con l’operatore *

L’operatore * moltiplica due numeri:

python
# basic_multiplication.py
# Multiplying integers
product = 6 * 7
print(product)  # Output: 42
 
# Multiplying floats
area = 3.5 * 2.0
print(area)  # Output: 7.0
 
# Multiplying by zero always gives zero
result = 1000 * 0
print(result)  # Output: 0

La moltiplicazione segue le stesse regole di conversione dei tipi. Quando moltiplichi un intero per un float, il risultato è un float:

python
# mixed_multiplication.py
result = 5 * 2.5
print(result)  # Output: 12.5
print(type(result))  # Output: <class 'float'>
 
# Even if the result is a whole number
result = 4 * 2.0
print(result)  # Output: 8.0 (note the .0)
print(type(result))  # Output: <class 'float'>

Nota che nell’ultimo esempio, anche se 4 × 2.0 fa 8, Python lo rappresenta come 8.0 perché uno degli operandi era un float. Il tipo del risultato dipende dai tipi degli input, non dal fatto che il risultato matematico sia un numero intero.

4.1.4) Esempi pratici con l’aritmetica di base

Vediamo come queste operazioni funzionano insieme in scenari realistici:

python
# shopping_cart.py
# Calculate a shopping cart total
item1_price = 12.99
item2_price = 8.50
item3_price = 15.00
 
subtotal = item1_price + item2_price + item3_price
print(f"Subtotal: ${subtotal}")  # Output: Subtotal: $36.49
 
tax_rate = 0.08
tax = subtotal * tax_rate
print(f"Tax: ${tax}")  # Output: Tax: $2.9192
 
total = subtotal + tax
print(f"Total: ${total}")  # Output: Total: $39.4092
python
# temperature_change.py
# Calculate temperature change
morning_temp = 45.5
afternoon_temp = 68.2
 
change = afternoon_temp - morning_temp
print(f"Temperature increased by {change} degrees")
# Output: Temperature increased by 22.7 degrees

Queste operazioni di base costituiscono le fondamenta per calcoli più complessi. Capire come funzionano—soprattutto come Python gestisce le conversioni di tipo—ti aiuterà a evitare sorprese quando i tuoi programmi eseguono calcoli.

4.2) Divisione, divisione per difetto e resto: /, // e %

La divisione in Python è più sfumata rispetto ad addizione, sottrazione o moltiplicazione. Python fornisce tre diversi operatori legati alla divisione, ognuno con uno scopo distinto. Capire quando usare ciascuno è cruciale per scrivere programmi corretti.

4.2.1) Divisione regolare con /

L’operatore / esegue la “divisione reale” (true division): restituisce sempre un risultato in virgola mobile, anche quando divide due interi:

python
# true_division.py
# Dividing integers
result = 10 / 2
print(result)  # Output: 5.0 (note: float, not 5)
print(type(result))  # Output: <class 'float'>
 
# Division that doesn't result in a whole number
result = 10 / 3
print(result)  # Output: 3.3333333333333335
 
# Dividing floats
result = 15.5 / 2.5
print(result)  # Output: 6.2

Il punto chiave qui è che / produce sempre un float, anche quando entrambi gli operandi sono interi e il risultato è matematicamente un numero intero. Questo è voluto: Python vuole che la divisione si comporti in modo coerente e preservi la precisione decimale.

Questo comportamento è diverso da quello di alcuni altri linguaggi di programmazione, dove dividere due interi produce un risultato intero. In Python 3, se vuoi un risultato intero, devi usare la divisione per difetto (che vedremo tra poco) o convertire esplicitamente il risultato.

4.2.2) Divisione per difetto con //

L’operatore // esegue la “divisione per difetto” (floor division): divide e poi arrotonda per difetto al più vicino intero. Quando entrambi gli operandi sono interi, il risultato è un intero. Quando almeno un operando è un float, il risultato è un float (ma sempre arrotondato per difetto):

python
# floor_division.py
# Floor division with integers
result = 10 // 3
print(result)  # Output: 3 (not 3.333...)
print(type(result))  # Output: <class 'int'>
 
# Even division still gives an integer
result = 10 // 2
print(result)  # Output: 5 (integer, not 5.0)
print(type(result))  # Output: <class 'int'>
 
# Floor division with floats gives a float
result = 10.0 // 3
print(result)  # Output: 3.0
print(type(result))  # Output: <class 'float'>

“Arrotondare per difetto” significa muoversi verso meno infinito, non solo rimuovere la parte decimale. Questo è importante per i numeri negativi:

python
# floor_division_negative.py
# Positive numbers: rounds down (toward negative infinity)
result = 7 // 2
print(result)  # Output: 3
 
# Negative numbers: still rounds toward negative infinity
result = -7 // 2
print(result)  # Output: -4 (not -3!)
 
# Why -4? Because -3.5 rounded down (toward negative infinity) is -4
# Think of the number line: ... -4, -3.5, -3, -2, -1, 0, 1, 2, 3, 3.5, 4 ...

La divisione per difetto è utile quando devi suddividere elementi in gruppi o calcolare quante unità complete rientrano in una quantità:

python
# floor_division_practical.py
# How many complete dozens in 50 eggs?
eggs = 50
eggs_per_dozen = 12
complete_dozens = eggs // eggs_per_dozen
print(f"Complete dozens: {complete_dozens}")  # Output: Complete dozens: 4
 
# How many full hours in 150 minutes?
minutes = 150
full_hours = minutes // 60
print(f"Full hours: {full_hours}")  # Output: Full hours: 2

4.2.3) Resto (modulo) con %

L’operatore % (chiamato “modulo” o “mod”) restituisce il resto dopo la divisione. Risponde alla domanda: “Dopo la divisione, cosa rimane?”

python
# modulo_basic.py
# 10 divided by 3 is 3 with remainder 1
result = 10 % 3
print(result)  # Output: 1
 
# 10 divided by 2 is 5 with remainder 0 (even division)
result = 10 % 2
print(result)  # Output: 0
 
# Works with floats too
result = 10.5 % 3
print(result)  # Output: 1.5

L’operatore modulo è incredibilmente utile in diversi schemi comuni di programmazione:

Checking if a number is even or odd:

python
# even_odd_check.py
number = 17
 
if number % 2 == 0:
    print(f"{number} is even")
else:
    print(f"{number} is odd")
# Output: 17 is odd

Getting the leftover items:

python
# leftover_items.py
eggs = 50
eggs_per_dozen = 12
 
leftover = eggs % eggs_per_dozen
print(f"Leftover eggs: {leftover}")  # Output: Leftover eggs: 2

Wrapping values within a range (like clock arithmetic):

python
# clock_arithmetic.py
# What hour is it 25 hours from now? (on a 12-hour clock)
current_hour = 10
hours_later = 25
future_hour = (current_hour + hours_later) % 12
print(f"Hour: {future_hour}")  # Output: Hour: 11

4.2.4) Relazione tra //, % e /

La divisione per difetto e il modulo sono strettamente collegati. Insieme, ti forniscono il risultato completo della divisione:

python
# division_relationship.py
dividend = 17
divisor = 5
 
quotient = dividend // divisor  # How many times 5 goes into 17
remainder = dividend % divisor  # What's left over
 
print(f"{dividend} ÷ {divisor} = {quotient} remainder {remainder}")
# Output: 17 ÷ 5 = 3 remainder 2
 
# You can verify: quotient * divisor + remainder should equal dividend
verification = quotient * divisor + remainder
print(f"Verification: {quotient} × {divisor} + {remainder} = {verification}")
# Output: Verification: 3 × 5 + 2 = 17

Questa relazione è sempre vera: per qualsiasi coppia di numeri a e b (con b diverso da zero), a == (a // b) * b + (a % b).

4.2.5) Scegliere l’operatore di divisione giusto

Ecco una guida rapida per scegliere quale operatore usare:

  • Usa / quando ti serve il risultato matematico esatto con decimali (il più comune nei calcoli)
  • Usa // quando devi contare quante unità complete rientrano (come dozzine, ore, pagine)
  • Usa % quando ti serve il resto o la quantità rimanente (come controllare pari/dispari, far girare valori, trovare avanzi)
python
# choosing_operators.py
# Scenario: Distributing 47 candies among 6 children
 
candies = 47
children = 6
 
# How many candies per child? (use //)
per_child = candies // children
print(f"Each child gets {per_child} candies")  # Output: Each child gets 7 candies
 
# How many candies are left over? (use %)
leftover = candies % children
print(f"Leftover candies: {leftover}")  # Output: Leftover candies: 5
 
# What's the exact average? (use /)
average = candies / children
print(f"Average per child: {average}")  # Output: Average per child: 7.833333333333333

4.3) Operatori di assegnazione composta

Quando programmi, ti capiterà spesso di dover aggiornare una variabile eseguendo un’operazione sul suo valore corrente. Per esempio, aggiungere a un totale progressivo, sottrarre da un saldo o moltiplicare un valore per un fattore. Python fornisce un modo conciso di farlo con gli operatori di assegnazione composta.

4.3.1) Cosa sono gli operatori di assegnazione composta

Un operatore di assegnazione composta combina un’operazione aritmetica con l’assegnazione. Invece di scrivere:

python
# traditional_update.py
count = 10
count = count + 5  # Add 5 to count
print(count)  # Output: 15

Puoi scrivere:

python
# augmented_update.py
count = 10
count += 5  # Same as: count = count + 5
print(count)  # Output: 15

Entrambe le versioni fanno esattamente la stessa cosa, ma la versione con operatore composto è più concisa ed esprime chiaramente l’intento: “increase count by 5”.

4.3.2) Tutti gli operatori di assegnazione composta

Python fornisce operatori di assegnazione composta per tutte le operazioni aritmetiche:

python
# all_augmented_operators.py
# Addition
x = 10
x += 5  # x = x + 5
print(f"After += 5: {x}")  # Output: After += 5: 15
 
# Subtraction
x = 10
x -= 3  # x = x - 3
print(f"After -= 3: {x}")  # Output: After -= 3: 7
 
# Multiplication
x = 10
x *= 4  # x = x * 4
print(f"After *= 4: {x}")  # Output: After *= 4: 40
 
# Division
x = 10
x /= 2  # x = x / 2
print(f"After /= 2: {x}")  # Output: After /= 2: 5.0
 
# Floor division
x = 10
x //= 3  # x = x // 3
print(f"After //= 3: {x}")  # Output: After //= 3: 3
 
# Modulo
x = 10
x %= 3  # x = x % 3
print(f"After %= 3: {x}")  # Output: After %= 3: 1
 
# Exponentiation (we'll see more about ** in section 4.6)
x = 2
x **= 3  # x = x ** 3 (2 to the power of 3)
print(f"After **= 3: {x}")  # Output: After **= 3: 8

4.3.3) Casi d’uso comuni per l’assegnazione composta

Gli operatori di assegnazione composta brillano in diversi schemi comuni di programmazione:

Accumulating a total:

python
# accumulating_total.py
total = 0
total += 10  # Add first item
total += 25  # Add second item
total += 15  # Add third item
print(f"Total: {total}")  # Output: Total: 50

Counting occurrences:

python
# counting.py
count = 0
# Imagine these happen as we process data
count += 1  # Found one
count += 1  # Found another
count += 1  # Found another
print(f"Count: {count}")  # Output: Count: 3

Updating a balance:

python
# balance_updates.py
balance = 100.00
balance -= 25.50  # Purchase
balance += 50.00  # Deposit
balance -= 10.00  # Purchase
print(f"Balance: ${balance}")  # Output: Balance: $114.5

Applying repeated operations:

python
# repeated_operations.py
value = 100
value *= 1.1  # Increase by 10%
value *= 1.1  # Increase by 10% again
value *= 1.1  # Increase by 10% again
print(f"Value after three 10% increases: {value}")
# Output: Value after three 10% increases: 133.10000000000002

4.3.4) Dettagli importanti sugli operatori di assegnazione composta

Augmented assignment creates a new object for immutable types:

Ricorda dal Capitolo 3 che i numeri sono immutabili: non puoi cambiare il valore di un numero, puoi solo creare nuovi numeri. Quando scrivi x += 5, Python crea un nuovo oggetto numero e lo assegna a x. Il vecchio oggetto numero viene scartato (approfondiremo questo concetto nel Capitolo 17 quando parleremo del modello a oggetti di Python):

python
# augmented_with_immutables.py
x = 10
print(id(x))  # Output: (some memory address)
 
x += 5
print(id(x))  # Output: (different memory address)

Per ora, ti basta capire che x += 5 è equivalente a x = x + 5: è una scorciatoia comoda, non un’operazione fondamentalmente diversa.

You can't use augmented assignment before a variable exists:

python
# augmented_requires_existing.py
# This will cause an error:
# count += 1  # NameError: name 'count' is not defined
 
# You must initialize the variable first:
count = 0
count += 1  # Now this works
print(count)  # Output: 1

Type conversions happen the same way:

python
# augmented_type_conversion.py
x = 10  # integer
x += 2.5  # Add a float
print(x)  # Output: 12.5
print(type(x))  # Output: <class 'float'>

Il risultato segue le stesse regole di conversione di tipo degli operatori normali: se mescoli interi e float, il risultato è un float.

4.3.5) Quando usare l’assegnazione composta

Usa gli operatori di assegnazione composta ogni volta che aggiorni una variabile in base al suo valore corrente. Rendono il tuo codice:

  • Più conciso: x += 5 è più corto di x = x + 5
  • Più leggibile: l’intento è immediatamente chiaro
  • Meno soggetto a errori: riduci il rischio di digitare male il nome della variabile

Confronta queste due versioni:

python
# comparison.py
# Without augmented assignment
accumulated_distance_in_kilometers = 0
accumulated_distance_in_kilometers = accumulated_distance_in_kilometers + 10
accumulated_distance_in_kilometers = accumulated_distance_in_kilometers + 25
 
# With augmented assignment
accumulated_distance_in_kilometers = 0
accumulated_distance_in_kilometers += 10
accumulated_distance_in_kilometers += 25

La versione con assegnazione composta è più chiara e ha meno possibilità di typo. Gli operatori di assegnazione composta sono una piccola funzionalità, ma vengono usati costantemente nel codice Python reale.

4.4) Precedenza degli operatori e parentesi

Quando combini più operazioni in un’unica espressione, Python ha bisogno di regole per determinare l’ordine di valutazione. 2 + 3 * 4 deve essere calcolato come (2 + 3) * 4 = 20 oppure come 2 + (3 * 4) = 14? La risposta dipende dalla precedenza degli operatori: le regole che determinano quali operazioni avvengono per prime.

4.4.1) Comprendere la precedenza degli operatori

Python segue le stesse regole di precedenza che hai imparato in matematica: moltiplicazione e divisione avvengono prima di addizione e sottrazione. Questo spesso è ricordato con l’acronimo PEMDAS (Parentheses, Exponents, Multiplication/Division, Addition/Subtraction), anche se parleremo delle potenze nella sezione 4.6.

python
# precedence_basic.py
# Multiplication happens before addition
result = 2 + 3 * 4
print(result)  # Output: 14 (not 20)
# Python calculates: 2 + (3 * 4) = 2 + 12 = 14
 
# Division happens before subtraction
result = 10 - 8 / 2
print(result)  # Output: 6.0 (not 1.0)
# Python calculates: 10 - (8 / 2) = 10 - 4.0 = 6.0

Ecco l’ordine di precedenza per gli operatori che abbiamo visto finora (dal più alto al più basso):

  1. Parentesi () — precedenza più alta, valutate sempre per prime
  2. Esponenziazione ** — (ne parleremo nella sezione 4.6)
  3. Moltiplicazione, divisione, divisione per difetto, modulo *, /, //, % — stesso livello, valutati da sinistra a destra
  4. Addizione, sottrazione +, - — stesso livello, valutati da sinistra a destra

Vediamo altri esempi di come funziona la precedenza:

python
# precedence_examples.py
# Multiplication before addition
result = 5 + 2 * 3
print(result)  # Output: 11 (5 + 6)
 
# Division before subtraction
result = 20 - 10 / 2
print(result)  # Output: 15.0 (20 - 5.0)
 
# Multiple operations at the same level: left to right
result = 10 - 3 + 2
print(result)  # Output: 9 ((10 - 3) + 2)
 
result = 20 / 4 * 2
print(result)  # Output: 10.0 ((20 / 4) * 2)

4.4.2) Usare le parentesi per controllare l’ordine

Le parentesi sovrascrivono la precedenza predefinita. Le operazioni all’interno delle parentesi vengono sempre eseguite per prime, indipendentemente dagli operatori coinvolti:

python
# parentheses_override.py
# Without parentheses: multiplication first
result = 2 + 3 * 4
print(result)  # Output: 14
 
# With parentheses: addition first
result = (2 + 3) * 4
print(result)  # Output: 20
 
# Another example
result = 10 - 8 / 2
print(result)  # Output: 6.0
 
result = (10 - 8) / 2
print(result)  # Output: 1.0

Puoi annidare parentesi per creare espressioni più complesse. Python valuta dalle parentesi più interne verso l’esterno:

python
# nested_parentheses.py
result = ((2 + 3) * 4) - 1
print(result)  # Output: 19
# Step 1: (2 + 3) = 5
# Step 2: (5 * 4) = 20
# Step 3: 20 - 1 = 19
 
result = 2 * (3 + (4 - 1))
print(result)  # Output: 12
# Step 1: (4 - 1) = 3
# Step 2: (3 + 3) = 6
# Step 3: 2 * 6 = 12

4.4.3) Quando gli operatori hanno la stessa precedenza

Quando in un’espressione compaiono più operatori allo stesso livello di precedenza, Python li valuta da sinistra a destra (questo si chiama “associatività a sinistra”):

python
# left_to_right.py
# Addition and subtraction: left to right
result = 10 - 3 + 2 - 1
print(result)  # Output: 8
# Evaluation: ((10 - 3) + 2) - 1 = (7 + 2) - 1 = 9 - 1 = 8
 
# Multiplication and division: left to right
result = 20 / 4 * 2
print(result)  # Output: 10.0
# Evaluation: (20 / 4) * 2 = 5.0 * 2 = 10.0
 
# This matters! Different order gives different result:
result = 20 / (4 * 2)
print(result)  # Output: 2.5

4.4.4) Esempi pratici con la precedenza

Vediamo scenari realistici in cui la comprensione della precedenza è importante:

python
# temperature_conversion.py
# Convert Fahrenheit to Celsius: C = (F - 32) * 5 / 9
fahrenheit = 98.6
 
# Correct: parentheses ensure subtraction happens first
celsius = (fahrenheit - 32) * 5 / 9
print(f"{fahrenheit}°F = {celsius}°C")
# Output: 98.6°F = 37.0°C
 
# Wrong: without parentheses, multiplication happens first
# celsius = fahrenheit - 32 * 5 / 9  # This would be wrong!
# This would calculate: fahrenheit - ((32 * 5) / 9)
python
# calculate_average.py
# Calculate average of three numbers
num1 = 85
num2 = 92
num3 = 78
 
# Correct: parentheses ensure addition happens before division
average = (num1 + num2 + num3) / 3
print(f"Average: {average}")  # Output: Average: 85.0
 
# Wrong: without parentheses, only num3 is divided by 3
# average = num1 + num2 + num3 / 3  # This would be wrong!
# This would calculate: 85 + 92 + (78 / 3) = 85 + 92 + 26.0 = 203.0
python
# discount_calculation.py
# Calculate price after discount and tax
original_price = 100.00
discount_rate = 0.20
tax_rate = 0.08
 
# Calculate discount amount
discount = original_price * discount_rate
 
# Calculate price after discount
discounted_price = original_price - discount
 
# Calculate final price with tax
final_price = discounted_price * (1 + tax_rate)
print(f"Final price: ${final_price}")  # Output: Final price: $86.4
 
# Or in one expression (using parentheses to be clear):
final_price = (original_price * (1 - discount_rate)) * (1 + tax_rate)
print(f"Final price: ${final_price}")  # Output: Final price: $86.4

4.4.5) Buone pratiche per la precedenza degli operatori

Use parentheses for clarity, even when not strictly necessary:

A volte aggiungere parentesi rende più chiaro il tuo intento, anche se non cambiano il risultato:

python
# clarity_with_parentheses.py
# These are equivalent, but the second is clearer:
result = 2 + 3 * 4
result = 2 + (3 * 4)  # Clearer: shows you know multiplication happens first
 
# Complex expressions benefit from parentheses:
result = (subtotal * tax_rate) + (subtotal * tip_rate)  # Clear intent

Break complex expressions into steps:

Quando un’espressione diventa troppo complessa, spesso è meglio dividerla in più righe:

python
# breaking_into_steps.py
# Instead of this complex one-liner:
result = ((price * quantity) * (1 - discount)) * (1 + tax)
 
# Consider breaking it into steps:
subtotal = price * quantity
discounted = subtotal * (1 - discount)
final = discounted * (1 + tax)
result = final

La versione a più passaggi è più facile da leggere, fare debug e verificare. Non sacrificare la chiarezza per la concisione.

When in doubt, use parentheses:

Se non sei sicuro della precedenza, aggiungi parentesi. L’interprete Python non si lamenterà, e il tuo io futuro (o altri programmatori che leggeranno il tuo codice) ti ringrazieranno.

Capire la precedenza degli operatori ti aiuta a scrivere espressioni corrette e a leggere il codice degli altri. Il principio chiave: quando combini operazioni, pensa all’ordine di valutazione e usa le parentesi per rendere esplicito il tuo intento.

4.5) Mescolare interi e float nelle espressioni

Hai già visto che Python gestisce automaticamente la combinazione di interi e float nelle operazioni semplici. Ora esploriamo questo comportamento in modo più sistematico e capiamo le regole che governano la conversione dei tipi nelle espressioni numeriche.

4.5.1) La regola della promozione di tipo

Quando Python esegue un’operazione aritmetica che coinvolge sia un intero che un float, converte automaticamente (o “promuove”) l’intero a float prima di eseguire l’operazione. Il risultato è sempre un float:

python
# type_promotion.py
# Integer + Float = Float
result = 10 + 3.5
print(result)  # Output: 13.5
print(type(result))  # Output: <class 'float'>
 
# Float + Integer = Float (order doesn't matter)
result = 3.5 + 10
print(result)  # Output: 13.5
print(type(result))  # Output: <class 'float'>
 
# This applies to all arithmetic operators
result = 5 * 2.0
print(result)  # Output: 10.0 (float, not int)
print(type(result))  # Output: <class 'float'>

Perché Python fa questo? Perché i float possono rappresentare sia numeri interi che decimali, mentre gli interi non possono rappresentare decimali. Convertire in float preserva tutte le informazioni ed evita di perdere precisione.

Ecco una rappresentazione visiva di come Python decide il tipo del risultato:

Yes

No

Yes

No

Operation with
int and float

Any operand
is float?

Result is float

Division
operator /?

Result is int

4.5.2) Promozione di tipo nelle espressioni complesse

Quando un’espressione contiene più operazioni con tipi misti, Python applica la regola di promozione a ogni passaggio:

python
# complex_mixed_types.py
# Multiple operations with mixed types
result = 10 + 3.5 * 2
print(result)  # Output: 17.0
print(type(result))  # Output: <class 'float'>
 
# What happens step by step:
# 1. 3.5 * 2 → 3.5 * 2.0 (2 promoted to float) → 7.0 (float)
# 2. 10 + 7.0 → 10.0 + 7.0 (10 promoted to float) → 17.0 (float)
 
# Another example
result = 5 / 2 + 3
print(result)  # Output: 5.5
print(type(result))  # Output: <class 'float'>
 
# Step by step:
# 1. 5 / 2 → 2.5 (division always produces float)
# 2. 2.5 + 3 → 2.5 + 3.0 (3 promoted to float) → 5.5 (float)

Una volta che un’operazione in un’espressione produce un float, tutte le operazioni successive che coinvolgono quel risultato produrranno a loro volta float.

4.5.3) Caso speciale: la divisione regolare produce sempre float

Ricorda dalla sezione 4.2 che l’operatore / produce sempre un float, anche quando divide due interi:

python
# division_always_float.py
# Even when the result is a whole number
result = 10 / 2
print(result)  # Output: 5.0 (not 5)
print(type(result))  # Output: <class 'float'>
 
# This means any expression with / will have a float result
result = 10 / 2 + 3  # 5.0 + 3 → 5.0 + 3.0 → 8.0
print(result)  # Output: 8.0
print(type(result))  # Output: <class 'float'>

Se vuoi un risultato intero dalla divisione, usa invece la divisione per difetto // (ma ricorda che arrotonda per difetto):

python
# floor_division_integer.py
# Floor division with integers produces an integer
result = 10 // 2
print(result)  # Output: 5 (integer)
print(type(result))  # Output: <class 'int'>
 
# But floor division with any float produces a float
result = 10.0 // 2
print(result)  # Output: 5.0 (float)
print(type(result))  # Output: <class 'float'>

4.5.4) Implicazioni pratiche della combinazione di tipi

Capire la promozione di tipo ti aiuta a prevedere e controllare i tipi dei tuoi risultati:

python
# practical_type_mixing.py
# Calculating price per item
total_cost = 47.50
num_items = 5
 
price_per_item = total_cost / num_items  # Float / int → float
print(f"Price per item: ${price_per_item}")
# Output: Price per item: $4.75
 
# Calculating average (will be float even if inputs are integers)
total_points = 450
num_tests = 5
 
average = total_points / num_tests  # Int / int → float
print(f"Average: {average}")  # Output: Average: 90.0
 
# If you need an integer result, convert explicitly
average_rounded = int(total_points / num_tests)
print(f"Average (as integer): {average_rounded}")
# Output: Average (as integer): 90

4.5.5) Quando i risultati interi sono importanti

A volte hai specificamente bisogno di risultati interi per conteggi, indici o altre operazioni discrete. Ecco come assicurarti di ottenere interi:

python
# ensuring_integer_results.py
# Using floor division when you need integer results
items = 47
items_per_box = 12
 
# How many complete boxes?
complete_boxes = items // items_per_box  # Integer result
print(f"Complete boxes: {complete_boxes}")
# Output: Complete boxes: 3
 
# If you use regular division, you get a float
boxes_float = items / items_per_box
print(f"Boxes (float): {boxes_float}")
# Output: Boxes (float): 3.9166666666666665
 
# Converting float to integer (truncates toward zero)
boxes_truncated = int(boxes_float)
print(f"Boxes (truncated): {boxes_truncated}")
# Output: Boxes (truncated): 3

Nota la differenza: // arrotonda per difetto (verso meno infinito), mentre int() tronca verso zero. Per i numeri positivi sono uguali, ma per i negativi differiscono:

python
# truncation_vs_floor.py
# For positive numbers: same result
print(7 // 2)    # Output: 3
print(int(7/2))  # Output: 3
 
# For negative numbers: different results
print(-7 // 2)    # Output: -4 (rounds down toward negative infinity)
print(int(-7/2))  # Output: -3 (truncates toward zero)

4.5.6) Evitare risultati float inattesi

A volte potresti sorprenderti nel vedere un float quando ti aspettavi un intero. Questo di solito accade a causa della divisione o della combinazione di tipi:

python
# unexpected_floats.py
# Calculating average - result is always float because of division
count = 10
total = 100
average = total / count
print(average)  # Output: 10.0 (float, even though it's a whole number)
 
# If you need an integer and you know it divides evenly:
average_int = total // count
print(average_int)  # Output: 10 (integer)
 
# Or convert explicitly:
average_int = int(total / count)
print(average_int)  # Output: 10 (integer)

Il punto chiave: le regole di promozione di tipo di Python sono progettate per preservare la precisione ed evitare perdite di dati. Quando mescoli interi e float, o quando usi la divisione regolare, aspettati risultati float. Se ti servono interi, usa la divisione per difetto o la conversione esplicita, ma sii consapevole di come gestiscono l’arrotondamento.

4.6) Utili funzioni numeriche built-in: abs(), min(), max() e pow()

Python fornisce diverse funzioni built-in che eseguono operazioni numeriche comuni. Queste funzioni funzionano sia con interi che con numeri in virgola mobile e sono strumenti essenziali per la programmazione quotidiana. Esploriamo le più usate.

4.6.1) Valore assoluto con abs()

La funzione abs() restituisce il valore assoluto di un numero—la sua distanza da zero, ignorando il segno:

python
# absolute_value.py
# Absolute value of negative numbers
result = abs(-42)
print(result)  # Output: 42
 
# Absolute value of positive numbers (unchanged)
result = abs(42)
print(result)  # Output: 42
 
# Works with floats too
result = abs(-3.14)
print(result)  # Output: 3.14
 
# Absolute value of zero is zero
result = abs(0)
print(result)  # Output: 0

La funzione valore assoluto è utile ogni volta che ti interessa la magnitudine ma non la direzione:

python
# practical_abs.py
# Calculate temperature difference (magnitude only)
morning_temp = 45.5
evening_temp = 38.2
 
difference = abs(evening_temp - morning_temp)
print(f"Temperature changed by {difference} degrees")
# Output: Temperature changed by 7.3 degrees
 
# Calculate distance between two points (always positive)
position1 = 10
position2 = 25
 
distance = abs(position2 - position1)
print(f"Distance: {distance}")  # Output: Distance: 15
 
# Works the same regardless of order
distance = abs(position1 - position2)
print(f"Distance: {distance}")  # Output: Distance: 15

4.6.2) Trovare minimo e massimo con min() e max()

La funzione min() restituisce il più piccolo tra i suoi argomenti, e max() restituisce il più grande:

python
# min_max_basic.py
# Find minimum of two numbers
smallest = min(10, 25)
print(smallest)  # Output: 10
 
# Find maximum of two numbers
largest = max(10, 25)
print(largest)  # Output: 25
 
# Works with more than two arguments
smallest = min(5, 12, 3, 18, 7)
print(smallest)  # Output: 3
 
largest = max(5, 12, 3, 18, 7)
print(largest)  # Output: 18
 
# Works with floats and mixed types
smallest = min(3.5, 2, 4.1, 1.9)
print(smallest)  # Output: 1.9

Queste funzioni sono preziose per trovare gli estremi nei dati:

python
# practical_min_max.py
# Find the best and worst test scores
test1 = 85
test2 = 92
test3 = 78
test4 = 95
 
highest_score = max(test1, test2, test3, test4)
lowest_score = min(test1, test2, test3, test4)
 
print(f"Highest score: {highest_score}")  # Output: Highest score: 95
print(f"Lowest score: {lowest_score}")    # Output: Lowest score: 78
 
# Clamp a value within a range
value = 150
minimum = 0
maximum = 100
 
# Ensure value is not below minimum
value = max(value, minimum)
# Ensure value is not above maximum
value = min(value, maximum)
 
print(f"Clamped value: {value}")  # Output: Clamped value: 100

Il pattern di clamping (usare max() e min() insieme) è comune quando devi vincolare un valore a un intervallo specifico:

python
# clamping_pattern.py
def clamp(value, min_value, max_value):
    """Constrain value to be within [min_value, max_value]"""
    return max(min_value, min(value, max_value))
 
# Test the clamp function
print(clamp(150, 0, 100))   # Output: 100 (too high, clamped to max)
print(clamp(-10, 0, 100))   # Output: 0 (too low, clamped to min)
print(clamp(50, 0, 100))    # Output: 50 (within range, unchanged)

4.6.3) Esponenziazione con pow()

La funzione pow() eleva un numero a potenza. Python dispone anche dell’operatore ** per l’esponenziazione, che è più comunemente usato, ma pow() offre funzionalità aggiuntive:

python
# exponentiation.py
# Using the ** operator (most common)
result = 2 ** 3  # 2 to the power of 3
print(result)  # Output: 8
 
result = 5 ** 2  # 5 squared
print(result)  # Output: 25
 
# Using pow() function (equivalent)
result = pow(2, 3)
print(result)  # Output: 8
 
result = pow(5, 2)
print(result)  # Output: 25
 
# Negative exponents give fractions
result = 2 ** -3  # 1 / (2^3) = 1/8
print(result)  # Output: 0.125
 
# Fractional exponents give roots
result = 9 ** 0.5  # Square root of 9
print(result)  # Output: 3.0
 
result = 8 ** (1/3)  # Cube root of 8
print(result)  # Output: 2.0

La funzione pow() può accettare un terzo argomento opzionale per l’esponenziazione modulare (utile in crittografia e teoria dei numeri, ma oltre lo scopo dell’aritmetica di base):

python
# modular_exponentiation.py
# pow(base, exponent, modulus) computes (base ** exponent) % modulus efficiently
result = pow(2, 10, 100)  # (2^10) % 100
print(result)  # Output: 24
 
# This is more efficient than computing separately for large numbers:
# result = (2 ** 10) % 100  # Same result, but less efficient for large numbers

Per la maggior parte degli usi quotidiani, l’operatore ** è più comodo di pow():

python
# practical_exponentiation.py
# Calculate compound interest: A = P(1 + r)^t
principal = 1000  # Initial amount
rate = 0.05       # 5% interest rate
years = 10
 
amount = principal * (1 + rate) ** years
print(f"Amount after {years} years: ${amount:.2f}")
# Output: Amount after 10 years: $1628.89
 
# Calculate area of a square
side_length = 5
area = side_length ** 2
print(f"Area: {area}")  # Output: Area: 25
 
# Calculate volume of a cube
side_length = 3
volume = side_length ** 3
print(f"Volume: {volume}")  # Output: Volume: 27

4.6.4) Combinare le funzioni built-in

Queste funzioni lavorano bene insieme per risolvere problemi comuni:

python
# combining_functions.py
# Find the range (difference between max and min)
values = [15, 42, 8, 23, 37]
value_range = max(values) - min(values)
print(f"Range: {value_range}")  # Output: Range: 34
 
# Note: We're using a list here (we'll learn about lists in detail in Chapter 13)
# For now, just understand that max() and min() can work with a list of values
 
# Calculate distance between two points in 2D space
x1, y1 = 3, 4
x2, y2 = 6, 8
 
# Distance formula: sqrt((x2-x1)^2 + (y2-y1)^2)
# We'll use ** for squaring (square root comes in section 4.11)
distance_squared = (x2 - x1) ** 2 + (y2 - y1) ** 2
distance = distance_squared ** 0.5  # Square root via fractional exponent
print(f"Distance: {distance}")  # Output: Distance: 5.0

Queste funzioni built-in sono strumenti fondamentali nella programmazione Python. Sono efficienti, ben testate e funzionano in modo coerente tra diversi tipi numerici. Usale ogni volta che ti servono queste operazioni comuni: non c’è bisogno di scrivere le tue implementazioni.

4.7) Arrotondare i numeri con round()

Quando lavori con numeri in virgola mobile, spesso devi arrotondare i risultati a un certo numero di decimali per visualizzazione, calcoli o memorizzazione. La funzione round() di Python fornisce questa funzionalità.

4.7.1) Arrotondamento di base con round()

La funzione round() accetta un numero e lo arrotonda all’intero più vicino:

python
# basic_rounding.py
# Round to nearest integer
result = round(3.7)
print(result)  # Output: 4
 
result = round(3.2)
print(result)  # Output: 3
 
# Exactly halfway rounds to nearest even number (banker's rounding)
result = round(2.5)
print(result)  # Output: 2 (rounds to even)
 
result = round(3.5)
print(result)  # Output: 4 (rounds to even)
 
# Negative numbers work too
result = round(-3.7)
print(result)  # Output: -4
 
result = round(-3.2)
print(result)  # Output: -3

Nota il comportamento quando si arrotondano numeri esattamente a metà tra due interi (come 2.5 o 3.5). Python usa il metodo “round half to even” (chiamato anche “banker’s rounding”), che arrotonda verso il numero pari più vicino. Questo riduce i bias negli arrotondamenti ripetuti. Per la maggior parte della programmazione quotidiana, questo dettaglio conta raramente, ma è bene esserne consapevoli.

4.7.2) Arrotondare a un numero specifico di cifre decimali

La funzione round() accetta un secondo argomento opzionale che specifica quante cifre decimali mantenere:

python
# decimal_places.py
# Round to 2 decimal places
result = round(3.14159, 2)
print(result)  # Output: 3.14
 
# Round to 1 decimal place
result = round(3.14159, 1)
print(result)  # Output: 3.1
 
# Round to 3 decimal places
result = round(2.71828, 3)
print(result)  # Output: 2.718
 
# You can round to 0 decimal places (same as omitting the argument)
result = round(3.7, 0)
print(result)  # Output: 4.0 (note: returns float, not int)

Quando specifichi le cifre decimali, round() restituisce un float (anche se arrotondi a 0 decimali). Quando ometti il secondo argomento, restituisce un intero.

4.7.3) Usi pratici dell’arrotondamento

L’arrotondamento è essenziale per visualizzare denaro, misurazioni e altri valori in cui una precisione eccessiva è inutile o fuorviante:

python
# practical_rounding.py
# Display prices with 2 decimal places
price = 19.99
tax_rate = 0.08
total = price * (1 + tax_rate)
 
print(f"Total (unrounded): ${total}")  # Output: Total (unrounded): $21.5892
print(f"Total (rounded): ${round(total, 2)}")  # Output: Total (rounded): $21.59
 
# Calculate and display average
total_score = 456
num_tests = 7
average = total_score / num_tests
 
print(f"Average (unrounded): {average}")  # Output: Average (unrounded): 65.14285714285714
print(f"Average (rounded): {round(average, 2)}")  # Output: Average (rounded): 65.14
 
# Round measurements to reasonable precision
distance_meters = 123.456789
distance_rounded = round(distance_meters, 1)
print(f"Distance: {distance_rounded} meters")  # Output: Distance: 123.5 meters

4.7.4) Arrotondare vs troncare vs floor/ceil

È importante capire che l’arrotondamento è diverso dal troncare (rimuovere le cifre decimali) o dalle operazioni floor/ceil:

python
# rounding_vs_others.py
value = 3.7
 
# Rounding: nearest integer
rounded = round(value)
print(f"Rounded: {rounded}")  # Output: Rounded: 4
 
# Truncating: remove decimal part (convert to int)
truncated = int(value)
print(f"Truncated: {truncated}")  # Output: Truncated: 3
 
# We'll see floor and ceil in section 4.11, but briefly:
# Floor: largest integer <= value (always rounds down)
# Ceiling: smallest integer >= value (always rounds up)

Per i numeri negativi, le differenze sono più evidenti:

python
# negative_rounding.py
value = -3.7
 
# Rounding: nearest integer
rounded = round(value)
print(f"Rounded: {rounded}")  # Output: Rounded: -4
 
# Truncating: toward zero
truncated = int(value)
print(f"Truncated: {truncated}")  # Output: Truncated: -3
 
# Floor (rounds down toward negative infinity): -4
# Ceiling (rounds up toward positive infinity): -3

4.7.5) Considerazioni importanti sull’arrotondamento

La precisione dei float può causare sorprese:

A causa di come i computer rappresentano i numeri in virgola mobile (di cui parleremo nella sezione 4.10), l’arrotondamento non produce sempre esattamente il risultato che potresti aspettarti:

python
# rounding_surprises.py
# Sometimes rounding doesn't give the exact decimal you expect
value = 2.675
rounded = round(value, 2)
print(rounded)  # Output: 2.67 (not 2.68 as you might expect)
 
# This happens because 2.675 can't be represented exactly in binary floating-point
# The actual stored value is slightly less than 2.675

Per la maggior parte degli scopi pratici, questo non è un problema. Ma se stai lavorando con calcoli finanziari dove serve aritmetica decimale esatta, potresti aver bisogno del modulo decimal di Python (che non tratteremo in questo libro, ma è utile sapere che esiste).

Arrotondare per la visualizzazione vs per il calcolo:

Spesso vuoi arrotondare solo per la visualizzazione, mantenendo la piena precisione per i calcoli:

python
# rounding_for_display.py
price1 = 19.99
price2 = 15.49
tax_rate = 0.08
 
# Calculate with full precision
subtotal = price1 + price2
tax = subtotal * tax_rate
total = subtotal + tax
 
# Round only for display
print(f"Subtotal: ${round(subtotal, 2)}")  # Output: Subtotal: $35.48
print(f"Tax: ${round(tax, 2)}")            # Output: Tax: $2.84
print(f"Total: ${round(total, 2)}")        # Output: Total: $38.32
 
# The variables still contain full precision
print(f"Total (full precision): ${total}")
# Output: Total (full precision): $38.3184

La funzione round() è una delle funzioni built-in più utilizzate in Python. Usala ogni volta che devi presentare risultati numerici in un formato leggibile o quando devi limitare la precisione per uno scopo specifico.

4.8) Pattern numerici comuni (contatori, totali, medie)

Ora che conosci le operazioni numeriche e le funzioni di Python, esploriamo alcuni pattern comuni che userai ripetutamente nei programmi reali. Questi pattern costituiscono i mattoni di base per elaborare dati numerici.

4.8.1) Contatori: tenere traccia del “quante volte”

Un contatore è una variabile che tiene traccia di quante volte qualcosa accade. La inizializzi a zero e la incrementi ogni volta che un evento si verifica:

python
# basic_counter.py
# Count how many numbers we've processed
count = 0  # Initialize counter
 
# Process first number
count += 1
print(f"Processed {count} number(s)")  # Output: Processed 1 number(s)
 
# Process second number
count += 1
print(f"Processed {count} number(s)")  # Output: Processed 2 number(s)
 
# Process third number
count += 1
print(f"Processed {count} number(s)")  # Output: Processed 3 number(s)

Nei programmi reali, useresti tipicamente i contatori all’interno di cicli (loops), che vedremo nei Capitoli 10 e 11. Per ora, capisci il pattern: parti da zero, aggiungi uno ogni volta.

I contatori possono tenere traccia di diversi tipi di eventi:

python
# multiple_counters.py
# Track different categories
even_count = 0
odd_count = 0
 
# Check number 1
number = 4
if number % 2 == 0:
    even_count += 1
else:
    odd_count += 1
 
# Check number 2
number = 7
if number % 2 == 0:
    even_count += 1
else:
    odd_count += 1
 
# Check number 3
number = 10
if number % 2 == 0:
    even_count += 1
else:
    odd_count += 1
 
print(f"Even numbers: {even_count}")  # Output: Even numbers: 2
print(f"Odd numbers: {odd_count}")    # Output: Odd numbers: 1

4.8.2) Accumulatori: costruire totali progressivi

Un accumulatore è una variabile che raccoglie un totale progressivo. Come un contatore, la inizializzi a zero, ma invece di aggiungere uno ogni volta, aggiungi importi variabili:

python
# basic_accumulator.py
# Calculate total sales
total_sales = 0  # Initialize accumulator
 
# First sale
sale = 19.99
total_sales += sale
print(f"Total so far: ${total_sales}")  # Output: Total so far: $19.99
 
# Second sale
sale = 34.50
total_sales += sale
print(f"Total so far: ${total_sales}")  # Output: Total so far: $54.49
 
# Third sale
sale = 12.00
total_sales += sale
print(f"Total so far: ${total_sales}")  # Output: Total so far: $66.49

Gli accumulatori sono fondamentali per l’elaborazione dei dati. Ti permettono di aggregare valori mentre li processi:

python
# multiple_accumulators.py
# Track both total and count to calculate average later
total_score = 0
count = 0
 
# Process score 1
score = 85
total_score += score
count += 1
 
# Process score 2
score = 92
total_score += score
count += 1
 
# Process score 3
score = 78
total_score += score
count += 1
 
print(f"Total score: {total_score}")  # Output: Total score: 255
print(f"Number of scores: {count}")   # Output: Number of scores: 3

4.8.3) Calcolare le medie

Una media combina i pattern di contatore e accumulatore: ti serve sia un totale (accumulatore) sia un conteggio (contatore), poi fai la divisione:

python
# calculating_average.py
# Calculate average of test scores
total_score = 0
count = 0
 
# Add scores
total_score += 85
count += 1
 
total_score += 92
count += 1
 
total_score += 78
count += 1
 
total_score += 88
count += 1
 
# Calculate average
average = total_score / count
print(f"Average score: {average}")  # Output: Average score: 85.75
 
# Often you'll want to round the average
average_rounded = round(average, 2)
print(f"Average (rounded): {average_rounded}")  # Output: Average (rounded): 85.75

Important: Always check for division by zero:

Quando calcoli medie, devi assicurarti che il conteggio non sia zero:

python
# safe_average.py
total_score = 0
count = 0
 
# If no scores were added, count is still 0
# Dividing by zero causes an error!
 
if count > 0:
    average = total_score / count
    print(f"Average: {average}")
else:
    print("No scores to average")
    # Output: No scores to average

Impareremo di più su come gestire condizioni di questo tipo nel Capitolo 8.

4.8.4) Trovare massimo e minimo progressivi

A volte devi tenere traccia del valore più grande o più piccolo visto finora: Puoi usare le funzioni max() e min() per implementare questo:

python
# running_max_min_simplified.py
# Track highest and lowest using max() and min()
 
highest_temp = 72
lowest_temp = 72
 
# Update with new temperature
current_temp = 85
highest_temp = max(highest_temp, current_temp)
lowest_temp = min(lowest_temp, current_temp)
 
# Update with another temperature
current_temp = 68
highest_temp = max(highest_temp, current_temp)
lowest_temp = min(lowest_temp, current_temp)
 
print(f"High: {highest_temp}, Low: {lowest_temp}")
# Output: High: 85, Low: 68

4.9) Operatori bit a bit per interi: &, |, ^, <<, >> (breve panoramica)

Gli operatori bit a bit lavorano sui singoli bit (cifre binarie) degli interi. Anche se non userai questi operatori nella programmazione quotidiana tanto spesso quanto gli operatori aritmetici, sono importanti per alcuni compiti come lavorare con flag, permessi, manipolazione di dati a basso livello e ottimizzazione delle prestazioni.

Questa sezione fornisce una breve panoramica. Comprendere le operazioni bit a bit richiede di capire la rappresentazione binaria, che è un argomento più profondo di quanto possiamo esplorare completamente in questo capitolo introduttivo.

4.9.1) Comprendere la rappresentazione binaria (introduzione rapida)

I computer memorizzano gli interi come sequenze di bit (0 e 1). Per esempio:

  • Il decimale 5 è il binario 101 (1×4 + 0×2 + 1×1)
  • Il decimale 12 è il binario 1100 (1×8 + 1×4 + 0×2 + 0×1)

La funzione bin() di Python mostra la rappresentazione binaria di un intero:

python
# binary_representation.py
# See binary representation of integers
print(bin(5))   # Output: 0b101
print(bin(12))  # Output: 0b1100
print(bin(255)) # Output: 0b11111111
 
# The 0b prefix indicates binary notation

4.9.2) AND bit a bit (&)

L’operatore & esegue un AND bit a bit: ogni bit del risultato è 1 solo se i bit corrispondenti in entrambi gli operandi sono 1:

python
# bitwise_and.py
# 12 is 1100 in binary
# 10 is 1010 in binary
result = 12 & 10
print(result)  # Output: 8
print(bin(result))  # Output: 0b1000
 
# How it works:
#   1100  (12)
# & 1010  (10)
# ------
#   1000  (8)

L’AND bit a bit è spesso usato per controllare se specifici bit sono impostati (test dei flag):

python
# checking_flags.py
# File permissions example (simplified)
READ = 4    # 100 in binary
WRITE = 2   # 010 in binary
EXECUTE = 1 # 001 in binary
 
permissions = 6  # 110 in binary (READ + WRITE)
 
# Check if READ permission is set
has_read = (permissions & READ) != 0
print(f"Has read: {has_read}")  # Output: Has read: True
 
# Check if EXECUTE permission is set
has_execute = (permissions & EXECUTE) != 0
print(f"Has execute: {has_execute}")  # Output: Has execute: False

4.9.3) OR bit a bit (|)

L’operatore | esegue un OR bit a bit: ogni bit del risultato è 1 se il bit corrispondente in almeno uno degli operandi è 1:

python
# bitwise_or.py
# 12 is 1100 in binary
# 10 is 1010 in binary
result = 12 | 10
print(result)  # Output: 14
print(bin(result))  # Output: 0b1110
 
# How it works:
#   1100  (12)
# | 1010  (10)
# ------
#   1110  (14)

L’OR bit a bit è usato per combinare flag:

python
# combining_flags.py
READ = 4    # 100 in binary
WRITE = 2   # 010 in binary
EXECUTE = 1 # 001 in binary
 
# Grant READ and WRITE permissions
permissions = READ | WRITE
print(f"Permissions: {permissions}")  # Output: Permissions: 6
print(bin(permissions))  # Output: 0b110
 
# Add EXECUTE permission
permissions = permissions | EXECUTE
print(f"Permissions: {permissions}")  # Output: Permissions: 7
print(bin(permissions))  # Output: 0b111

4.9.4) XOR bit a bit (^)

L’operatore ^ esegue uno XOR (exclusive OR) bit a bit: ogni bit del risultato è 1 se i bit corrispondenti negli operandi sono diversi:

python
# bitwise_xor.py
# 12 is 1100 in binary
# 10 is 1010 in binary
result = 12 ^ 10
print(result)  # Output: 6
print(bin(result))  # Output: 0b110
 
# How it works:
#   1100  (12)
# ^ 1010  (10)
# ------
#   0110  (6)

Lo XOR ha proprietà interessanti, come il toggle dei bit o lo scambio di valori:

python
# xor_properties.py
# XOR with itself gives 0
result = 5 ^ 5
print(result)  # Output: 0
 
# XOR with 0 gives the original number
result = 5 ^ 0
print(result)  # Output: 5
 
# XOR is its own inverse (useful for simple encryption)
original = 42
key = 123
encrypted = original ^ key
decrypted = encrypted ^ key
print(f"Original: {original}, Encrypted: {encrypted}, Decrypted: {decrypted}")
# Output: Original: 42, Encrypted: 81, Decrypted: 42

4.9.5) Shift a sinistra (<<) e a destra (>>)

L’operatore << sposta i bit verso sinistra, e >> sposta i bit verso destra:

python
# bit_shifting.py
# Left shift: multiply by powers of 2
result = 5 << 1  # Shift left by 1 bit
print(result)  # Output: 10
# 5 is 101 in binary, shifted left becomes 1010 (10)
 
result = 5 << 2  # Shift left by 2 bits
print(result)  # Output: 20
# 5 is 101 in binary, shifted left becomes 10100 (20)
 
# Right shift: divide by powers of 2 (floor division)
result = 20 >> 1  # Shift right by 1 bit
print(result)  # Output: 10
# 20 is 10100 in binary, shifted right becomes 1010 (10)
 
result = 20 >> 2  # Shift right by 2 bits
print(result)  # Output: 5
# 20 is 10100 in binary, shifted right becomes 101 (5)

Lo shift a sinistra di n bit moltiplica per 2^n, e lo shift a destra divide per 2^n (divisione per difetto):

python
# shift_as_multiplication.py
# Left shift multiplies by powers of 2
print(3 << 1)  # Output: 6 (3 * 2^1 = 3 * 2)
print(3 << 2)  # Output: 12 (3 * 2^2 = 3 * 4)
print(3 << 3)  # Output: 24 (3 * 2^3 = 3 * 8)
 
# Right shift divides by powers of 2
print(24 >> 1)  # Output: 12 (24 // 2^1 = 24 // 2)
print(24 >> 2)  # Output: 6 (24 // 2^2 = 24 // 4)
print(24 >> 3)  # Output: 3 (24 // 2^3 = 24 // 8)

4.9.6) Quando usare gli operatori bit a bit

Gli operatori bit a bit sono utili in scenari specifici:

  • Lavorare con flag e permessi binari (come i permessi dei file in Unix/Linux)
  • Manipolazione di dati a basso livello (impacchettare più valori in un unico intero)
  • Programmazione di rete (manipolare indirizzi IP, flag di protocollo)
  • Grafica e programmazione di giochi (manipolazione dei colori, rilevamento collisioni)
  • Ottimizzazione delle prestazioni (lo shift di bit è più veloce di moltiplicazione/divisione per potenze di 2)

Per la maggior parte dei compiti di programmazione quotidiana, non avrai bisogno degli operatori bit a bit. Ma quando ti servono—soprattutto lavorando con programmazione di sistema, networking o codice critico per le prestazioni—sono strumenti preziosi.

Nota: Questa panoramica copre le basi. Le operazioni bit a bit diventano più complesse con i numeri negativi (coinvolgono la rappresentazione in complemento a due), che è oltre lo scopo di questa introduzione. Per ora, concentrati sul capire che questi operatori esistono e che cosa fanno a livello generale.

4.10) Precisione dei numeri in virgola mobile ed errori di arrotondamento (spiegazione semplice)

I numeri in virgola mobile (floating-point) in Python (e nella maggior parte dei linguaggi di programmazione) possono rappresentare un vasto intervallo di valori, da frazioni minuscole a numeri enormi. Tuttavia, hanno una limitazione che può sorprendere i principianti: non possono rappresentare esattamente tutti i numeri decimali. Questa sezione spiega perché accade e cosa significa per i tuoi programmi.

4.10.1) Perché i numeri in virgola mobile non sono sempre esatti

I computer memorizzano i numeri in virgola mobile in binario (base 2), non in decimale (base 10). Alcune frazioni decimali che a noi sembrano semplici non possono essere rappresentate esattamente in binario, proprio come 1/3 non può essere rappresentato esattamente in decimale (0,333333... continua all’infinito).

Ecco un esempio sorprendente:

python
# floating_point_surprise.py
# This seems like it should be exactly 0.3
result = 0.1 + 0.2
print(result)  # Output: 0.30000000000000004 (not exactly 0.3!)
 
# Check if it equals 0.3
print(result == 0.3)  # Output: False

Questo non è un bug di Python: è una limitazione fondamentale di come i computer rappresentano i numeri in virgola mobile. Il numero decimale 0.1 non può essere rappresentato esattamente nel formato floating-point binario, proprio come 1/3 non può essere rappresentato esattamente in decimale.

4.10.2) Comprendere il problema di rappresentazione

Vediamo altri esempi di questo comportamento:

python
# more_precision_examples.py
# Simple decimal values that aren't exact in binary
print(0.1)  # Output: 0.1 (Python rounds for display)
print(repr(0.1))  # Output: 0.1 (still rounded)
 
# But the actual stored value has tiny errors
print(0.1 + 0.1 + 0.1)  # Output: 0.30000000000000004
 
# Multiplication can accumulate these errors
result = 0.1 * 3
print(result)  # Output: 0.30000000000000004
 
# Some numbers are exact (powers of 2)
print(0.5)  # Output: 0.5 (exact)
print(0.25)  # Output: 0.25 (exact)
print(0.125)  # Output: 0.125 (exact)

I numeri che sono potenze di 2 (come 0.5, 0.25, 0.125) o somme di potenze di 2 possono essere rappresentati esattamente. Ma la maggior parte delle frazioni decimali no.

4.10.3) Implicazioni pratiche

Per la maggior parte della programmazione quotidiana, questi piccoli errori non contano. Ma ci sono situazioni in cui devi esserne consapevole:

Comparare numeri in virgola mobile:

python
# comparing_floats.py
# Direct equality comparison can fail
a = 0.1 + 0.2
b = 0.3
 
print(a == b)  # Output: False (due to tiny difference)
 
# Better: check if they're close enough
difference = abs(a - b)
tolerance = 0.0001  # How close is "close enough"?
 
print(difference < tolerance)  # Output: True (they're close enough)
 
# Python 3.5+ provides math.isclose() for this (we'll see in section 4.11)

Accumulo di errori nei calcoli ripetuti:

python
# accumulated_errors.py
# Adding 0.1 ten times
total = 0.0
for i in range(10):
    total += 0.1
 
print(total)  # Output: 0.9999999999999999 (not exactly 1.0)
print(total == 1.0)  # Output: False
 
# The error accumulates with each addition

Calcoli finanziari:

python
# financial_calculations.py
# Money calculations can have surprising results
price = 0.10
quantity = 3
total = price * quantity
 
print(total)  # Output: 0.30000000000000004
 
# For financial calculations, consider rounding to cents
total_cents = round(total * 100)  # Convert to cents
total_dollars = total_cents / 100
print(total_dollars)  # Output: 0.3
 
# Or use Python's decimal module for exact decimal arithmetic
# (beyond the scope of this chapter, but worth knowing about)

4.10.4) Strategie per lavorare con i numeri in virgola mobile

Arrotonda i risultati per la visualizzazione:

python
# rounding_for_display.py
result = 0.1 + 0.2
print(f"Result: {round(result, 2)}")  # Output: Result: 0.3
 
# Or use formatted output (we'll learn more in Chapter 6)
print(f"Result: {result:.2f}")  # Output: Result: 0.30

Non confrontare i float per uguaglianza esatta:

python
# safe_float_comparison.py
a = 0.1 + 0.2
b = 0.3
 
# Instead of: if a == b:
# Use: if they're close enough
if abs(a - b) < 0.0001:
    print("Close enough to equal")
# Output: Close enough to equal

Sii consapevole quando la precisione è importante:

Per il calcolo scientifico, i calcoli finanziari o qualsiasi ambito in cui l’aritmetica decimale esatta è cruciale, potresti avere bisogno di:

  • Modulo decimal di Python per l’aritmetica decimale esatta
  • Modulo fractions di Python per l’aritmetica razionale esatta
  • Arrotondamento accurato nei punti appropriati dei calcoli

Questi moduli sono oltre lo scopo di questo capitolo, ma è utile sapere che esistono.

4.10.5) Quando la precisione dei float non importa

Per la maggior parte dei compiti di programmazione quotidiana, la precisione dei numeri in virgola mobile è più che sufficiente:

python
# when_precision_is_fine.py
# Calculating area
length = 5.5
width = 3.2
area = length * width
print(f"Area: {area:.2f} square meters")  # Output: Area: 17.60 square meters
 
# Converting temperature
fahrenheit = 98.6
celsius = (fahrenheit - 32) * 5 / 9
print(f"Temperature: {celsius:.1f}°C")  # Output: Temperature: 37.0°C
 
# Calculating average
total = 456.78
count = 7
average = total / count
print(f"Average: {average:.2f}")  # Output: Average: 65.25

Quando arrotondi i risultati per la visualizzazione o quando i piccoli errori sono insignificanti rispetto alla precisione delle misurazioni, l’aritmetica in virgola mobile funziona perfettamente.

4.10.6) Il punto chiave

I numeri in virgola mobile sono approssimazioni dei numeri reali. Per la maggior parte dei compiti di programmazione, sono abbastanza accurati. Ma ricorda:

  1. Non confrontare i float per uguaglianza esatta: controlla invece se sono “abbastanza vicini”
  2. Arrotonda i risultati in fase di visualizzazione per evitare di mostrare una precisione priva di significato
  3. Sii consapevole dell’accumulo di errori nei calcoli ripetuti
  4. Per i calcoli finanziari, considera l’arrotondamento alla precisione appropriata o l’uso del modulo decimal

Capire queste limitazioni ti aiuta a scrivere programmi più robusti e a evitare bug sorprendenti. La buona notizia è che, per la stragrande maggioranza dei compiti di programmazione, l’aritmetica in virgola mobile “semplicemente funziona” se segui queste semplici linee guida.

4.11) (Opzionale) Funzioni matematiche di base con il modulo math (sqrt, floor, ceil, pi)

Gli operatori e le funzioni built-in di Python coprono l’aritmetica di base, ma per operazioni matematiche più avanzate Python fornisce il modulo math. Un modulo è una raccolta di funzioni e valori correlati che puoi usare nei tuoi programmi. Impareremo molto di più sui moduli nel Capitolo 22, ma per ora introdurremo le basi necessarie per usare il modulo math.

4.11.1) Importare il modulo math

Per usare il modulo math, devi importarlo all’inizio del tuo programma:

python
# importing_math.py
import math
 
# Now you can use functions from the math module
# by writing math.function_name()

Quando importi un modulo, ottieni accesso a tutte le sue funzioni e valori. Li usi scrivendo il nome del modulo, un punto e poi il nome della funzione o del valore.

4.11.2) Costanti matematiche: pi ed e

Il modulo math fornisce valori precisi per importanti costanti matematiche:

python
# math_constants.py
import math
 
# Pi (π): ratio of circle's circumference to diameter
print(math.pi)  # Output: 3.141592653589793
 
# Euler's number (e): base of natural logarithms
print(math.e)  # Output: 2.718281828459045
 
# Using pi to calculate circle properties
radius = 5
circumference = 2 * math.pi * radius
area = math.pi * radius ** 2
 
print(f"Circumference: {circumference:.2f}")  # Output: Circumference: 31.42
print(f"Area: {area:.2f}")  # Output: Area: 78.54

4.11.3) Radice quadrata con sqrt()

La funzione sqrt() calcola la radice quadrata di un numero:

python
# square_root.py
import math
 
# Square root of perfect squares
result = math.sqrt(16)
print(result)  # Output: 4.0
 
result = math.sqrt(25)
print(result)  # Output: 5.0
 
# Square root of non-perfect squares
result = math.sqrt(2)
print(result)  # Output: 1.4142135623730951
 
# Using sqrt in calculations
# Distance formula: sqrt((x2-x1)^2 + (y2-y1)^2)
x1, y1 = 3, 4
x2, y2 = 6, 8
 
distance = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
print(f"Distance: {distance}")  # Output: Distance: 5.0

Nota che sqrt() restituisce un float, anche per i quadrati perfetti. Inoltre, non puoi calcolare la radice quadrata di numeri negativi con sqrt() (genererà un errore).

4.11.4) Floor e ceil con floor() e ceil()

La funzione floor() arrotonda per difetto al più vicino intero (verso meno infinito), e ceil() arrotonda per eccesso (verso più infinito):

python
# floor_and_ceil.py
import math
 
# Floor: rounds down
result = math.floor(3.7)
print(result)  # Output: 3
 
result = math.floor(3.2)
print(result)  # Output: 3
 
# Ceiling: rounds up
result = math.ceil(3.7)
print(result)  # Output: 4
 
result = math.ceil(3.2)
print(result)  # Output: 4
 
# With negative numbers
result = math.floor(-3.7)
print(result)  # Output: -4 (rounds toward negative infinity)
 
result = math.ceil(-3.7)
print(result)  # Output: -3 (rounds toward positive infinity)

Queste funzioni sono utili quando devi assicurarti che un valore rientri in un certo intervallo o quando ti servono risultati interi con un comportamento di arrotondamento specifico:

python
# practical_floor_ceil.py
import math
 
# How many boxes needed to pack all items?
items = 47
items_per_box = 12
 
# Use ceil to round up (need a full box even if not completely filled)
boxes_needed = math.ceil(items / items_per_box)
print(f"Boxes needed: {boxes_needed}")  # Output: Boxes needed: 4
 
# How many complete pages for a document?
total_lines = 250
lines_per_page = 30
 
# Use floor to count only complete pages
complete_pages = math.floor(total_lines / lines_per_page)
print(f"Complete pages: {complete_pages}")  # Output: Complete pages: 8

4.11.5) Confrontare floor(), ceil(), round() e int()

È utile capire come differiscono questi diversi approcci all’arrotondamento:

python
# comparing_rounding_methods.py
import math
 
value = 3.7
 
print(f"Original: {value}")
print(f"floor(): {math.floor(value)}")  # Output: floor(): 3
print(f"ceil(): {math.ceil(value)}")    # Output: ceil(): 4
print(f"round(): {round(value)}")       # Output: round(): 4
print(f"int(): {int(value)}")           # Output: int(): 3
 
# With negative numbers, the differences are more apparent
value = -3.7
 
print(f"\nOriginal: {value}")
print(f"floor(): {math.floor(value)}")  # Output: floor(): -4
print(f"ceil(): {math.ceil(value)}")    # Output: ceil(): -3
print(f"round(): {round(value)}")       # Output: round(): -4
print(f"int(): {int(value)}")           # Output: int(): -3

Ecco una rappresentazione visiva di come si comportano queste funzioni:

Value: 3.7

floor: 3
rounds down

ceil: 4
rounds up

round: 4
rounds to nearest

int: 3
truncates toward zero

Value: -3.7

floor: -4
rounds down toward -∞

ceil: -3
rounds up toward +∞

round: -4
rounds to nearest

int: -3
truncates toward zero

4.11.6) Altre funzioni utili del modulo math

Il modulo math contiene molte altre funzioni. Eccone alcune frequentemente utili:

python
# other_math_functions.py
import math
 
# Absolute value (also available as built-in abs())
result = math.fabs(-5.5)
print(result)  # Output: 5.5
 
# Power function (also available as ** operator)
result = math.pow(2, 3)
print(result)  # Output: 8.0
 
# Trigonometric functions (angles in radians)
result = math.sin(math.pi / 2)  # sin(90 degrees)
print(result)  # Output: 1.0
 
result = math.cos(0)  # cos(0 degrees)
print(result)  # Output: 1.0
 
# Logarithms
result = math.log(math.e)  # Natural log (base e)
print(result)  # Output: 1.0
 
result = math.log10(100)  # Log base 10
print(result)  # Output: 2.0
 
# Check if a float is close to another (Python 3.5+)
a = 0.1 + 0.2
b = 0.3
result = math.isclose(a, b)
print(result)  # Output: True

La funzione isclose() è particolarmente utile per confrontare numeri in virgola mobile (come discusso nella sezione 4.10):

python
# isclose_example.py
import math
 
# Instead of direct equality comparison
a = 0.1 + 0.2
b = 0.3
 
# Don't do this:
# if a == b:  # This would be False
 
# Do this instead:
if math.isclose(a, b):
    print("Values are close enough")
# Output: Values are close enough
 
# You can specify the tolerance
if math.isclose(a, b, rel_tol=1e-9):  # Very strict tolerance
    print("Values are very close")
# Output: Values are very close

4.11.7) Quando usare il modulo math

Usa il modulo math quando ti servono:

  • Costanti matematiche (π, e)
  • Radici quadrate e altre radici
  • Funzioni trigonometriche (sin, cos, tan)
  • Funzioni logaritmiche ed esponenziali
  • Controllo preciso dell’arrotondamento (floor, ceil)
  • Confronto di numeri in virgola mobile (isclose)

Per l’aritmetica di base (+, -, *, /, //, %, **), usa gli operatori built-in di Python. Per operazioni matematiche più avanzate, importa e usa il modulo math.

Il modulo math fa parte della libreria standard di Python, il che significa che è sempre disponibile: devi solo importarlo. Esploreremo altri moduli e impareremo il sistema di importazione in dettaglio nel Capitolo 22.


In questo capitolo hai imparato come lavorare con i numeri in Python: eseguire operazioni aritmetiche, comprendere i diversi tipi di divisione, usare la precedenza degli operatori, mescolare interi e float, usare le funzioni numeriche built-in, riconoscere pattern numerici comuni, lavorare con operatori bit a bit, comprendere la precisione dei float e usare il modulo math per operazioni avanzate.

Queste operazioni numeriche costituiscono le fondamenta per innumerevoli compiti di programmazione, dai calcoli semplici all’analisi complessa dei dati. Man mano che continui a imparare Python, userai costantemente queste operazioni, spesso in combinazione con le strutture di controllo del flusso e le collezioni di dati che imparerai nei capitoli successivi.

Esercitati con queste operazioni finché non diventano una seconda natura. Prova a scrivere piccoli programmi che calcolano quantità del mondo reale: convertire temperature, calcolare aree e volumi, elaborare dati finanziari o analizzare misurazioni. Più ti eserciti, più ti sentirai a tuo agio con le capacità numeriche di Python.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai