Usar una computadora para hacer matemáticas bastante complejas es una de las razones por las que esta máquina se desarrolló originalmente. Siempre que los números enteros y las sumas, restas y multiplicaciones estén involucrados exclusivamente en los cálculos, todo está bien. Tan pronto como entran en juego números o fracciones de punto flotante, así como divisiones, se complica enormemente todo el asunto.
Como usuario habitual, no somos del todo conscientes de estos problemas que ocurren detrás de escena y pueden terminar con resultados bastante sorprendentes y posiblemente inexactos para nuestros cálculos. Como desarrolladores, tenemos que asegurarnos de que se tomen en cuenta las medidas adecuadas para instruir a la computadora para que funcione correctamente.
En nuestra vida diaria usamos el sistema decimal que se basa en el número 10. La computadora usa el sistema binario, que es base 2, e internamente almacena y procesa los valores como una secuencia de 1 y 0. Los valores con los que trabajamos deben transformarse constantemente entre las dos representaciones. Como se explica en Documentación de Python:
… la mayoría de las fracciones decimales no se pueden representar exactamente como fracciones binarias. Una consecuencia es que, en general, los números decimales de coma flotante que ingresa solo son aproximados por los números binarios de coma flotante realmente almacenados en la máquina.
Este comportamiento conduce a resultados sorprendentes en adiciones simples, como se muestra aquí:
Listado 1: inexactitudes con números de punto flotante
>>> s = 0.3 + 0.3 + 0.3
>>> s
0.8999999999999999
Como puede ver aquí, la salida es inexacta, ya que debería resultar en 0.9.
El Listado 2 muestra un caso similar para formatear un número de punto flotante para 17 lugares decimales.
Te puede interesar:Revisión del curso: Práctica de la visión por computadora con OpenCV y PythonListado 2: Dar formato a un número de punto flotante
>>> format(0.1, '.17f')
'0.10000000000000001'
Como puede haber aprendido de los ejemplos anteriores, tratar con números de punto flotante es un poco complicado y requiere medidas adicionales para lograr el resultado correcto y minimizar los errores de cálculo. Redondear el valor puede resolver al menos algunos de los problemas. Una posibilidad es la incorporada round()
función (para obtener más detalles sobre su uso, consulte a continuación):
Listado 3: Cálculo con valores redondeados
>>> s = 0.3 + 0.3 + 0.3
>>> s
0.8999999999999999
>>> s == 0.9
False
>>> round(0.9, 1) == 0.9
True
Como alternativa, puede trabajar con el módulo matemático, o trabajar explícitamente con fracciones almacenadas como dos valores (numerador y denominador) en lugar de los valores de coma flotante redondeados, bastante inexactos.
Para almacenar los valores así los dos módulos de Python decimal y fracción entrar en juego (ver ejemplos a continuación). Pero primero, echemos un vistazo más de cerca al término «redondeo».
¿Qué es el redondeo?
Contenido
En pocas palabras, el proceso de redondeo significa:
… reemplazando [a value] con un número diferente que es aproximadamente igual al original, pero tiene una representación más corta, más simple o más explícita.
Fuente: https://en.wikipedia.org/wiki/Rounding
Te puede interesar:Listas enlazadas en detalle con ejemplos de Python: Listas enlazadas simplesBásicamente, agrega inexactitud a un valor calculado con precisión al acortarlo. En la mayoría de los casos, esto se hace eliminando los dígitos después del punto decimal, por ejemplo, de 3,73 a 3,7, de 16,67 a 16,7 o de 999,95 a 1000.
Esta reducción se realiza por varias razones, por ejemplo, para ahorrar espacio al almacenar el valor o simplemente para eliminar los dígitos no utilizados. Además, los dispositivos de salida, como las pantallas analógicas o los relojes, pueden mostrar el valor calculado con una precisión limitada y requieren datos de entrada ajustados.
En general, se aplican dos reglas bastante simples para redondear, es posible que las recuerde de la escuela. Los dígitos del 0 al 4 conducen al redondeo hacia abajo, y los números del 5 al 9 conducen a redondeando. La siguiente tabla muestra una selección de casos de uso.
| original value | rounded to | result |
|----------------|--------------|--------|
| 226 | the ten | 230 |
| 226 | the hundred | 200 |
| 274 | the hundred | 300 |
| 946 | the thousand | 1,000 |
| 1,024 | the thousand | 1,000 |
| 10h45m50s | the minute | 10h45m |
Métodos de redondeo
Los matemáticos han desarrollado una variedad de diferentes métodos de redondeo para abordar el problema del redondeo. Esto incluye truncamiento simple, redondeo hacia arriba, redondeo hacia abajo, redondeo a la mitad hacia arriba, redondeo a la mitad hacia abajo, así como redondear a la mitad desde cero y redondear a la mitad a par.
Por ejemplo, redondear a la mitad desde cero es aplicado por la Comisión Europea de Asuntos Económicos y Financieros al convertir divisas al euro. Varios países, como Suecia, los Países Bajos, Nueva Zelanda y Sudáfrica, siguen la regla denominada «redondeo de efectivo», «redondeo de centavos» o «redondeo sueco».
[Cash rounding] ocurre cuando la unidad mínima de cuenta es menor que la denominación física más baja de la moneda. El monto a pagar por una transacción en efectivo se redondea al múltiplo más cercano de la unidad monetaria mínima disponible, mientras que las transacciones pagadas de otras formas no se redondean.Fuente: https://en.wikipedia.org/wiki/Cash_rounding
En Sudáfrica, desde 2002, el redondeo de efectivo se realiza a los 5 centavos más cercanos. En general, este tipo de redondeo no se aplica a los pagos electrónicos que no son en efectivo.
Te puede interesar:Programación Python en modo interactivo vs scriptPor el contrario, redondear de la mitad a par es la estrategia predeterminada para Python, Numpy y Pandas, y está en uso por el round()
función que ya se mencionó antes. Pertenece a la categoría de los métodos de redondeo al más cercano y también se conoce como redondeo convergente, redondeo estadístico, redondeo holandés, redondeo gaussiano, redondeo impar-par y redondeo bancario. Este método se define en IEEE 754 y funciona de tal manera que «si la parte fraccionaria de x
es 0.5, entonces y
es el número entero par más cercano a x
. «Se supone» que las probabilidades de que un empate en un conjunto de datos se redondee hacia abajo o hacia arriba son iguales «, lo que suele ser el caso en la práctica. Aunque no es completamente perfecta, esta estrategia conduce a resultados apreciables.
La siguiente tabla ofrece ejemplos prácticos de redondeo para este método:
| original value | rounded to |
|----------------|------------|
| 23.3 | 23 |
| 23.5 | 24 |
| 24.0 | 24 |
| 24.5 | 24 |
| 24.8 | 25 |
| 25.5 | 26 |
Funciones de Python
Python viene con la función incorporada round()
eso es bastante útil en nuestro caso. Acepta dos parámetros: el valor original y el número de dígitos después del punto decimal. La siguiente lista ilustra el uso del método para uno, dos y cuatro dígitos después del punto decimal.
Listado 4: Redondeo con un número específico de dígitos
>>> round(15.45625, 1)
15.5
>>> round(15.45625, 2)
15.46
>>> round(15.45625, 4)
15.4563
Si llama a esta función sin el segundo parámetro, el valor se redondea a un valor entero completo.
Listado 5: Redondeo sin un número específico de dígitos
>>> round(0.85)
1
>>> round(0.25)
0
>>> round(1.5)
2
Los valores redondeados funcionan bien en caso de que no necesite resultados absolutamente precisos. Tenga en cuenta el hecho de que comparar valores redondeados también puede ser una pesadilla. Será más obvio en el siguiente ejemplo: la comparación de valores redondeados basados en el redondeo previo y posterior al redondeo.
El primer cálculo del Listado 6 contiene valores redondeados previamente y describe el redondeo antes de sumar los valores. El segundo cálculo contiene un resumen post-redondeado que significa redondeo después de la suma. Notará que el resultado de la comparación es diferente.
Te puede interesar:Programación funcional en PythonListado 6: Pre-redondeo vs. post-redondeo
>>> round(0.3, 10) + round(0.3, 10) + round(0.3, 10) == round(0.9, 10)
False
>>> round(0.3 + 0.3 + 0.3, 10) == round(0.9, 10)
True
Módulos de Python para cálculos de punto flotante
Hay cuatro módulos populares que pueden ayudarlo a manejar adecuadamente los números de punto flotante. Esto incluye el math
módulo, el Numpy
módulo, el decimal
módulo, y el fractions
módulo.
los math
El módulo se centra en constantes matemáticas, operaciones de punto flotante y métodos trigonométricos. los Numpy
El módulo se describe a sí mismo como «el paquete fundamental para la computación científica» y es famoso por su variedad de métodos de matriz. los decimal
módulo cubre aritmética decimal de coma fija y coma flotante, y la fractions
El módulo trata de números racionales, específicamente.
Primero, tenemos que intentar mejorar el cálculo del Listado 1. Como muestra el Listado 7, después de haber importado el math
módulo podemos acceder al método fsum()
que acepta una lista de números de coma flotante. Para el primer cálculo, no hay diferencia entre los valores integrados sum()
método, y el fsum()
método del math
módulo, pero para el segundo lo es, y devuelve el resultado correcto que esperaríamos. La precisión depende del algoritmo IEEE 754 subyacente.
Listado 7: Cálculos de coma flotante con la ayuda del math
módulo
>>> import math
>>> sum([0.1, 0.1, 0.1])
0.30000000000000004
>>> math.fsum([0.1, 0.1, 0.1])
0.30000000000000004
>>> sum([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1])
0.9999999999999999
>>> math.fsum([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1])
1.0
En segundo lugar, echemos un vistazo a Numpy
módulo. Viene con el método around () que redondea los valores proporcionados como una matriz. Procesa los valores individuales de la misma manera que el predeterminado round()
método.
Para comparar valores Numpy
ofrece el equal()
método. Similar a around()
acepta valores individuales así como listas de valores (los llamados vectores) para ser procesados. El Listado 8 muestra una comparación de valores individuales y valores redondeados. El comportamiento observado es bastante similar a los métodos mostrados anteriormente.
Listado 8: Comparación de valores usando el método de igualdad del Numpy
módulo
>>> import numpy
>>> print (numpy.equal(0.3, 0.3))
True
>>> print (numpy.equal(0.3 + 0.3 + 0.3 , 0.9))
False
>>> print (numpy.equal(round(0.3 + 0.3 + 0.3) , round(0.9)))
True
La opción tres es la decimal
módulo. Ofrece una representación decimal exacta y conserva los dígitos significativos. La precisión predeterminada es de 28 dígitos y puede cambiar este valor a un número tan grande como sea necesario para su problema. El Listado 9 muestra cómo usar una precisión de 8 dígitos.
Listado 9: Creando números decimales usando el decimal
módulo
>>> import decimal
>>> decimal.getcontext().prec = 8
>>> a = decimal.Decimal(1)
>>> b = decimal.Decimal(7)
>>> a / b
Decimal('0.14285714')
Ahora, comparar los valores flotantes se vuelve mucho más fácil y conduce al resultado que estábamos buscando.
Listado 10: Comparaciones usando el decimal
módulo
>>> import decimal
>>> decimal.getcontext().prec = 1
>>> a = decimal.Decimal(0.3)
>>> b = decimal.Decimal(0.3)
>>> c = decimal.Decimal(0.3)
>>> a + b + c
Decimal('0.9')
>>> a + b + c == decimal.Decimal('0.9')
True
los decimal
El módulo también viene con un método para redondear valores: cuantizar (). La estrategia de redondeo predeterminada se establece en redondear de la mitad a par, y también se puede cambiar a un método diferente si es necesario. El Listado 11 ilustra el uso de la quantize()
método. Tenga en cuenta que el número de dígitos se especifica utilizando un valor decimal como parámetro.
Listado 11: Redondeo de un valor usando quantize()
>>> d = decimal.Decimal(4.6187)
>>> d.quantize(decimal.Decimal("1.00"))
Decimal('4.62')
Por último, pero no menos importante, echaremos un vistazo a fractions
módulo. Este módulo le permite manejar valores de punto flotante como fracciones, por ejemplo 0.3
como 3/10. Esto simplifica la comparación de valores de coma flotante y elimina por completo el redondeo de valores. El Listado 12 muestra cómo usar el módulo de fracciones.
Listado 12: Almacenamiento y comparación de valores de punto flotante como fracciones
Te puede interesar:Importaciones relativas vs absolutas en Python>>> import fractions
>>> fractions.Fraction(4, 10)
Fraction(2, 5)
>>> fractions.Fraction(6, 18)
Fraction(1, 3)
>>> fractions.Fraction(125)
Fraction(125, 1)
>>> a = fractions.Fraction(6, 18)
>>> b = fractions.Fraction(1, 3)
>>> a == b
True
Además, los dos módulos decimal
y fractions
se pueden combinar, como se muestra en el siguiente ejemplo.
Listado 13: Trabajar con decimales y fracciones
>>> import fractions
>>> import decimal
>>> a = fractions.Fraction(1,10)
>>> b = fractions.Fraction(decimal.Decimal(0.1))
>>> a,b
(Fraction(1, 10), Fraction(3602879701896397, 36028797018963968))
>>> a == b
False
Conclusión
Almacenar y procesar valores de punto flotante correctamente es una misión y requiere mucha atención por parte de los programadores. Redondear los valores puede ayudar, pero asegúrese de verificar el orden correcto de redondeo y el método que utiliza. Esto es más importante al desarrollar cosas como software financiero, por lo que querrá verificar las reglas de la ley local para redondear.
Python le brinda todas las herramientas necesarias y viene con «baterías incluidas». ¡Feliz piratería!