Pregunta 1¿Cuál es la opción más apropiada cuando no puedes tolerar ni una deriva de un centavo en cálculos monetarios?
decimal y fractions — Aritmética precisa sin errores de float
Aprende los módulos decimal y fractions de Python desde cero. Cubre el problema de 0.1 + 0.2 != 0.3 con float, el cálculo monetario exacto con Decimal (y por qué construyes Decimal desde cadenas), aritmética con Fraction que se auto-reduce, y elegir entre los tres tipos numéricos — con ejercicios prácticos ejecutables.
Este artículo cubre los dos módulos para los casos donde `float` causa problemas. decimal.Decimal hace aritmética exacta en base 10, lo que lo hace esencial para cálculos monetarios; fractions.Fraction mantiene el numerador y el denominador como enteros para calcular con fracciones reducidas de forma exacta. Ambos existen para eliminar los errores de redondeo inevitables de float.
Errores de redondeo de float y decimal.Decimal — No uses float para matemáticas precisas
El float de Python está representado internamente en binario, así que las fracciones decimales como `0.1` que no dividen limpiamente en binario llevan un pequeño error de redondeo. El ejemplo más famoso es `0.1 + 0.2 == 0.3` devolviendo `False`. Para gráficos o computación científica el error es despreciable, pero en sumas financieras incluso una pequeña deriva se vuelve crítica.
Por eso existe decimal.Decimal. Está representado internamente en base 10, así que construir uno desde la cadena "0.1" te permite calcular sin error.
0.1. Decimal usa una representación interna en base 10, así que calcula exactamente como dice la cadena de entrada, pero es más lento que float. Para procesar dinero e impuestos, elige Decimal.from decimal import Decimal
# error de redondeo de float
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False ← contraintuitivo!
# Construye Decimal desde cadena (desde float hereda el error)
a = Decimal("0.1")
b = Decimal("0.2")
print(a + b) # 0.3
print(a + b == Decimal("0.3")) # True
# Un ejemplo monetario
price = Decimal("1980")
tax_rate = Decimal("0.10")
total = price * (Decimal("1") + tax_rate)
print(total) # 2178.00
Construye Decimal desde una cadena, no desde float
Si pasas un float — Decimal(0.1) — estás entregando un valor que ya lleva un error de redondeo, así que terminas con un Decimal que aún tiene el error del float. Pasa siempre una cadena: Decimal("0.1"). Es difícil de detectar en pruebas y es la trampa clásica que produce una deriva de un centavo en producción por primera vez.
fractions.Fraction — Mantener fracciones como fracciones
Fraction es un tipo que almacena el numerador y el denominador como enteros para la aritmética. Construye uno con Fraction(1, 3), y las sumas, restas y multiplicaciones siguientes devuelven un resultado de fracción reducida. Como float, 1/3 es la aproximación 0.3333333333333333, pero con Fraction se queda como "un tercio" mismo.
+ / - / * también son Fraction, y se reducen automáticamente. Puedes convertir a float con float(f), pero eso introduce error de redondeo.| Tipo | Representación interna | Mejor uso |
|---|---|---|
| int | Entero | Conteos y recuentos (sin error) |
| float | Coma flotante binaria | Computación científica, gráficos (rápido, con error) |
| Decimal | Representación en base 10 | Dinero e impuestos (cuando importa la precisión base 10) |
| Fraction | Numerador y denominador (enteros) | Razones y probabilidades (cuando quieres una forma reducida) |
Los objetos Fraction exponen atributos para el numerador y el denominador. f.numerator devuelve el numerador y f.denominator devuelve el denominador — ambos después de la reducción. Por ejemplo, Fraction(2, 6) se normaliza internamente a Fraction(1, 3), así .numerator es 1 y .denominator es 3.
| Atributo / Método | Significado | Ejemplo |
|---|---|---|
| f.numerator | Numerador | Fraction(1, 3).numerator → 1 |
| f.denominator | Denominador | Fraction(1, 3).denominator → 3 |
| float(f) | Convertir a float | float(Fraction(1, 2)) → 0.5 |
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2Al construir un Decimal, ¿cuál es la forma correcta que no introduce error?
Pregunta 3¿Cuál usas cuando quieres calcular razones o probabilidades como fracciones reducidas?