Aprende leyendo en orden

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.

float vs Decimal
float0.1 + 0.2→ 0.30000000000000004error de redondeoDecimalDecimal('0.1') + Decimal('0.2')→ Decimal('0.3')sin error
float es rápido (instrucciones de CPU) pero introduce errores de redondeo con valores como 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.

Primero observa el error de redondeo de float, luego cambia a Decimal para un cálculo exacto con impuestos.

① Importa Decimal desde el módulo decimal.

② Calcula 0.1 + 0.2 con float e imprímelo como float: ◯◯ (deberías ver el error).

③ Imprime el resultado de 0.1 + 0.2 == 0.3 como float equal?: ◯◯ (devuelve False, contraintuitivamente).

④ Calcula 0.1 + 0.2 con Decimal e imprímelo como Decimal: ◯◯ (sin error esta vez).

⑤ Define un precio sin impuestos de 1980 y una tasa del 10% como Decimal, calcula el total con impuestos y imprímelo como total: ◯◯.

Editor Python

Ejecutar el código para ver el resultado

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.

Fraction de un vistazo
Fraction(1, 3)+ Fraction(1, 6)Fraction(1, 2)(auto-reducido)
Construye con Fraction(numerador, denominador) — un tipo razón entera. Los resultados de + / - / * también son Fraction, y se reducen automáticamente. Puedes convertir a float con float(f), pero eso introduce error de redondeo.
TipoRepresentación internaMejor uso
intEnteroConteos y recuentos (sin error)
floatComa flotante binariaComputación científica, gráficos (rápido, con error)
DecimalRepresentación en base 10Dinero e impuestos (cuando importa la precisión base 10)
FractionNumerador 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étodoSignificadoEjemplo
f.numeratorNumeradorFraction(1, 3).numerator → 1
f.denominatorDenominadorFraction(1, 3).denominator → 3
float(f)Convertir a floatfloat(Fraction(1, 2)) → 0.5

Suma dos fracciones y accede a los atributos del resultado. Calcula 1/3 + 1/6 manteniéndolo como Fraction y confirma que la respuesta se reduce automáticamente.

① Importa Fraction del módulo fractions.

② Construye 1/3 y 1/6 como Fraction.

③ Suma los dos e imprime el resultado como sum: ◯/◯ (debería estar auto-reducido).

④ Lee el numerador y el denominador del resultado vía atributos, e imprímelos como numerator: ◯ denominator: ◯.

⑤ Convierte el resultado a float e imprímelo como as float: ◯.◯.

Editor Python

Ejecutar el código para ver el resultado
QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

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?

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?