Cuando convierte un valor float
a Decimal
, el Decimal
contendrá una representación del número binario que pueda. Es bueno ser preciso, pero no siempre es lo que quieres. Como muchos números decimales no se pueden representar exactamente en binario, el Decimal
resultante estará un poco apagado, a veces un poco alto, a veces un poco bajo.
>>> from decimal import Decimal >>> for f in (0.1, 0.3, 1e25, 1e28, 1.0000000000001): print Decimal(f) 0.1000000000000000055511151231257827021181583404541015625 0.299999999999999988897769753748434595763683319091796875 10000000000000000905969664 9999999999999999583119736832 1.000000000000099920072216264088638126850128173828125
Lo ideal sería que el Decimal
se redondeara al equivalente decimal más probable.
Intenté convertir a str
ya que un Decimal
creado a partir de una cadena será exacto. Desafortunadamente, las rondas son demasiado largas.
>>> for f in (0.1, 0.3, 1e25, 1e28, 1.0000000000001): print Decimal(str(f)) 0.1 0.3 1E+25 1E+28 1.0
¿Hay una manera de obtener un Decimal
bien redondeado de un flotador?
Resulta que repr
hace un mejor trabajo de convertir un float
en una cadena que str
. Es la forma rápida y fácil de hacer la conversión.
>>> for f in (0.1, 0.3, 1e25, 1e28, 1.0000000000001): print Decimal(repr(f)) 0.1 0.3 1E+25 1E+28 1.0000000000001
Antes de que descubriera eso, se me ocurrió una forma de fuerza bruta para hacer el redondeo. Tiene la ventaja de reconocer que los números grandes tienen una precisión de 15 dígitos: el método de repr
anterior solo reconoce un dígito significativo para los ejemplos 1e25 y 1e28.
from decimal import Decimal,DecimalTuple def _increment(digits, exponent): new_digits = [0] + list(digits) new_digits[-1] += 1 for i in range(len(new_digits)-1, 0, -1): if new_digits[i] > 9: new_digits[i] -= 10 new_digits[i-1] += 1 if new_digits[0]: return tuple(new_digits[:-1]), exponent + 1 return tuple(new_digits[1:]), exponent def nearest_decimal(f): sign, digits, exponent = Decimal(f).as_tuple() if len(digits) > 15: round_up = digits[15] >= 5 exponent += len(digits) - 15 digits = digits[:15] if round_up: digits, exponent = _increment(digits, exponent) while digits and digits[-1] == 0 and exponent < 0: digits = digits[:-1] exponent += 1 return Decimal(DecimalTuple(sign, digits, exponent)) >>> for f in (0.1, 0.3, 1e25, 1e28, 1.0000000000001): print nearest_decimal(f) 0.1 0.3 1.00000000000000E+25 1.00000000000000E+28 1.0000000000001
Edición: descubrí una razón más para usar el redondeo de fuerza bruta. repr
intenta devolver una cadena que identifique de forma única la representación subyacente float
bit float
, pero no necesariamente garantiza la precisión del último dígito. Al usar un dígito menos, mi función de redondeo será más a menudo el número que usted esperaría.
>>> print Decimal(repr(2.0/3.0)) 0.6666666666666666 >>> print dec.nearest_decimal(2.0/3.0) 0.666666666666667
El decimal creado con repr
es en realidad más preciso, pero implica un nivel de precisión que no existe. La función más nearest_decimal
ofrece una mejor correspondencia entre precisión y precisión.
He implementado esto en Pharo Smalltalk, en un método Float
llamado asMinimalDecimalFraction
.
Es exactamente el mismo problema que imprimir la fracción decimal más corta que se volvería a interpretar como la misma flotación / doble, asumiendo el redondeo correcto (al más cercano).
¿Ver mi respuesta en Número de dígitos del recuento después de `.` en números de punto flotante? para mas referencias