ich hab ein Etwas zu lösen. Ist rein privat und keine Hausaufgabe

Ich habe eine Software, die eingebaute sin(), pow(), erf(), etc.. Funktionen hat. Mit der Software habe ich früher Berechnungen durchgeführt. Altes Ding. Ich wollte das mit Python für mich nachbauen und laufe nun vollends gegen eine Wand!
Der Grund ist einerseits, dass große Zahlen in die Wertetabellen der Software hineinkopiert werden können, die Anwendung aber zur Präsentation irgendwann* Rundungen und Umformatierungen zu wissenschaftl. Darstellung vornimmt.
*irgendwann:
- positiv float 0.999... : ab der 13. Nachkommastelle (0.999999999999999, nächste NKS -> 1)
- negativ float -0.111... : ab der 15. NKS bleibt die zahl unverändert, ab der 18. NKS dann wisschftl. (-> -0.111111111111111, ab 18. NKS -> 1.11111111111111e+017)
- negativ float -0.999... : ab der 15. NKS wird gerundet (-> 15. NKS -0.999999999999999 -> 16. NKS -> -1)
- positiv float 0.001... : ab der 5. NKS dann wissenschaftlich. (0.01... 0.0001 -> ab der 5. NKS ->1e-005
Das ähnlich-Gleiche macht es dann auch auf der Ausgabeseite. Dort wird eine Spalte mit Werten ausgegeben, die ebenfalls ab bestimmten Zahlenwerten zu wissenschaftl. Darstellung konvertiert werden oder eben eine Rundung auf die nächste Ganzzahl erfahren. Ausserdem erfolgt eine Rundung auf die 5. signifikante Nachkommastelle. Und der Exponent ist immer drei-stellig. Und es ist wohl double-prec.
Ich habe die ganzen Berechnungs-Funktionen (mit ihren verrückten edges und overflows und pipapo) soweit nachgebaut. Zumindest denke ich das, weil meine Test-läufe an gleichen Datensätzen nahezu(!) gleiche Ergebnisse bringen. Nahezu, weil es bei großen pos/neg Exponenten Rundungen auftauchen, die ich nicht reproduzieren kann.
"Ok... das ist die float Ungenauigkeit/Fehler in der double-prec Arithmetik..". Ich blicks einfach nicht!
Die Software selbst ist in C++ geschrieben, ist von ungefähr 2008-2009 und verwendet die BOOST-Bib zur Serialisierung (und ich nehme dann auch an für den double-prec Datentyp den es auch irgendwie serialisieren muss). Zumindest kann ich das aus den save-Files herauslesen, die BOOST XML sind .
Durch austesten der Limits in der Software, bin ich der Meinung, dass aufjeden IEEE754-Standard double-floats benutzt werden:
Exponent-Max liegt bei 2**1024 (alles drunter geht) und der Exponent-Min hat seine Grenze bei > 2**(-1075), alles <= 2**(-1075) wird als NaN ausgegeben.
Ich bin noch am lernen, deswegen verzeiht mir meinen Code-Kauderwälsch.
Mein Problem ist, dass ich floats wohl doch nicht verstehe. Also, ich weiss was das ist, ich verstehe auch warum es zu Rundungsfehlern kommt. Aber ich kann es nicht nachprogrammieren bzw. hin/her-konvertieren:
Wenn ich in der Software pow(2, 999.9) berechnen lasse, dann wird mir ausgegeben:
pow(2, 999.9) -> 9.99753000000001e+300
Mach ich das in Python:
pow(2, 999.9) -> 9.997528812204251e+300
Wie bekomme ich in Python das gleiche Ergebnis wie in der Software?
Das komische Rundungsverhalten auf die 5. signifikante Stelle nach dem Komma und den 3-stelligen Exponent hab ich glaub ich irgendwie hingekriegt. Das mache ich mit dieser Funktion:
Code: Alles auswählen
def round_significant(x, p)
if not isinstance(x, str) and math.isfinite(x):
x = x.real # May be complex number comming in?
if 10**(-4) < abs(x) < 10**15:
x_decimal = decimal.Decimal(str(x))
significant_digits = p - x_decimal.adjusted() - 1
result_decimal = round((x_decimal), (significant_digits))
return int(result_decimal) if float(result_decimal).is_integer() else float(result_decimal)
elif x == 0:
return 0
else:
return np.format_float_scientific(x, unique=False, precision=p-1, exp_digits=3, trim="-")
Warum fällt der Wert auf 9.99753000000001e+300 ???? 9.99753e+300 ist selbst in Python mit float darstellbar:
Code: Alles auswählen
9.99753e+300 == float(str(9.99753e+300)) # True
Code: Alles auswählen
np.nextafter(9.99753e+300, np.inf) -> 9.99753000000001e+300
Code: Alles auswählen
pow(2, 1000) -> '1.07151e+301' -> 1.07151e+301
pow(2, 999.9) -> '9.99753e+300' -> 9.99753000000001e+300 (!)
pow(2, 999.8) -> '9.32802e+300' -> 9.32802000000001e+300 (!)
pow(2, 999.7) -> '8.70335e+300' -> 8.70335000000001e+300 (!)
pow(2, 999.6) -> '8.12052e+300' -> 8.12052000000001e+300 (!)
pow(2, 999.5) -> '7.57671e+300' -> 7.57671e+300
pow(2, 999.4) -> '7.06932e+300' -> 7.06932000000001e+300 (!)
pow(2, 999.3) -> '6.59591e+300' -> 6.59591e+300
pow(2, 999.2) -> '6.1542e+300' -> 6.1542e+300
pow(2, 999.1) -> '5.74207e+300' -> 5.74207e+300
... meine Funktion round_significant(x, p) macht anscheinend was sie soll, aber warum gibt die Software z.B. 9.99753000000001e+300 für pow(2, 999.9) aus und 7.57671e+300 für pow(2, 999.5) und wie kann ich das nachreproduzieren? Ich verzweifle.
Wie gesagt, dies passiert so ab abs(exp) > 200. Bei kleineren Zahlen scheinen keine Probleme aufzutauchen und meine Testläufe mit verschachtelten Funktionen wie:
mod(x, sin(pow(2, x))) mit x[2000, -2000] zeigen die gleichen (gerundeten) Werte, wie sie die Software auch zeigt ausser eben bei großen +/- x. Daher nehme ich an, die Berechnung funktioniert mit Python-Floats. Decimal taucht momentan nur in der round_significant() Funktion auf, wobei ich das gerne loswerden würde.
Hat jemand eine Idee oder kann dieses Verhalten vielleicht sogar jemand nachreproduzieren??
Danke sehr für jeglichen Input der mich auf die richtige Schiene bringt.