4. Trabajar con números
En el Capítulo 3 aprendiste cómo crear variables y trabajar con los tipos numéricos básicos de Python: enteros y números de coma flotante (floats). Ahora es momento de poner esos números a trabajar. En este capítulo aprenderás a realizar cálculos, combinar operaciones y usar las herramientas integradas de Python para trabajar con datos numéricos.
Los números son fundamentales en la programación. Ya sea que estés calculando totales, procesando medidas, gestionando inventarios o analizando datos, necesitarás realizar operaciones aritméticas. Python hace que las operaciones numéricas sean directas e intuitivas, pero hay detalles importantes que comprender, especialmente sobre cómo funcionan los distintos tipos de división, cómo interactúan los operadores y cómo se comportan los números de coma flotante.
Al final de este capítulo, te sentirás cómodo realizando cálculos, entendiendo el comportamiento de los operadores y usando las funciones numéricas de Python para resolver problemas del mundo real.
4.1) Aritmética básica: suma, resta y multiplicación
Empecemos con las operaciones aritméticas más fundamentales. Python usa símbolos familiares para las matemáticas básicas, y estas operaciones funcionan tal como esperarías de la aritmética cotidiana.
4.1.1) Suma con el operador +
El operador + suma dos números. Funciona tanto con enteros como con números de coma flotante:
# 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: 100La suma es sencilla, pero hay un detalle importante: cuando sumas un entero y un float, Python convierte automáticamente el resultado a float para preservar la precisión decimal:
# mixed_addition.py
result = 10 + 3.5
print(result) # Output: 13.5
print(type(result)) # Output: <class 'float'>Esta conversión automática ocurre porque un float puede representar tanto números enteros como decimales, mientras que un entero no puede representar decimales. Python elige el tipo que no perderá información.
4.1.2) Resta con el operador -
El operador - resta el segundo número del primero:
# 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: -15Al igual que la suma, la resta también promociona enteros a floats cuando mezclas tipos:
# mixed_subtraction.py
result = 100 - 0.01
print(result) # Output: 99.99
print(type(result)) # Output: <class 'float'>4.1.3) Multiplicación con el operador *
El operador * multiplica dos números:
# 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: 0La multiplicación sigue las mismas reglas de conversión de tipos. Cuando multiplicas un entero por un float, el resultado es un float:
# 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'>Observa en el último ejemplo que, aunque 4 × 2.0 es igual a 8, Python lo representa como 8.0 porque uno de los operandos era un float. El tipo del resultado depende de los tipos de las entradas, no de si el resultado matemático es un número entero.
4.1.4) Ejemplos prácticos con aritmética básica
Veamos cómo estas operaciones funcionan juntas en escenarios realistas:
# 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# 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 degreesEstas operaciones básicas forman la base de cálculos más complejos. Entender cómo funcionan, especialmente cómo maneja Python las conversiones de tipos, te ayudará a evitar sorpresas cuando tus programas realicen cálculos.
4.2) División, división entera y resto: /, // y %
La división en Python es más matizada que la suma, la resta o la multiplicación. Python proporciona tres operadores relacionados con la división, cada uno con un propósito distinto. Entender cuándo usar cada uno es crucial para escribir programas correctos.
4.2.1) División normal con /
El operador / realiza "división verdadera" (true division): siempre devuelve un resultado de coma flotante, incluso cuando divides dos enteros:
# 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.2El punto clave aquí es que / siempre produce un float, incluso cuando ambos operandos son enteros y el resultado es matemáticamente un número entero. Esto es intencional: Python quiere que la división se comporte de forma consistente y preserve la precisión decimal.
Este comportamiento es distinto al de otros lenguajes de programación donde dividir dos enteros produce un entero. En Python 3, si quieres un resultado entero, debes usar la división entera (que veremos a continuación) o convertir el resultado explícitamente.
4.2.2) División entera (floor division) con //
El operador // realiza "división entera" (floor division): divide y luego redondea hacia abajo al entero más cercano. Cuando ambos operandos son enteros, el resultado es un entero. Cuando alguno de los operandos es un float, el resultado es un float (pero igualmente redondeado hacia abajo):
# 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'>"Redondear hacia abajo" significa moverse hacia el infinito negativo, no solo quitar la parte decimal. Esto importa para números negativos:
# 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 división entera es útil cuando necesitas dividir elementos en grupos o calcular cuántas unidades completas caben en una cantidad:
# 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: 24.2.3) Resto (módulo) con %
El operador % (llamado "módulo" o "mod") devuelve el resto después de la división. Responde a la pregunta: "Después de dividir, ¿qué sobra?"
# 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.5El operador módulo es increíblemente útil para varios patrones comunes de programación:
Comprobar si un número es par o impar:
# even_odd_check.py
number = 17
if number % 2 == 0:
print(f"{number} is even")
else:
print(f"{number} is odd")
# Output: 17 is oddObtener los elementos que sobran:
# leftover_items.py
eggs = 50
eggs_per_dozen = 12
leftover = eggs % eggs_per_dozen
print(f"Leftover eggs: {leftover}") # Output: Leftover eggs: 2Hacer que valores "den la vuelta" dentro de un rango (como en un reloj):
# 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: 114.2.4) Relación entre //, % y /
La división entera y el módulo están estrechamente relacionados. Juntos te dan el resultado completo de la división:
# 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 = 17Esta relación siempre se cumple: para cualesquiera números a y b (donde b no es cero), a == (a // b) * b + (a % b).
4.2.5) Elegir el operador de división adecuado
Aquí tienes una guía rápida para elegir qué operador usar:
- Usa
/cuando necesites el resultado matemático exacto con decimales (lo más común en cálculos) - Usa
//cuando necesites contar cuántos grupos completos caben (como docenas, horas, páginas) - Usa
%cuando necesites el resto o la cantidad que sobra (como comprobar par/impar, hacer que valores den la vuelta, encontrar sobrantes)
# 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.8333333333333334.3) Operadores de asignación aumentada
Al programar, con frecuencia necesitarás actualizar una variable realizando una operación sobre su valor actual. Por ejemplo, sumar a un total acumulado, restar de un saldo o multiplicar un valor por un factor. Python proporciona una forma concisa de hacer esto con los operadores de asignación aumentada.
4.3.1) Qué son los operadores de asignación aumentada
Un operador de asignación aumentada combina una operación aritmética con la asignación. En lugar de escribir:
# traditional_update.py
count = 10
count = count + 5 # Add 5 to count
print(count) # Output: 15Puedes escribir:
# augmented_update.py
count = 10
count += 5 # Same as: count = count + 5
print(count) # Output: 15Ambas versiones hacen exactamente lo mismo, pero la versión aumentada es más concisa y expresa con mayor claridad la intención: "incrementa count en 5".
4.3.2) Todos los operadores de asignación aumentada
Python proporciona operadores de asignación aumentada para todas las operaciones aritméticas:
# 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: 84.3.3) Casos de uso comunes de la asignación aumentada
Los operadores de asignación aumentada brillan en varios patrones comunes de programación:
Acumular un total:
# 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: 50Contar ocurrencias:
# 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: 3Actualizar un saldo:
# 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.5Aplicar operaciones repetidas:
# 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.100000000000024.3.4) Detalles importantes sobre la asignación aumentada
La asignación aumentada crea un nuevo objeto para tipos inmutables:
Recuerda del Capítulo 3 que los números son inmutables: no puedes cambiar el valor de un número, solo crear nuevos números. Cuando escribes x += 5, Python crea un nuevo objeto número y lo asigna a x. El objeto número antiguo se descarta (exploraremos este concepto más a fondo en el Capítulo 17 cuando hablemos del modelo de objetos de Python):
# augmented_with_immutables.py
x = 10
print(id(x)) # Output: (some memory address)
x += 5
print(id(x)) # Output: (different memory address)Por ahora, solo entiende que x += 5 es equivalente a x = x + 5: es una abreviatura conveniente, no una operación fundamentalmente diferente.
No puedes usar asignación aumentada antes de que exista una variable:
# 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: 1Las conversiones de tipo ocurren de la misma manera:
# augmented_type_conversion.py
x = 10 # integer
x += 2.5 # Add a float
print(x) # Output: 12.5
print(type(x)) # Output: <class 'float'>El resultado sigue las mismas reglas de conversión de tipos que los operadores normales: si mezclas enteros y floats, el resultado es un float.
4.3.5) Cuándo usar asignación aumentada
Usa operadores de asignación aumentada siempre que estés actualizando una variable en función de su valor actual. Hacen que tu código sea:
- Más conciso:
x += 5es más corto quex = x + 5 - Más legible: La intención es inmediatamente clara
- Menos propenso a errores: No corres el riesgo de escribir mal el nombre de la variable
Compara estas dos versiones:
# 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 += 25La versión con asignación aumentada es más clara y tiene menos posibilidades de errores tipográficos. Los operadores de asignación aumentada son una característica pequeña, pero se usan constantemente en código Python real.
4.4) Precedencia de operadores y paréntesis
Cuando combinas múltiples operaciones en una sola expresión, Python necesita reglas para determinar el orden de evaluación. ¿Se debe calcular 2 + 3 * 4 como (2 + 3) * 4 = 20 o como 2 + (3 * 4) = 14? La respuesta depende de la precedencia de operadores: las reglas que determinan qué operaciones ocurren primero.
4.4.1) Entender la precedencia de operadores
Python sigue las mismas reglas de precedencia que aprendiste en matemáticas: la multiplicación y la división ocurren antes que la suma y la resta. A menudo se recuerda con el acrónimo PEMDAS (Paréntesis, Exponentes, Multiplicación/División, Adición/Sustracción), aunque veremos exponentes en la sección 4.6.
# 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.0Aquí está el orden de precedencia para los operadores que hemos aprendido hasta ahora (de mayor a menor):
- Paréntesis
()— máxima precedencia, siempre se evalúan primero. - Exponenciación
**— (la veremos en la sección 4.6). - Multiplicación, división, división entera, módulo
*,/,//,%— mismo nivel, se evalúan de izquierda a derecha. - Suma, resta
+,-— mismo nivel, se evalúan de izquierda a derecha.
Veamos más ejemplos de cómo funciona la precedencia:
# 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) Usar paréntesis para controlar el orden
Los paréntesis anulan la precedencia por defecto. Las operaciones dentro de paréntesis siempre se realizan primero, sin importar los operadores involucrados:
# 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.0Puedes anidar paréntesis para crear expresiones más complejas. Python evalúa desde los paréntesis más internos hacia afuera:
# 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 = 124.4.3) Cuando los operadores tienen la misma precedencia
Cuando aparecen múltiples operadores del mismo nivel de precedencia en una expresión, Python los evalúa de izquierda a derecha (esto se llama "asociatividad por la izquierda"):
# 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.54.4.4) Ejemplos prácticos con precedencia
Veamos escenarios realistas donde entender la precedencia importa:
# 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)# 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# 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.44.4.5) Buenas prácticas para la precedencia de operadores
Usa paréntesis por claridad, incluso cuando no sean estrictamente necesarios:
A veces añadir paréntesis hace tu intención más clara, incluso si no cambian el resultado:
# 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 intentDivide expresiones complejas en pasos:
Cuando una expresión se vuelve demasiado compleja, a menudo es mejor dividirla en múltiples líneas:
# 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 = finalLa versión en varios pasos es más fácil de leer, depurar y verificar. No sacrifiques claridad por concisión.
Cuando dudes, usa paréntesis:
Si no estás seguro sobre la precedencia, añade paréntesis. Al intérprete de Python no le molestará, y tu yo futuro (u otros programadores que lean tu código) te lo agradecerán.
Entender la precedencia de operadores te ayuda a escribir expresiones correctas y a leer el código de otras personas. El principio clave: cuando combines operaciones, piensa en el orden de evaluación y usa paréntesis para hacer explícita tu intención.
4.5) Mezclar enteros y floats en expresiones
Ya has visto que Python maneja automáticamente la mezcla de enteros y floats en operaciones simples. Ahora exploremos este comportamiento de forma más sistemática y entendamos las reglas que gobiernan la conversión de tipos en expresiones numéricas.
4.5.1) La regla de promoción de tipos
Cuando Python realiza una operación aritmética que involucra un entero y un float, convierte automáticamente (o "promociona") el entero a float antes de realizar la operación. El resultado es siempre un float:
# 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'>¿Por qué hace esto Python? Porque los floats pueden representar tanto números enteros como decimales, mientras que los enteros no pueden representar decimales. Convertir a float preserva toda la información y evita perder precisión.
Aquí hay una representación visual de cómo decide Python el tipo del resultado:
4.5.2) Promoción de tipos en expresiones complejas
Cuando una expresión contiene múltiples operaciones con tipos mezclados, Python aplica la regla de promoción en cada paso:
# 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 vez que cualquier operación en una expresión produce un float, todas las operaciones posteriores que involucren ese resultado también producirán floats.
4.5.3) Caso especial: la división normal siempre produce floats
Recuerda de la sección 4.2 que el operador / siempre produce un float, incluso cuando divide dos enteros:
# 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'>Si quieres un resultado entero de una división, usa // en su lugar (pero recuerda que redondea hacia abajo):
# 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) Implicaciones prácticas de mezclar tipos
Entender la promoción de tipos te ayuda a predecir y controlar los tipos de tus resultados:
# 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): 904.5.5) Cuando los resultados enteros importan
A veces necesitas específicamente resultados enteros para contar, indexar u otras operaciones discretas. Aquí se muestra cómo asegurarte de obtener enteros:
# 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): 3Observa la diferencia: // redondea hacia abajo (hacia el infinito negativo), mientras que int() trunca hacia cero. Para números positivos son lo mismo, pero para números negativos difieren:
# 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) Evitar resultados float inesperados
A veces podrías sorprenderte al obtener un float cuando esperabas un entero. Esto suele ocurrir por la división o la mezcla de tipos:
# 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)La idea clave: las reglas de promoción de tipos de Python están diseñadas para preservar la precisión y evitar pérdida de datos. Cuando mezcles enteros y floats, o cuando uses división normal, espera resultados float. Si necesitas enteros, usa división entera o conversión explícita, pero sé consciente de cómo manejan el redondeo.
4.6) Funciones numéricas integradas útiles: abs(), min(), max() y pow()
Python proporciona varias funciones integradas que realizan operaciones numéricas comunes. Estas funciones funcionan con enteros y floats, y son herramientas esenciales para la programación cotidiana. Exploremos las que se usan con más frecuencia.
4.6.1) Valor absoluto con abs()
La función abs() devuelve el valor absoluto de un número: su distancia a cero, ignorando el signo:
# 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: 0La función de valor absoluto es útil siempre que te importe la magnitud pero no la dirección:
# 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: 154.6.2) Encontrar mínimo y máximo con min() y max()
La función min() devuelve el menor de sus argumentos, y max() devuelve el mayor:
# 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.9Estas funciones son invaluables para encontrar extremos en datos:
# 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: 100El patrón de "clamp" (recorte) usando max() y min() juntos es común cuando necesitas restringir un valor a un rango específico:
# 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) Exponenciación con pow()
La función pow() eleva un número a una potencia. Python también tiene el operador ** para exponenciación, que es el más usado, pero pow() ofrece algunas características adicionales:
# 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.0La función pow() puede tomar un tercer argumento opcional para exponenciación modular (útil en criptografía y teoría de números, pero más allá del alcance de la aritmética básica):
# 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 numbersPara la mayoría de usos cotidianos, el operador ** es más conveniente que pow():
# 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: 274.6.4) Combinar funciones integradas
Estas funciones funcionan bien juntas para resolver problemas comunes:
# 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.0Estas funciones integradas son herramientas fundamentales en la programación en Python. Son eficientes, bien probadas y funcionan de forma consistente con distintos tipos numéricos. Úsalas siempre que necesites estas operaciones comunes: no hay necesidad de escribir tus propias implementaciones.
4.7) Redondear números con round()
Al trabajar con números de coma flotante, a menudo necesitas redondear resultados a un número específico de decimales para mostrarlos, para cálculos o para almacenarlos. La función round() de Python proporciona esta capacidad.
4.7.1) Redondeo básico con round()
La función round() toma un número y lo redondea al entero más cercano:
# 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: -3Observa el comportamiento al redondear números exactamente a mitad de camino entre dos enteros (como 2.5 o 3.5). Python usa "redondeo al par" (round half to even, también llamado "banker's rounding"), que redondea al número par más cercano. Esto reduce el sesgo en operaciones de redondeo repetidas. Para la mayoría de la programación cotidiana, este detalle rara vez importa, pero es bueno tenerlo presente.
4.7.2) Redondear a un número específico de decimales
La función round() acepta un segundo argumento opcional que especifica cuántos decimales conservar:
# 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)Cuando especificas decimales, round() devuelve un float (incluso si redondeas a 0 decimales). Cuando omites el segundo argumento, devuelve un entero.
4.7.3) Usos prácticos del redondeo
El redondeo es esencial para mostrar dinero, medidas y otros valores donde la precisión excesiva es innecesaria o confusa:
# 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 meters4.7.4) Redondear vs truncar vs floor/ceil
Es importante entender que redondear es diferente de truncar (eliminar decimales) o de las operaciones floor/ceil:
# 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)Para números negativos, las diferencias son más marcadas:
# 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): -34.7.5) Consideraciones importantes sobre el redondeo
La precisión de coma flotante puede causar sorpresas:
Debido a cómo los ordenadores representan los números de coma flotante (lo que discutiremos en la sección 4.10), el redondeo no siempre produce exactamente el resultado que podrías esperar:
# 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.675Para la mayoría de fines prácticos, esto no es un problema. Pero si trabajas con cálculos financieros donde la aritmética decimal exacta importa, podrías necesitar el módulo decimal de Python (que no cubriremos en este libro, pero vale la pena saber que existe).
Redondear para mostrar vs redondear para calcular:
A menudo querrás redondear solo para mostrar, manteniendo la precisión completa para los cálculos:
# 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.3184La función round() es una de las funciones integradas que más se usan en Python. Úsala siempre que necesites presentar resultados numéricos en un formato legible o cuando necesites limitar la precisión para un propósito específico.
4.8) Patrones numéricos comunes (contadores, totales, promedios)
Ahora que entiendes las operaciones numéricas y funciones de Python, exploremos patrones comunes que usarás repetidamente en programas reales. Estos patrones forman los bloques de construcción para procesar datos numéricos.
4.8.1) Contadores: llevar la cuenta de cuántos
Un contador es una variable que lleva la cuenta de cuántas veces ocurre algo. Lo inicializas en cero y lo incrementas cada vez que sucede un evento:
# 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)En programas reales, típicamente usarías contadores dentro de bucles (loops) (que aprenderemos en los Capítulos 10 y 11). Por ahora, entiende el patrón: empezar en cero, sumar uno cada vez.
Los contadores pueden seguir distintos tipos de eventos:
# 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: 14.8.2) Acumuladores: construir totales
Un acumulador es una variable que recoge un total acumulado. Como un contador, lo inicializas en cero, pero en lugar de sumar uno cada vez, sumas cantidades variables:
# 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.49Los acumuladores son fundamentales para el procesamiento de datos. Te permiten agregar valores a medida que los procesas:
# 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: 34.8.3) Calcular promedios
Un promedio combina los patrones de contador y acumulador: necesitas tanto un total (acumulador) como un conteo (contador), y luego dividir:
# 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.75Importante: siempre comprueba la división por cero:
Al calcular promedios, debes asegurarte de que el conteo no sea cero:
# 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 averageAprenderemos más sobre cómo manejar condiciones como esta en el Capítulo 8.
4.8.4) Encontrar máximo y mínimo en curso
A veces necesitas rastrear el valor más grande o más pequeño visto hasta el momento: puedes usar las funciones max() y min() para implementar esto:
# 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: 684.9) Operadores bit a bit (bitwise) para enteros: &, |, ^, <<, >> (visión general breve)
Los operadores bit a bit (bitwise) trabajan sobre los bits individuales (dígitos binarios) de los enteros. Aunque no usarás estos operadores tan a menudo como los operadores aritméticos en la programación diaria, son importantes para ciertas tareas como trabajar con banderas (flags), permisos, manipulación de datos de bajo nivel y optimización de rendimiento.
Esta sección proporciona una visión general breve. Entender las operaciones bit a bit requiere entender la representación binaria, que es un tema más profundo de lo que podemos explorar completamente en este capítulo introductorio.
4.9.1) Entender la representación binaria (introducción rápida)
Los ordenadores almacenan enteros como secuencias de bits (0 y 1). Por ejemplo:
- El 5 decimal es
101en binario (1×4 + 0×2 + 1×1). - El 12 decimal es
1100en binario (1×8 + 1×4 + 0×2 + 0×1).
La función bin() de Python muestra la representación binaria de un entero:
# 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 notation4.9.2) AND bit a bit (&)
El operador & realiza un AND bit a bit: cada bit en el resultado es 1 solo si los bits correspondientes en ambos operandos son 1:
# 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)El AND bit a bit se usa a menudo para comprobar si determinados bits están activados (probar banderas):
# 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: False4.9.3) OR bit a bit (|)
El operador | realiza un OR bit a bit: cada bit en el resultado es 1 si el bit correspondiente en alguno de los operandos es 1:
# 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)El OR bit a bit se usa para combinar banderas:
# 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: 0b1114.9.4) XOR bit a bit (^)
El operador ^ realiza un XOR (exclusive OR) bit a bit: cada bit en el resultado es 1 si los bits correspondientes en los operandos son diferentes:
# 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)XOR tiene propiedades interesantes, como activar/desactivar bits o intercambiar valores:
# 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: 424.9.5) Desplazamiento a la izquierda (<<) y a la derecha (>>)
El operador << desplaza bits a la izquierda, y >> desplaza bits a la derecha:
# 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)Desplazar a la izquierda n bits multiplica por 2^n, y desplazar a la derecha divide por 2^n (división entera):
# 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) Cuándo usar operadores bit a bit
Los operadores bit a bit son útiles en escenarios específicos:
- Trabajar con banderas binarias y permisos (como los permisos de archivos en Unix/Linux).
- Manipulación de datos de bajo nivel (empaquetar múltiples valores en un solo entero).
- Programación de red (manipular direcciones IP, banderas de protocolo).
- Gráficos y programación de juegos (manipulación de colores, detección de colisiones).
- Optimización de rendimiento (el desplazamiento de bits es más rápido que multiplicar/dividir por potencias de 2).
Para la mayoría de las tareas de programación cotidianas, no necesitarás operadores bit a bit. Pero cuando los necesites, especialmente al trabajar con programación de sistemas, redes o código crítico en rendimiento, son herramientas invaluables.
Nota: Esta visión general cubre lo básico. Las operaciones bit a bit se vuelven más complejas con números negativos (involucran la representación en complemento a dos), lo cual está más allá del alcance de esta introducción. Por ahora, céntrate en entender que estos operadores existen y qué hacen a un nivel general.
4.10) Precisión de coma flotante y errores de redondeo (explicación sencilla)
Los números de coma flotante en Python (y en la mayoría de lenguajes de programación) pueden representar una amplia gama de valores, desde fracciones diminutas hasta números enormes. Sin embargo, tienen una limitación que puede sorprender a los principiantes: no pueden representar todos los números decimales exactamente. Esta sección explica por qué ocurre esto y qué significa para tus programas.
4.10.1) Por qué los números de coma flotante no siempre son exactos
Los ordenadores almacenan números de coma flotante en binario (base 2), no en decimal (base 10). Algunas fracciones decimales que nos parecen simples no pueden representarse exactamente en binario, del mismo modo que 1/3 no puede representarse exactamente en decimal (0.333333... continúa para siempre).
Aquí hay un ejemplo sorprendente:
# 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: FalseEsto no es un error en Python: es una limitación fundamental de cómo los ordenadores representan los números de coma flotante. El número decimal 0.1 no puede representarse exactamente en coma flotante binaria, de la misma forma que 1/3 no puede representarse exactamente en decimal.
4.10.2) Entender el problema de representación
Veamos más ejemplos de este comportamiento:
# 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)Los números que son potencias de 2 (como 0.5, 0.25, 0.125) o sumas de potencias de 2 pueden representarse exactamente. Pero la mayoría de las fracciones decimales no.
4.10.3) Implicaciones prácticas
Para la mayoría de la programación cotidiana, estos pequeños errores no importan. Pero hay situaciones en las que necesitas ser consciente de ellos:
Comparar números de coma flotante:
# 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)Acumular errores en cálculos repetidos:
# 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 additionCálculos financieros:
# 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) Estrategias para trabajar con números de coma flotante
Redondear resultados para mostrar:
# 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.30No comparar floats por igualdad exacta:
# 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 equalSé consciente cuando la precisión importa:
Para computación científica, cálculos financieros o cualquier dominio donde la aritmética decimal exacta sea crucial, podrías necesitar:
- El módulo
decimalde Python para aritmética decimal exacta. - El módulo
fractionsde Python para aritmética racional exacta. - Redondeo cuidadoso en puntos apropiados de tus cálculos.
Estos módulos están más allá del alcance de este capítulo, pero es valioso saber que existen.
4.10.5) Cuando la precisión de coma flotante no importa
Para la mayoría de las tareas de programación cotidiana, la precisión de coma flotante es más que suficiente:
# 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.25Cuando redondeas resultados para mostrarlos o cuando los errores diminutos son insignificantes en comparación con la precisión de tus medidas, la aritmética de coma flotante funciona perfectamente bien.
4.10.6) La idea clave
Los números de coma flotante son aproximaciones de números reales. Para la mayoría de las tareas de programación, son lo suficientemente precisos. Pero recuerda:
- No compares floats por igualdad exacta: en su lugar, comprueba si son lo suficientemente cercanos.
- Redondea resultados al mostrarlos para evitar mostrar precisión sin sentido.
- Sé consciente de la acumulación de errores en cálculos repetidos.
- Para cálculos financieros, considera redondear a la precisión adecuada o usar el módulo
decimal.
Entender estas limitaciones te ayuda a escribir programas más robustos y evitar errores sorprendentes. La buena noticia es que, para la gran mayoría de tareas de programación, la aritmética de coma flotante "simplemente funciona" si sigues estas pautas sencillas.
4.11) (Opcional) Funciones matemáticas básicas con el módulo math (sqrt, floor, ceil, pi)
Los operadores y funciones integradas de Python cubren la aritmética básica, pero para operaciones matemáticas más avanzadas, Python proporciona el módulo math. Un módulo es una colección de funciones y valores relacionados que puedes usar en tus programas. Aprenderemos mucho más sobre módulos en el Capítulo 22, pero por ahora, introduciremos lo básico que necesitas para usar el módulo math.
4.11.1) Importar el módulo math
Para usar el módulo math, necesitas importarlo al principio de tu programa:
# importing_math.py
import math
# Now you can use functions from the math module
# by writing math.function_name()Cuando importas un módulo, obtienes acceso a todas sus funciones y valores. Los usas escribiendo el nombre del módulo, un punto y luego el nombre de la función o valor.
4.11.2) Constantes matemáticas: pi y e
El módulo math proporciona valores precisos para constantes matemáticas importantes:
# 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.544.11.3) Raíz cuadrada con sqrt()
La función sqrt() calcula la raíz cuadrada de un número:
# 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.0Ten en cuenta que sqrt() devuelve un float, incluso para cuadrados perfectos. Además, no puedes tomar la raíz cuadrada de números negativos con sqrt() (generará un error).
4.11.4) Floor y ceiling con floor() y ceil()
La función floor() redondea hacia abajo al entero más cercano (hacia el infinito negativo), y ceil() redondea hacia arriba (hacia el infinito positivo):
# 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)Estas funciones son útiles cuando necesitas asegurarte de que un valor esté dentro de cierto rango o cuando necesitas resultados enteros con un comportamiento de redondeo específico:
# 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: 84.11.5) Comparar floor(), ceil(), round() e int()
Es útil entender cómo difieren estos enfoques de redondeo:
# 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(): -3Aquí hay una representación visual de cómo se comportan estas funciones:
4.11.6) Otras funciones útiles del módulo math
El módulo math contiene muchas otras funciones. Aquí hay algunas más que son comúnmente útiles:
# 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: TrueLa función isclose() es particularmente útil para comparar números de coma flotante (como discutimos en la sección 4.10):
# 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 close4.11.7) Cuándo usar el módulo math
Usa el módulo math cuando necesites:
- Constantes matemáticas (π, e).
- Raíces cuadradas y otras raíces.
- Funciones trigonométricas (
sin,cos,tan). - Funciones logarítmicas y exponenciales.
- Control preciso del redondeo (
floor,ceil). - Comparación de coma flotante (
isclose).
Para la aritmética básica (+, -, *, /, //, %, **), usa los operadores integrados de Python. Para operaciones matemáticas más avanzadas, importa y usa el módulo math.
El módulo math forma parte de la biblioteca estándar de Python, lo que significa que siempre está disponible: solo necesitas importarlo. Exploraremos más módulos y aprenderemos sobre el sistema de importación en detalle en el Capítulo 22.
En este capítulo, has aprendido a trabajar con números en Python: realizar operaciones aritméticas, entender los distintos tipos de división, usar la precedencia de operadores, mezclar enteros y floats, emplear funciones numéricas integradas, reconocer patrones numéricos comunes, trabajar con operadores bit a bit, entender la precisión de coma flotante y usar el módulo math para operaciones avanzadas.
Estas operaciones numéricas forman la base de innumerables tareas de programación, desde cálculos simples hasta análisis de datos complejos. A medida que continúes aprendiendo Python, usarás estas operaciones constantemente, a menudo en combinación con las estructuras de control de flujo y las colecciones de datos que aprenderás en los próximos capítulos.
Practica estas operaciones hasta que se vuelvan algo natural. Intenta escribir pequeños programas que calculen cantidades del mundo real: convertir temperaturas, calcular áreas y volúmenes, procesar datos financieros o analizar mediciones. Cuanto más practiques, más cómodo te sentirás con las capacidades numéricas de Python.