2010-04-18 30 views
12

Voglio rappresentare un numero in virgola mobile come una stringa arrotondata a un numero di cifre significative e mai utilizzando il formato esponenziale. Essenzialmente, voglio visualizzare qualsiasi numero in virgola mobile e assicurarmi che sia “ bello ”.Rappresentazione piacevole di un numero in virgola mobile in python

Non ci sono più parti a 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 terminato con string formatting operator. [modifica] Sono stato corretto; l'operatore di formattazione delle stringhe può farlo.
  • ho bisogno di essere arrotondato il modo in cui una persona si aspetterebbe, non qualcosa come 1,999999999999

ho capito un modo per farlo, anche se sembra un lavoro tutto e non è abbastanza perfetto. (La massima precisione è 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 farlo? Perché Python non ha una funzione integrata per questo?

+0

ci sono delle lingue che fanno * * hanno una funzione built-in per questo? –

+3

Nel caso in cui: si conosca l'opzione di formattazione '%. * G'% (3, 3.142596)? (Soprattutto l'asterisco) – tzot

+0

Mi mancava l'opzione di formattazione dell'asterisco, tuttavia l'uso di "g" restituisce esponenti e l'uso di "f" è tutt'altro che perfetto: "%. * F"% (0, 34500000000000000000000) restituisce "34499999999999999000000" – dln385

risposta

8

Appare che non esiste un trucco di formattazione di stringa incorporato che consente di (1) stampare i caratteri float la cui prima cifra significativa appare dopo il 15 ° decimale e (2) non in notazione scientifica. In questo modo viene lasciata la manipolazione manuale delle stringhe.

Sotto utilizzo il modulo decimal per estrarre le cifre decimali dal float. La funzione float_to_decimal viene utilizzata per convertire il galleggiante in un oggetto Decimal. Il modo ovvio decimal.Decimal(str(f)) è errato perché str(f) può perdere cifre significative.

float_to_decimal è stato rimosso dallo decimal module's documentation.

Una volta ottenute le cifre decimali come una tupla di ints, il codice seguente fa la cosa ovvia: tagliare il numero desiderato di cifre significative, arrotondare se necessario, unire le cifre insieme in una stringa, virare su un segno , posizionare un punto decimale e zeri a sinistra o a destra, come appropriato.

Nella parte inferiore sono presenti alcuni casi 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) 
+0

Questo è un codice bellissimo. È esattamente quello che volevo. Se lo si modifica in modo che utilizzi un decimale anziché float (operazione banale da eseguire), è possibile evitare tutti gli errori in virgola mobile tutti insieme. L'unico problema che trovo è con numeri interi lunghi. Ad esempio, prova '(34500000000000000000000, 17, '34500000000000000000000')'. Anche se, di nuovo, questo è probabilmente solo da errori in virgola mobile. – dln385

+0

Buona presa. Ho apportato una leggera modifica alla definizione di 'd' che risolve il problema con i long ints, e come effetto collaterale, consente anche di passare stringhe numeriche anche per l'argomento' number' arg. – unutbu

+0

Questa correzione funziona, ma ho trovato altri errori non correlati. Usando '(756867, 7, '756867.0')', torno '75686.7'. Sembra che le condizioni per il riempimento del risultato siano un po 'spente. – dln385

6

se si vuole punto di precisione floating è necessario utilizzare il modulo decimal, che fa parte del Python Standard Library:

>>> import decimal 
>>> d = decimal.Decimal('0.0000000000368568') 
>>> print '%.15f' % d 
0.000000000036857 
+0

Il modulo decimale sembra che potrebbe aiutare, ma questo in realtà non risponde alla mia domanda. – dln385

+0

@ dln385: come mai? Soddisfa tutti i requisiti che hai elencato. –

+0

1. Specifica la precisione, cifre non significative. C'è una grande differenza. 2. Non oltrepasserà il punto decimale. Ad esempio, 756867 con 3 cifre significative è 757000. Vedere la domanda originale. 3. Questo metodo si rompe con grandi numeri, come i lunghi int. – dln385

-1

carri precisione arbitraria sono necessari per rispondere adeguatamente a questa domanda. Quindi usare il decimal module è 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 arrotondare a significativi figure.Il metodo "quantize()" decimale non sarà arrotondato più in alto del punto decimale e la funzione "round()" restituisce sempre un valore float. Non so se si tratta di bug, ma significa che l'unico modo per arrotondare infiniti numeri in virgola mobile di precisione è di analizzarli come elenco o stringa e eseguire manualmente l'arrotondamento. In altre parole, non esiste una risposta sana a questa domanda.

+0

Non hai bisogno di galleggianti di precisione arbitrari. Si noti che non annulla mai la risposta arrotondata come un galleggiante, solo come una stringa. BTW '-1 * anything' può essere scritto come' -nulla'. – Ponkadoodle

+0

Non capisco che il tuo "non giri più in alto del punto decimale". Prova: 'Decimal ('123.456'). Quantize (Decimal ('1e1'))', per esempio. –

+0

BTW, l'arrotondamento di un'istanza Decimale restituisce un altro decimale in Python 3.x. –

0

Ecco uno snippet che formatta un valore in base alle barre di errore fornite.

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 
Problemi correlati