2010-04-18 6 views
12

Ich möchte eine Fließkommazahl als eine Zeichenfolge auf eine ganze Reihe von signifikanten Ziffern gerundet darstellen, und nie das exponentielle Format verwenden. Im Wesentlichen möchte ich irgendeine Fließkommazahl anzeigen und sicherstellen, dass sie “ gut aussieht ”.Schön eine Fließkommazahl in Python

Es gibt mehrere Teile für dieses Problem:

  • Ich brauche die Anzahl signifikanter Stellen angeben zu können.
  • Die Anzahl der signifikanten Stellen variabel sein muss, die nicht mit dem string formatting operator getan werden kann. [Bearbeiten] Ich wurde korrigiert; Der Operator für die Zeichenfolgenformatierung kann dies tun.
  • Ich brauche es die Art und Weise eine Person etwas erwarten würde, nicht wie 1,999999999999

gerundet wird ich eine Möglichkeit, dies zu tun, herausgefunden habe, obwohl es wie ein Werk der ganzen aussieht und es ist nicht ziemlich perfekt. (Die maximale Genauigkeit beträgt 15 signifikante Stellen.)

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

Gibt es einen besseren Weg, dies zu tun? Warum hat Python keine eingebaute Funktion dafür?

+0

Gibt es Sprachen, die * * eine eingebaute Funktion für diese haben Sie? –

+3

Nur für den Fall: Sie kennen die Formatierungsoption '%. * G'% (3, 3.142596)? (Vor allem der Stern) – tzot

+0

Ich habe die Formatierung Option Sternchen vermisst, aber mit 'g' gibt Exponenten und die Verwendung von 'f' ist bei weitem nicht perfekt: "%. * F"% (0, 3450000000000000000000000) gibt '34499999999999999000000' – dln385

Antwort

8

Es scheint, dass es keinen eingebauten String-Formatierungstrick gibt, mit dem Sie (1) Floats drucken können, deren erste signifikante Ziffer nach der 15. Dezimalstelle und (2) nicht in wissenschaftlicher Notation erscheint. So bleibt manuelle String-Manipulation.

Unten verwende ich das Modul decimal, um die Dezimalziffern aus dem Float zu extrahieren. Die float_to_decimal-Funktion wird verwendet, um den Float in ein Decimal Objekt zu konvertieren. Der offensichtliche Weg decimal.Decimal(str(f)) ist falsch, weil str(f) signifikante Ziffern verlieren kann.

float_to_decimal wurde von der decimal module's documentation aufgehoben.

Sobald die Dezimalziffern als Tupel von Ints erhalten werden, macht der folgende Code das Offensichtliche: hacken Sie die gewünschte Anzahl signifikanter Stellen ab, runden Sie falls nötig zusammen, verbinden Sie die Ziffern zu einem String, heften Sie ein Zeichen an , platzieren Sie je nach Bedarf einen Dezimalpunkt und Nullen nach links oder rechts.

Unten finden Sie ein paar Fälle, die ich verwendet habe, um die f Funktion zu testen.

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

Das ist ein schöner Code. Es ist genau das, was ich wollte. Wenn Sie es so ändern, dass es anstelle von float ein Dezimum benötigt (was trivial ist), können wir Fließkommafehler alle zusammen vermeiden. Das einzige Problem, das ich finde, ist mit langen ganzen Zahlen. Probieren Sie zum Beispiel '(34500000000000000000000, 17, '34500000000000000000000')' '. Auch dies ist wahrscheinlich nur von Gleitkommafehlern. – dln385

+0

Guter Fang. Ich habe eine kleine Änderung an der Definition von "d" vorgenommen, die das Problem mit langen Ints behebt, und als Nebeneffekt können Sie auch numerische Zeichenfolgen für das Argument "number" übergeben. – unutbu

+0

Dieser Fix funktioniert, aber ich habe andere, nicht zusammenhängende Fehler gefunden. Mit '(756867, 7, '756867.0')', bekomme ich '75686.7' zurück. Es scheint, die Bedingungen für das Auffüllen des Ergebnisses sind ein wenig aus. – dln385

6

Wenn Sie punktgenau wollen schwimmend müssen Sie das decimal Modul verwenden, die einen Teil der Python Standard Library ist:

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

Das Dezimal-Modul sieht aus wie es helfen könnte, aber das beantwortet meine Frage wirklich nicht. – dln385

+0

@ dln385: Wie so? Es erfüllt alle von Ihnen aufgeführten Anforderungen. –

+0

1. Das spezifiziert Genauigkeit, nicht signifikante Ziffern. Es gibt einen großen Unterschied. 2. Es wird nicht über den Dezimalpunkt hinaus gerundet. Beispiel: 756867 mit 3 signifikanten Ziffern ist 757000. Siehe die ursprüngliche Frage. 3. Diese Methode bricht mit großen Zahlen wie Long-Ints zusammen. – dln385

-1

beliebige Genauigkeit Schwimmer ist notwendig, um richtig zu beantworten diese Frage. Daher ist die Verwendung der decimal module ein Muss. Es gibt keine Methode eine Dezimalzahl in eine Zeichenfolge zu konvertieren, ohne jemals das exponentielle Format (ein Teil der ursprünglichen Frage) verwendet wird, so schrieb ich eine Funktion genau das zu tun:

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) 

Das Problem versucht zu erheblichen abzurunden Zahlen.Die "quantise()" -Methode von Decimal wird nicht höher als der Dezimalpunkt gerundet, und die Funktion "round()" gibt immer einen Gleitkommawert zurück. Ich weiß nicht, ob das Bugs sind, aber es bedeutet, dass der einzige Weg, unendliche Präzisions-Gleitkommazahlen zu runden, darin besteht, es als Liste oder String zu analysieren und die Rundung manuell durchzuführen. Mit anderen Worten, es gibt keine vernünftige Antwort auf diese Frage.

+0

Sie benötigen keine willkürlichen Präzisionsschwimmer. Beachten Sie, dass er die runde Antwort niemals als Float, sondern nur als String ansieht. BTW '-1 * anything' kann als '-anything' geschrieben werden. – Ponkadoodle

+0

Ich verstehe nicht, dass Ihr "nicht mehr als den Dezimalpunkt runden wird". Probieren Sie: Decimal ('123.456') .quantize (Dezimal ('1e1')) 'zum Beispiel. –

+0

BTW, Runden einer Dezimal-Instanz gibt ein anderes Dezimal in Python 3.x. –

0

Hier ist ein Ausschnitt, der einen Wert entsprechend den angegebenen Fehlerbalken formatiert.

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)) 

Beispiele:

5268685 (+1463262,-2401422) becomes 5300000 (+1500000,-2400000) 
0.84312 +- 0.173124 becomes 0.84 +- 0.17