representa bien un número de coma flotante en pitón
-
27-09-2019 - |
Pregunta
Quiero representar un número de coma flotante como una cadena redondea a un número de dígitos significativos, y nunca con el formato exponencial. En esencia, quiero mostrar cualquier número de coma flotante y asegúrese de que “se ve bien”.
Hay varias partes a este problema:
- Tengo que ser capaz de especificar el número de dígitos significativos.
- El número de dígitos significativos tiene que ser variable, que no puede ser hecho con el formato de cadenas href="http://docs.python.org/library/stdtypes.html#string-formatting-operations" operador. [Editar] He sido corregidas; el operador de formato de cadenas puede hacer esto.
- lo necesito para ser redondeado la forma de una persona podría esperar, no es algo como 1.999999999999
He descubierto una manera de hacer esto, sin embargo se ve como un trabajo todo el año y no es del todo perfecto. (La precisión máxima es de 15 dígitos significativos.)
>>> def f(number, sigfig):
return ("%.15f" % (round(number, int(-1 * floor(log10(number)) + (sigfig - 1))))).rstrip("0").rstrip(".")
>>> print f(0.1, 1)
0.1
>>> print f(0.0000000000368568, 2)
0.000000000037
>>> print f(756867, 3)
757000
¿Hay una mejor manera de hacer esto? ¿Por qué no Python tiene una función incorporada para esto?
Solución
Parece que hay ningún encuentra incorporado el formato de cadenas de truco que le permite flotadores (1) de impresión cuyo primer dígito significativo aparece después de la 15ª posición decimal y (2) que no están en notación científica. Por lo que las hojas de manipulación de cadenas manual.
A continuación utilizo el módulo decimal
para extraer los dígitos decimales del flotador.
La función float_to_decimal
se utiliza para convertir el flotador a un objeto Decimal
. La forma más obvia decimal.Decimal(str(f))
está mal porque str(f)
puede perder dígitos significativos.
float_to_decimal
fue levantada de la decimal del módulo de documentación .
Una vez que los dígitos decimales se obtienen como una tupla de enteros, el código de abajo hace lo obvio: cortar el número deseado de dígitos sigificant, hasta ronda si es necesario, se unen los dígitos juntos en una cadena, tachuela en una muestra, colocar un punto decimal y ceros a la izquierda o la derecha, según corresponda.
En la parte inferior encontrará algunos casos he utilizado para probar la función f
.
import decimal
def float_to_decimal(f):
# http://docs.python.org/library/decimal.html#decimal-faq
"Convert a floating point number to a Decimal with no loss of information"
n, d = f.as_integer_ratio()
numerator, denominator = decimal.Decimal(n), decimal.Decimal(d)
ctx = decimal.Context(prec=60)
result = ctx.divide(numerator, denominator)
while ctx.flags[decimal.Inexact]:
ctx.flags[decimal.Inexact] = False
ctx.prec *= 2
result = ctx.divide(numerator, denominator)
return result
def f(number, sigfig):
# http://stackoverflow.com/questions/2663612/nicely-representing-a-floating-point-number-in-python/2663623#2663623
assert(sigfig>0)
try:
d=decimal.Decimal(number)
except TypeError:
d=float_to_decimal(float(number))
sign,digits,exponent=d.as_tuple()
if len(digits) < sigfig:
digits = list(digits)
digits.extend([0] * (sigfig - len(digits)))
shift=d.adjusted()
result=int(''.join(map(str,digits[:sigfig])))
# Round the result
if len(digits)>sigfig and digits[sigfig]>=5: result+=1
result=list(str(result))
# Rounding can change the length of result
# If so, adjust shift
shift+=len(result)-sigfig
# reset len of result to sigfig
result=result[:sigfig]
if shift >= sigfig-1:
# Tack more zeros on the end
result+=['0']*(shift-sigfig+1)
elif 0<=shift:
# Place the decimal point in between digits
result.insert(shift+1,'.')
else:
# Tack zeros on the front
assert(shift<0)
result=['0.']+['0']*(-shift-1)+result
if sign:
result.insert(0,'-')
return ''.join(result)
if __name__=='__main__':
tests=[
(0.1, 1, '0.1'),
(0.0000000000368568, 2,'0.000000000037'),
(0.00000000000000000000368568, 2,'0.0000000000000000000037'),
(756867, 3, '757000'),
(-756867, 3, '-757000'),
(-756867, 1, '-800000'),
(0.0999999999999,1,'0.1'),
(0.00999999999999,1,'0.01'),
(0.00999999999999,2,'0.010'),
(0.0099,2,'0.0099'),
(1.999999999999,1,'2'),
(1.999999999999,2,'2.0'),
(34500000000000000000000, 17, '34500000000000000000000'),
('34500000000000000000000', 17, '34500000000000000000000'),
(756867, 7, '756867.0'),
]
for number,sigfig,answer in tests:
try:
result=f(number,sigfig)
assert(result==answer)
print(result)
except AssertionError:
print('Error',number,sigfig,result,answer)
Otros consejos
Si desea precisión de coma flotante es necesario utilizar el módulo decimal
, que es parte de la Python estándar Biblioteca :
>>> import decimal
>>> d = decimal.Decimal('0.0000000000368568')
>>> print '%.15f' % d
0.000000000036857
Aquí hay un fragmento que los formatos de un valor de acuerdo a las barras de error dados.
from math import floor, log10, round
def sigfig3(v, errplus, errmin):
i = int(floor(-log10(max(errplus,errmin)) + 2))
if i > 0:
fmt = "%%.%df" % (i)
return "{%s}^{%s}_{%s}" % (fmt % v,fmt % errplus, fmt % errmin)
else:
return "{%d}^{%d}_{%d}" % (round(v, i),round(errplus, i), numpy.round(i))
Ejemplos:
5268685 (+1463262,-2401422) becomes 5300000 (+1500000,-2400000)
0.84312 +- 0.173124 becomes 0.84 +- 0.17
Se necesitan flotadores de precisión arbitraria para responder adecuadamente a esta pregunta. Por lo tanto, utilizando la decimales Módulo es una necesidad. No existe un método para convertir un decimal a una cadena sin tener que utilizar el formato exponencial (parte de la pregunta original), así que escribí una función para hacer precisamente eso:
def removeExponent(decimal):
digits = [str(n) for n in decimal.as_tuple().digits]
length = len(digits)
exponent = decimal.as_tuple().exponent
if length <= -1 * exponent:
zeros = -1 * exponent - length
digits[0:0] = ["0."] + ["0"] * zeros
elif 0 < -1 * exponent < length:
digits.insert(exponent, ".")
elif 0 <= exponent:
digits.extend(["0"] * exponent)
sign = []
if decimal.as_tuple().sign == 1:
sign = ["-"]
print "".join(sign + digits)
El problema es tratar de vuelta a cifras significativas. Decimal "cuantización ()" método no redondos más alto que el punto decimal, y el "() redonda" función siempre devuelve un flotador. No sé si estos son errores, sino que significa que la única forma de números de punto flotante de precisión infinita redondos es analizarlo como una lista o una cadena y hacer el redondeo de forma manual. En otras palabras, no hay cuerdo respuesta a esta pregunta.