Convertir flotante a equivalente decimal redondeado

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