Skip to content
This page has been auto-translated and may contain errors.View in English

Números y aritmética ​

Los números aparecen en casi todos los programas que escribes. Un carrito de compras suma un precio. Un juego actualiza un puntaje. Un script cuenta cuántas veces ocurrió algo. Python te da operadores aritméticos que funcionan como las matemáticas de papel, más algunos otros que vale la pena conocer desde el principio.

Los operadores aritméticos de Python cubren el conjunto estándar más la división entera, el módulo y la potenciación. Algunos comportamientos difieren de otros lenguajes de formas que importan en la práctica: / siempre devuelve un float, la división entera redondea hacia el infinito negativo, y el módulo sigue la semántica del módulo matemático real.

Los enteros de Python no tienen overflow (el desborde o error que ocurre en lenguajes con enteros de tamaño fijo): crecen tanto como la memoria lo permita. Los floats son los decimales de doble precisión de siempre, adecuados para la mayoría de los casos pero nunca exactos para manejar dinero. Los operadores aritméticos siguen definiciones matemáticas en lugar de la convención de C que copian muchos lenguajes: // es división entera (redondea hacia el infinito negativo) y % toma el signo del divisor, no el del dividendo. Ambas decisiones mantienen el mismo resultado sin importar si tus valores son positivos o negativos, que es el comportamiento que quieres para lógica cíclica o de "wrapping".

Los operadores ​

Los cuatro operadores de las matemáticas (+, -, *, /) funcionan exactamente como esperarías. Python agrega tres más que vas a usar constantemente: división entera, resto y potenciación.

Los cuatro operadores estándar se comportan como esperas, con una regla importante: / siempre devuelve un float, incluso cuando el resultado es un número entero exacto. Los tres operadores adicionales amplían lo que puedes expresar sin trabajo extra.

Cada operador está conectado a un método dunder (un método nombrado con doble guion bajo que Python invoca por detrás): + a __add__, // a __floordiv__, % a __mod__, ** a __pow__, y así sucesivamente. La ventaja práctica es que tus propias clases pueden definir esos métodos y luego funcionar con + o * directamente, igual que un número integrado. Mezclar un int y un float en una misma operación siempre da un float, y / devuelve un float sin importar qué le pases.

python
price = 12.99
quantity = 3

print(price * quantity)   # 38.97
print(price + 2)          # 14.99
print(price - 1.00)       # 11.99
OperadorNombreEjemploResultado
+Suma5 + 38
-Resta5 - 32
*Multiplicación5 * 315
/División5 / 31.6666...
//División entera5 // 31
%Resto5 % 32
**Potenciación5 ** 3125
JunoLos operadores+, -, *, / se comportan como las matemáticas de papel. Los tres extras vale la pena aprenderlos temprano: // divide hasta llegar a un número entero, % te da el resto, y ** eleva a una potencia. Termino usando % más de lo que jamás imaginé.
JunoLos operadores Los cuatro básicos funcionan como esperas, con una regla para tener presente: / siempre devuelve un float. Los extras son // para división entera, % para el resto, y ** para potenciación. Cada uno te ahorra escribir una función auxiliar.
JunoLos operadores Todos los operadores se apoyan en un dunder, así que una clase que define __add__ o __mul__ se conecta directamente con + y *. Mezclar int y float siempre amplía a float, y / devuelve un float sin importar qué le pases.

División: / vs // ​

/ siempre te da el resultado decimal exacto, incluso si la respuesta es un número entero. // descarta la parte fraccionaria y redondea hacia el infinito negativo. Para números positivos eso significa cortar el decimal, pero para negativos avanza un paso más allá de cero:

/ siempre devuelve un float, sin importar si los valores de entrada son enteros. // devuelve el piso del resultado: el entero más grande que es menor o igual al resultado real. Para números positivos eso es igual a truncar. Para negativos, no lo es:

/ es división real y siempre devuelve un float. // es división entera (floor division): redondea el cociente real hacia el infinito negativo en lugar de truncar hacia cero, que es lo que hacen muchos otros lenguajes. La razón por la que esto importa en la práctica: // y % de Python cumplen a == (a // b) * b + (a % b) para cualquier entero de entrada, incluidos los negativos. La división por truncamiento rompe esa identidad para negativos, así que cualquier lógica cíclica o de "wrapping" que escribas se mantiene correcta aquí sin necesidad de tratar el signo como caso especial.

