wake-up-neo.com

Python-Gleitkommazahl beliebige Genauigkeit verfügbar?

Nur zum Spaß und weil es wirklich einfach war, habe ich ein kurzes Programm geschrieben, um Grafting-Nummern zu generieren, aber aufgrund von Gleitkomma-Präzisionsproblemen werden einige der größeren Beispiele nicht gefunden.

def isGrafting(a):
  for i in xrange(1, int(ceil(log10(a))) + 2):
    if a == floor((sqrt(a) * 10**(i-1)) % 10**int(ceil(log10(a)))):
      return 1

a = 0
while(1):
  if (isGrafting(a)):
    print "%d %.15f" % (a, sqrt(a))
  a += 1

Bei diesem Code fehlt mindestens eine bekannte Pfropfnummer. 9999999998 => 99999.99998999999999949999999994999999999374999999912... Nach dem Multiplizieren mit 10**5 scheint die Genauigkeit zu fallen.

>>> a = 9999999998
>>> sqrt(a)
99999.99999
>>> a == floor((sqrt(a) * 10**(5)) % 10**int(ceil(log10(a))))
False
>>> floor((sqrt(a) * 10**(5)) % 10**int(ceil(log10(a))))
9999999999.0
>>> print "%.15f" % sqrt(a)
99999.999989999996615
>>> print "%.15f" % (sqrt(a) * 10**5)
9999999999.000000000000000

Also habe ich ein kurzes C++ - Programm geschrieben, um zu sehen, ob meine CPU die Gleitkommazahl oder den Python irgendwie gekürzt hat.

#include <cstdio>
#include <cmath>
#include <stdint.h>

int main()
{
  uint64_t a = 9999999998;
  printf("%ld %.15f %.15f %.15f %.15f\n", a, sqrt((double)a), sqrt((double)a)*1e4, sqrt((double)a)*1e5, sqrt((double)a)*1e6);
  a = 999999999998;
  printf("%ld %.15f %.15f %.15f %.15f\n", a, sqrt((double)a), sqrt((double)a)*1e5, sqrt((double)a)*1e6, sqrt((double)a)*1e7);
  a = 99999999999998;
  printf("%ld %.15f %.15f %.15f %.15f\n", a, sqrt((double)a), sqrt((double)a)*1e6, sqrt((double)a)*1e7, sqrt((double)a)*1e8);
  return 0;
}

Welche Ausgänge:

9999999998 99999.999989999996615 999999999.899999976158142 9999999999.000000000000000 99999999990.000000000000000
999999999998 999999.999998999992386 99999999999.899993896484375 999999999999.000000000000000 9999999999990.000000000000000
99999999999998 9999999.999999899417162 9999999999999.900390625000000 99999999999999.000000000000000 999999999999990.000000000000000

Es sieht also so aus, als würde ich hart gegen die Grenzen der Fließkomma-Genauigkeit laufen und die CPU hebt die verbleibenden Bits ab, weil sie der Meinung ist, dass der verbleibende Unterschied ein Fließkomma-Fehler ist. Gibt es eine Möglichkeit, dies unter Python zu umgehen? Oder muss ich zu C wechseln und GMP oder etwas anderes verwenden?

39

In der Standardbibliothek ist möglicherweise das Modul decimal das, was Sie suchen. Ich habe auch gefunden, dass mpmath ziemlich hilfreich ist. Die Dokumentation hat auch viele gute Beispiele (leider ist auf meinem Bürocomputer mpmath nicht installiert; ansonsten würde ich einige Beispiele überprüfen und veröffentlichen).

Ein Vorbehalt bezüglich des Moduls decimal . Das Modul enthält mehrere eingebaute Funktionen für einfache mathematische Operationen (z. B. sqrt), aber die Ergebnisse dieser Funktionen stimmen möglicherweise nicht immer mit der entsprechenden Funktion in math oder anderen Modulen mit höherer Genauigkeit überein (obwohl sie möglicherweise genauer sind). Zum Beispiel, 

from decimal import *
import math

getcontext().prec = 30
num = Decimal(1) / Decimal(7)

print("   math.sqrt: {0}".format(Decimal(math.sqrt(num))))
print("decimal.sqrt: {0}".format(num.sqrt()))

In Python 3.2.3 gibt dies die ersten beiden Zeilen aus

   math.sqrt: 0.37796447300922719758631274089566431939601898193359375
decimal.sqrt: 0.377964473009227227214516536234
actual value: 0.3779644730092272272145165362341800608157513118689214

was, wie gesagt, nicht genau das ist, was Sie erwarten würden, und Sie können sehen, dass je höher die Genauigkeit ist, desto weniger passen die Ergebnisse zusammen. Beachten Sie, dass das Modul decimal in diesem Beispiel eine höhere Genauigkeit aufweist, da es dem actual value besser entspricht. 

37

Für dieses spezielle Problem ist decimal eine großartige Möglichkeit, da die Dezimalstellen als Tupel gespeichert werden. 

>>> a = decimal.Decimal(9999999998)
>>> a.as_Tuple()
DecimalTuple(sign=0, digits=(9, 9, 9, 9, 9, 9, 9, 9, 9, 8), exponent=0)

Da Sie nach einer Eigenschaft suchen, die ganz natürlich in Dezimalschreibweise ausgedrückt wird, ist es ein bisschen dumm, eine binäre Darstellung zu verwenden. Die Wikipedia-Seite, mit der Sie verlinkt sind, hat nicht angegeben, wie viele "Nicht-Pfropfungs-Ziffern" möglicherweise erscheinen, bevor die "Pfropfungs-Ziffern" beginnen. Daher können Sie Folgendes angeben:

>>> def isGrafting(dec, max_offset=5):
...     dec_digits = dec.as_Tuple().digits
...     sqrt_digits = dec.sqrt().as_Tuple().digits
...     windows = [sqrt_digits[o:o + len(dec_digits)] for o in range(max_offset)]
...     return dec_digits in windows
... 
>>> isGrafting(decimal.Decimal(9999999998))
True
>>> isGrafting(decimal.Decimal(77))
True

Ich denke, es gibt eine gute Chance, dass das Ergebnis von Decimal.sqrt() zumindest dafür genauer ist als das Ergebnis von math.sqrt(), da zwischen der binären Darstellung und der dezimalen Darstellung konvertiert wird. Betrachten Sie zum Beispiel Folgendes: 

>>> num = decimal.Decimal(1) / decimal.Decimal(7)
>>> decimal.Decimal(math.sqrt(num) ** 2) * 7
Decimal('0.9999999999999997501998194593')
>>> decimal.Decimal(num.sqrt() ** 2) * 7
Decimal('1.000000000000000000000000000')
8
senderle

Sie können es mit Decimal anstelle von Fließkommazahlen versuchen.

7
f p

Python hat keine Floats mit beliebiger Genauigkeit, aber es gibt Python-Pakete von Drittanbietern, die GMP verwenden: gmpy und PyGMP .

5
Ned Batchelder

verwenden Sie decimal (hier ist ein klareres Beispiel):

>>> 2.3-2.2
0.09999999999999964
>>> from decimal import Decimal
>>> Decimal('2.3')-Decimal('2.2')
Decimal('0.1')
>>> float(Decimal('2.3')-Decimal('2.2'))
0.1
>>> 
0
U9-Forward