Ben rappresenta un numero a virgola mobile in pitone
-
27-09-2019 - |
Domanda
voglio rappresentare un numero a virgola mobile come stringa arrotondato a qualche numero di cifre significative, e non utilizzando il formato esponenziale. In sostanza, voglio visualizzare qualsiasi numero in virgola mobile e assicurarsi che “sembra bello”.
Ci sono molte parti di questo problema:
- Ho bisogno di essere in grado di specificare il numero di cifre significative.
- Il numero di cifre significative deve essere variabile, che non può essere fatto con la href="http://docs.python.org/library/stdtypes.html#string-formatting-operations" rel="noreferrer"> formattazione della stringa . [Modifica] Sono stato corretto; l'operatore stringa di formattazione può fare questo.
- ho bisogno di essere arrotondato il modo in cui un persona si aspetterebbe, non qualcosa come 1,999999999999
Non ho capito un modo per farlo, anche se sembra un lavoro tutto e non è del tutto perfetto. (La precisione massima è di 15 cifre significative.)
>>> 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
C'è un modo migliore per fare questo? Perché Python non ha una funzione incorporata per questo?
Soluzione
Sembra non c'è è built-in di formattazione trucco che permette di galleggianti (1) Stampa la cui prima significativa cifre appare dopo il punto decimale e 15 (2), non in notazione scientifica. In modo che le foglie manuale di manipolazione di stringhe.
In basso uso del modulo decimal
per estrarre le cifre decimali dal galleggiante.
La funzione float_to_decimal
viene utilizzata per convertire il galleggiante a un oggetto Decimal
. Il modo più ovvio decimal.Decimal(str(f))
è sbagliato perché str(f)
può perdere cifre significative.
float_to_decimal
è stato revocato dalla decimale del modulo di documentazione .
Una volta che le cifre decimali sono ottenuti come una tupla di interi, il codice qui sotto fa la cosa più ovvia: recidere il numero desiderato di cifre sigificant, fino turno, se necessario, si uniscono le cifre insieme in una stringa, virare su un cartello, posizionare un punto decimale e zeri a sinistra oa destra come appropriato.
In fondo troverete alcuni casi ho usato per testare la funzione 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)
Altri suggerimenti
Se si vuole punto di precisione floating è necessario utilizzare il modulo decimal
, che fa parte del Python libreria standard :
>>> import decimal
>>> d = decimal.Decimal('0.0000000000368568')
>>> print '%.15f' % d
0.000000000036857
Ecco un frammento che i formati di un valore in base ai dati barre di errore.
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))
Esempi:
5268685 (+1463262,-2401422) becomes 5300000 (+1500000,-2400000)
0.84312 +- 0.173124 becomes 0.84 +- 0.17
carri precisione arbitraria sono necessari per rispondere adeguatamente a questa domanda. Pertanto utilizzando il decimale modulo è un must. Non esiste un metodo per convertire un numero decimale in una stringa senza mai usare il formato esponenziale (parte della domanda originale), così ho scritto una funzione per fare proprio questo:
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)
Il problema sta cercando di turno a cifre significative. di decimali "quantizzazione ()" metodo verrà non rotonda superiore al punto decimale e la funzione "round ()" restituisce sempre un galleggiante. Non so se questi sono insetti, ma significa che l'unico modo per numeri in virgola mobile infinita precisione rotonde è quello di analizzare come un elenco o stringa e fare dell'arrotondamento manualmente. In altre parole, non c'è una risposta sensata a questa domanda.