python
10 / 2     # 5.0   (siempre float, aunque la división sea exacta)
10 / 3     # 3.3333333333333335

10 // 3    # 3
7 // 2     # 3
-7 // 2    # -4    (redondea hacia el infinito negativo, no hacia cero)

En general vas a usar // con números positivos. Ten presente el comportamiento con negativos para cuando aparezcan.

JunoDivisión: / vs /// siempre te devuelve un decimal, incluso cuando la división es exacta: 4 / 2 es 2.0. // descarta la parte fraccionaria, pero cuidado con los negativos: -7 // 2 es -4, no -3, porque redondea hacia abajo en lugar de truncar. A mí me agarró desprevenido la primera vez.

Python llama a esto división entera (floor division) porque aplica la función matemática de piso (floor). Otros lenguajes truncan hacia cero en su lugar, dando un resultado distinto para negativos. El nombre // es una pista: divide y después redondea hacia abajo.

JunoDivisión: / vs /// siempre devuelve un float, incluso 4 / 2 da 2.0. // redondea hacia abajo en lugar de truncar, así que -7 // 2 es -4. Para números positivos ambos se ven iguales, la diferencia solo aparece con negativos.

// calcula floor(a / b), no un truncamiento. También funciona con floats: 7.5 // 2 es 3.0, el cociente redondeado hacia abajo devuelto como un float en lugar de un int.

JunoDivisión: / vs //// redondea el cociente hacia abajo, así que cumple a == (a // b) * b + (a % b) para cualquier entrada, negativos incluidos. Esa es la propiedad en la que apoyarte para lógica cíclica. Ten en cuenta que un operando float hace que // devuelva un float, como 7.5 // 2 dando 3.0.

El operador de resto % ​

% te da lo que queda después de una división entera. Si 10 // 3 es 3 (porque 3 entra en 10 tres veces), entonces 10 % 3 es 1 (porque 3 × 3 = 9, y 10 - 9 = 1). El uso más común es comprobar si un número es par o impar.

% es el operador módulo. Comprobar si algo es par o impar es el uso más común, pero se generaliza a cualquier problema de ciclos o "wrapping": mantener un contador dentro de un rango, distribuir elementos entre grupos, repetir una secuencia. El patrón siempre es valor % límite, que devuelve algo entre 0 y límite - 1.

El % de Python es el módulo real: el resultado siempre toma el signo del divisor. Muchos otros lenguajes toman el signo del dividendo en su lugar, así que darían -1 donde Python da 2 para -7 % 3. El resultado de Python surge de definir el módulo como a - (a // b) * b con // redondeando hacia el infinito negativo. La ventaja es que valor % límite se mantiene entre 0 y límite - 1 incluso cuando valor se vuelve negativo, así que un índice que da la vuelta al final de una lista cae dentro del rango sin necesidad de una cláusula de resguardo.

python
10 % 3    # 1
10 % 2    # 0  (división exacta)
10 % 7    # 3

6 % 2     # 0  (par)
7 % 2     # 1  (impar)
JunoEl operador de resto %% es lo que sobra después de una división entera. 10 % 3 es 1 porque 3 entra en 10 tres veces y sobra 1. Lo vas a encontrar sobre todo como la comprobación de par/impar: n % 2 es 0 para par, 1 para impar.
JunoEl operador de resto %% es el módulo, y va mucho más allá de par/impar: cada vez que necesitas mantener un valor dentro de un rango, valor % límite lo envuelve entre 0 y límite - 1. Contadores, asignación de grupos, ciclos repetidos, todo con la misma lógica.
JunoEl operador de resto %% sigue el signo del divisor, así que -7 % 3 es 2, no -1. Eso es lo que permite que valor % límite se mantenga dentro del rango con entradas negativas, sin cláusula de resguardo para un índice que da la vuelta.

Potenciación ** ​

** eleva un número a una potencia. Usa dos asteriscos, no el símbolo ^ (que en Python significa otra cosa):

** es la potenciación. También funciona con floats, lo que te permite expresar raíces como potencias fraccionarias en lugar de llamar a una función aparte:

** eleva a una potencia, y la forma con potencia fraccionaria es útil: n ** 0.5 es una raíz cuadrada sin necesidad de importar nada. Dos operandos int dan un int, cualquier operando float da un float. Algo que conviene saber antes de usarlo en trabajo de precisión o con números grandes: 9 ** 0.5 pasa por aritmética de floats, así que puede terminar en 2.9999999999999996 para algunas entradas. Cuando la exactitud importa, math.isqrt() para raíces enteras y math.pow() son las herramientas a considerar, tratadas en el capítulo de Módulos.

python
2 ** 10    # 1024
3 ** 3     # 27
9 ** 0.5   # 3.0  (raíz cuadrada: elevar a la potencia 0.5)
JunoPotenciación **** eleva un número a una potencia, así que 2 ** 10 es 1024. Una potencia fraccionaria te da una raíz: 9 ** 0.5 es 3.0. La única trampa es el símbolo: usa **, no ^, que en Python hace algo completamente distinto.
JunoPotenciación **** es la potenciación, y acepta floats, así que 9 ** 0.5 te da una raíz cuadrada sin llamar a una función aparte. Usa la forma de potencia fraccionaria cuando quieras una raíz rápida en línea.
JunoPotenciación ** Dos ints dan un int, cualquier float da un float. El truco de la raíz con ** 0.5 tiene el problema de que pasa por aritmética de floats y puede desviarse un poco, así que usa math.isqrt() o math.pow() cuando la exactitud cuente.

Precedencia de operadores ​

Python sigue el orden matemático estándar: primero potenciación, luego multiplicación y división, y después suma y resta. Cuando tengas dudas, usa paréntesis. Dejan clara la intención y no cuestan nada:

Python sigue el orden estándar PEMDAS/BODMAS. La parte que suele confundir: /, // y % comparten el mismo nivel de precedencia y se evalúan de izquierda a derecha cuando se combinan. Los paréntesis son gratis; úsalos siempre que el orden no sea evidente a simple vista:

Precedencia de mayor a menor entre los operadores aritméticos: **, luego el - unario (un signo menos delante de un solo valor), luego * / // % (de izquierda a derecha cuando comparten nivel), y finalmente + -. La trampa que muerde en código real: -2 ** 2 es -(2 ** 2), que es -4, porque ** se aplica antes que el signo menos inicial. Si en algún momento elevas al cuadrado un valor que podría ser negativo, ponlo entre paréntesis como (-2) ** 2, o el signo saldrá mal sin ningún error que te avise.

python
2 + 3 * 4      # 14, no 20
2 ** 3 + 1     # 9,  no 512
10 - 4 / 2     # 8.0, no 3.0

(2 + 3) * 4    # 20
10 / (2 + 3)   # 2.0
JunoPrecedencia de operadores El mismo orden que en las matemáticas de papel: primero potencias, luego multiplicar y dividir, después sumar y restar. Cuando el orden no sea evidente a simple vista, agrega paréntesis. No cuestan nada y dejan claro exactamente lo que quisiste decir.
JunoPrecedencia de operadores Orden PEMDAS estándar, con una trampa: /, // y % están todos en el mismo nivel y se evalúan de izquierda a derecha cuando se combinan. Esa regla de izquierda a derecha es lo que la gente suele leer mal, así que usa paréntesis siempre que la agrupación no sea clara.
JunoPrecedencia de operadores La trampa es que el menos unario se aplica con menor precedencia que **, así que -2 ** 2 es -4. Eleva al cuadrado cualquier cosa que pueda ser negativa como (-2) ** 2, o el signo se invierte sin ningún error que lo señale.

Cómo interactúan int y float ​

Python tiene una regla consistente: / siempre devuelve un decimal, incluso 4 / 2 da 2.0. Cualquier operación que mezcle un entero y un decimal también da un decimal. Cuando necesites un número entero, usa // o convierte con int().

Las reglas de tipo son predecibles: / siempre devuelve float. // y % con dos enteros devuelven int. Cualquier operación que mezcle int y float devuelve float. Esto significa que 4 / 2 es 2.0, no 2, algo que importa cuando necesitas un entero (por ejemplo, para usarlo como índice).

La regla es fija y vale la pena internalizarla: el int se amplía a float cada vez que se mezclan ambos tipos, / siempre devuelve un float, y // devuelve un int solo cuando ambos operandos son int. El error que esto evita es que un float se cuele en algo que necesita un número entero, como un índice de lista o una clave de diccionario, donde 2.0 y 2 no son intercambiables. Cuando un cálculo pueda producir un float y necesites un int, conviértelo con int() en ese punto en lugar de confiar en que la división haya dado un resultado exacto.

python
4 / 2      # 2.0   (float, siempre)
4 // 2     # 2     (int)
4 + 2      # 6     (int)
4 + 2.0    # 6.0   (float)
4 * 0.5    # 2.0   (float)
JunoCómo interactúan int y float/ siempre da un decimal, así que 4 / 2 es 2.0, no 2. Mezcla un número entero con un decimal en cualquier operación y obtienes un decimal como resultado. Cuando necesites un número entero, usa // o envuélvelo con int().
JunoCómo interactúan int y float/ siempre es float, // y % sobre dos ints se mantienen como int, y cualquier int mezclado con un float sale como float. Lo que atrapa a la gente es que 4 / 2 sea 2.0 cuando querías un índice.
JunoCómo interactúan int y float Un float escurridizo salido de / rompe cualquier cosa que necesite un int exacto, como un índice o una clave de diccionario donde 2.0 no es 2. Convierte con int() en ese límite en lugar de confiar en que la división salga exacta.

Precisión de los floats ​

Hay un detalle que sorprende a casi todo el mundo en algún momento:

python
0.1 + 0.2   # 0.30000000000000004

Ese pequeño error no es un bug de Python. Las computadoras almacenan los números decimales en binario, y algunos valores como 0.1 no se pueden representar exactamente. Es similar a cómo 1/3 no se puede escribir exactamente en decimal. Para la mayoría de los cálculos cotidianos no importa. Para mostrar cantidades de dinero, round() o el especificador de formato :.2f mantienen la salida ordenada.

Los floats te dan aproximadamente entre 15 y 16 dígitos decimales significativos de precisión. La imprecisión aparece porque algunas fracciones no se pueden almacenar exactamente en binario, así que 0.1 + 0.2 produce 0.30000000000000004. Esa desviación solo se nota cuando inspeccionas el valor crudo; formatear con :.2f o round() la oculta en la salida.

Para trabajo financiero donde las fracciones de centavo se van acumulando, Python ofrece decimal.Decimal en la biblioteca estándar, con aritmética exacta en base 10. Eso se cubre en el capítulo de Módulos.

Un float almacena números en binario (base 2), y cualquier fracción cuyo denominador no sea una potencia de dos (como 1/10) no tiene forma binaria exacta, de la misma manera que 1/3 no tiene forma decimal exacta. Esa es la razón completa por la que 0.1 + 0.2 termina en 0.30000000000000004. El error por operación es mínimo, pero se acumula a lo largo de una cadena larga de operaciones aritméticas, así que dos formas distintas de calcular el mismo total pueden diferir en los últimos dígitos.

La regla que evita el error: nunca uses un float para dinero, y nunca compares floats por igualdad exacta con ==. Usa decimal.Decimal cuando necesites aritmética exacta en base 10 (facturación, contabilidad), o fractions.Fraction cuando necesites proporciones exactas sin ningún redondeo. Ambos vienen incluidos en la biblioteca estándar de Python, cubiertos en el capítulo de Módulos.

JunoPrecisión de los floats Las computadoras almacenan los decimales en binario, y algunos valores como 0.1 no tienen forma binaria exacta, así que 0.1 + 0.2 sale como 0.30000000000000004. No es un bug de Python, todos los lenguajes hacen esto. Para la salida cotidiana, round() o :.2f lo mantienen ordenado.
JunoPrecisión de los floats El binario no puede guardar cada decimal exactamente, así que 0.1 + 0.2 se desvía a 0.30000000000000004. Está bien para la mayoría de las matemáticas, y :.2f o round() ordenan la presentación. Cuando los centavos tienen que cuadrar exactamente, pásate a decimal.Decimal.
JunoPrecisión de los floats La regla que te salva: nada de floats para dinero, y nunca == entre floats, porque el error se acumula a lo largo de una cadena de operaciones. Usa decimal.Decimal para base 10 exacta y fractions.Fraction para proporciones exactas.

Literales numéricos legibles ​

Python te permite poner guiones bajos en los literales numéricos para hacer más legibles los números grandes. Python los ignora por completo; están ahí solo para ti:

Los guiones bajos son válidos en cualquier parte de un literal numérico y se eliminan durante el análisis sin afectar el valor. Son útiles como separador de miles en constantes y para agrupar dígitos en literales binarios o hexadecimales:

Los guiones bajos se eliminan antes de que Python vea el valor, así que no tienen ningún efecto sobre él. Funcionan igual en enteros, floats y literales con base (0xFF_FF, 0b1010_0001, 1_234.567_890), lo que los hace útiles para agrupar bytes en una constante hexadecimal, no solo para los miles en una decimal. Las únicas restricciones: un guion bajo no puede ir al principio, al final, ni al lado de un punto decimal o de un marcador de exponente.

python
population = 8_100_000_000
distance_km = 384_400
pi_approx = 3.141_592_653
JunoLiterales numéricos legibles Coloca guiones bajos en un número largo para hacerlo legible: 8_100_000_000 es el mismo valor que 8100000000. Python los ignora por completo, están ahí para tus ojos, no para el intérprete.
JunoLiterales numéricos legibles Los guiones bajos son válidos en cualquier parte dentro de un literal numérico y desaparecen al momento de analizarlo, así que el valor no cambia. Buenos como separador de miles en constantes, y también útiles para agrupar dígitos en binario o hexadecimal.
JunoLiterales numéricos legibles Los guiones bajos son puramente estéticos en enteros, floats y literales con base, así que 0xFF_FF y 0b1010_0001 se leen más claro. Las únicas reglas: no al principio, no al final, ni al lado de un punto decimal o un marcador de exponente.

Funciones integradas útiles ​

abs() ​

abs() devuelve el valor absoluto: siempre positivo, sin importar el signo de la entrada. Úsala cuando te importa qué tan lejos está un número de cero, no en qué dirección.

abs() devuelve la magnitud de un número. Funciona con enteros y floats. Es útil para cálculos de distancia, márgenes de error, y cualquier situación donde la dirección no importa y solo necesitas el tamaño del valor.

abs() devuelve la magnitud de un número, y el tipo de retorno coincide con el de la entrada: un int da un int, un float da un float. El uso cotidiano es comparar qué tan lejos están dos valores de un objetivo sin importar de qué lado caigan, por ejemplo abs(measured - expected) < tolerance como comprobación de tolerancia, que se lee más claro que dos comparaciones contra un rango con signo.

python
abs(-5)     # 5
abs(3.7)    # 3.7
abs(-0.5)   # 0.5
Junoabs()abs() quita el signo y devuelve el tamaño positivo: abs(-5) es 5, abs(3.7) es 3.7. Úsala cuando te importa qué tan lejos está un número de cero, no hacia qué lado se inclina.
Junoabs()abs() da la magnitud de un int o float, descartando la dirección. Es la forma limpia de escribir comprobaciones de distancia y de margen de error, en cualquier lugar donde el signo no forme parte de la pregunta.
Junoabs()abs() conserva el tipo de la entrada, un int sigue siendo un int. El patrón que vale la pena recordar es abs(measured - expected) < tolerance: una sola comprobación de tolerancia en lugar de dos comparaciones alrededor de un rango con signo.

round() ​

round() redondea al entero más cercano por defecto. Pásale un segundo argumento para conservar una cantidad específica de decimales:

python
round(3.7)          # 4
round(3.2)          # 3
round(3.14159, 2)   # 3.14

Algo que vale la pena saber: round(2.5) da 2, no 3. Python redondea al número par más cercano cuando un valor está exactamente a la mitad entre dos opciones.

round() usa el redondeo bancario: cuando el valor está exactamente a la mitad, redondea al número par más cercano en lugar de redondear siempre hacia arriba. Esto minimiza el error acumulado en trabajo estadístico, pero puede sorprenderte si esperas que 0.5 siempre redondee hacia arriba:

python
round(2.5)   # 2  (redondea al par más cercano)
round(3.5)   # 4
round(4.5)   # 4  (no 5)
round(3.14159, 2)   # 3.14

round() usa el redondeo al par más cercano (redondeo bancario): un empate exacto va hacia el entero par más próximo, no siempre hacia arriba. Eso evita que el error de redondeo sesgue una columna larga de cifras hacia un solo lado, y por eso es el valor por defecto en trabajo estadístico. Hay dos precauciones prácticas. Primero, un empate en un float a menudo no es un empate exacto una vez que está en binario, así que round(2.675, 2) da 2.67, no 2.68, porque 2.675 no se puede almacenar exactamente. Segundo, cuando necesites un redondeo de moneda predecible, hazlo con decimal.Decimal y un modo de redondeo explícito en lugar de confiar en round() sobre floats.

python
round(2.5)   # 2
round(3.5)   # 4
round(4.5)   # 4
Junoround()round(x) va al número entero más cercano, round(x, n) conserva n decimales. La sorpresa: un valor que cae exactamente a la mitad redondea al número par más cercano, así que round(2.5) es 2, no 3.
Junoround()round() usa redondeo bancario, así que los empates exactos van al número par más cercano: round(2.5) es 2, round(4.5) es 4. Reduce el sesgo en trabajo agregado, pero espéralo si asumías que los empates siempre redondean hacia arriba.
Junoround() El redondeo bancario más el binario hacen que round(2.675, 2) sea 2.67, ya que 2.675 no es un empate exacto por debajo. Para moneda, redondea con decimal.Decimal y un modo elegido en lugar de confiar en round() sobre floats.

divmod() ​

divmod() te da el cociente y el resto en una sola llamada. Devuelve un par de valores, una tupla (cubierta en el capítulo de Tuplas y conjuntos), que puedes asignar a dos nombres a la vez:

divmod(a, b) es equivalente a (a // b, a % b) pero calculado en un solo paso. Úsalo cuando necesites ambos valores de todas formas: paginación, conversión de tiempo, o distribución de elementos en grupos.

divmod(a, b) hace la división una sola vez y devuelve tanto el cociente entero como el resto del módulo como un par, así que evitas recalcular la misma división con // y luego con %. Se lee más limpio en los lugares donde necesitas ambas mitades de todos modos: conversión de tiempo (divmod(seconds, 60)), paginación, o distribuir elementos en filas de tamaño fijo.

python
divmod(10, 3)   # (3, 1): cociente 3, resto 1
divmod(7, 2)    # (3, 1)
divmod(9, 3)    # (3, 0)

quotient, remainder = divmod(10, 3)
print(quotient)    # 3
print(remainder)   # 1
Junodivmod()divmod(a, b) devuelve el cociente y el resto juntos: divmod(10, 3) es (3, 1). Puedes desempaquetar ambos a la vez con quotient, remainder = divmod(10, 3).
Junodivmod()divmod(a, b) es (a // b, a % b) en un solo paso. Úsalo cuando necesites ambas partes, como en conversión de tiempo o paginación, en lugar de calcular la división dos veces.
Junodivmod() Una sola división, dos resultados: divmod() evita el trabajo redundante de hacer // y % por separado. Se lee mejor donde quieres ambas mitades de todos modos, como divmod(seconds, 60) o el armado de filas.

En la práctica ​

Una calculadora de propina:

python
bill = 45.50
tip_rate = 0.18
tip = round(bill * tip_rate, 2)
total = round(bill + tip, 2)

print(f"Cuenta: ${bill}")
print(f"Propina: ${tip}")
print(f"Total: ${total}")

round() hace que la salida se vea como dinero en lugar de una larga tira de decimales.

Contar páginas para paginación y llevar el progreso como porcentaje:

python
total_items = 153
items_per_page = 10

full_pages, leftover = divmod(total_items, items_per_page)
total_pages = (total_items + items_per_page - 1) // items_per_page

print(f"Páginas completas: {full_pages}, sobrante: {leftover}")
print(f"Total de páginas necesarias: {total_pages}")   # 16
python
total_files = 847
processed_files = 312

percent = round(processed_files / total_files * 100, 1)
print(f"Progreso: {processed_files}/{total_files} ({percent}%)")

La fórmula de división redondeando hacia arriba (n + d - 1) // d es un truco estándar con enteros para redondear hacia arriba sin convertir a float.

Normalización min-max y cambio porcentual: dos patrones que aparecen constantemente en trabajo de datos:

python
# normalización min-max: escala un valor al rango 0.0 a 1.0
value = 75
minimum = 0
maximum = 100

normalised = (value - minimum) / (maximum - minimum)
print(f"Normalizado: {normalised:.2f}")   # 0.75

# cambio porcentual entre dos mediciones
before = 1_200
after = 1_380

change = (after - before) / before * 100
print(f"Cambio: {change:.1f}%")          # 15.0%

Ambos patrones se reducen a una razón: un valor relativo a un rango de referencia o a una magnitud de referencia. La precisión de float es suficiente para la mayoría del trabajo analítico; el error acumulado solo importa cuando el cálculo encadena docenas de operaciones o involucra valores que difieren en muchos órdenes de magnitud.