Seite 1 von 2

Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Montag 17. Juni 2024, 09:42
von tony_pythony
Hallo Zusammen,

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="-")
Was ich nicht verstehe:
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
Warum also der Sprung auf das nächste Float:

Code: Alles auswählen

np.nextafter(9.99753e+300, np.inf) -> 9.99753000000001e+300
Ich verstehe nicht. Hier ist das Verhalten für ein paar weitere Zahlen:

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.

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Montag 17. Juni 2024, 10:27
von Sirius3
Das Problem ist ja kein Python-Problem. Das korrekte Ergebnis ist 9.997528812204408592154943746...E+300 und da ist die Python-Rechnung (und damit alle IEEE754-kompatiblen Rechnungen) näher dran.
Du müßtest also genau nachvollziehen, was Dein anderes Programm da anders macht. Ich würde mal versuchen, dass die Berechnung pow in Deinem anderen Programm fehlerhaft, bzw. ungenau ist. Wenn Du also genau die selben Werte möchtest, müßtest Du exakt die pow-Implementierung in Python nachprogrammieren.

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Montag 17. Juni 2024, 11:40
von tony_pythony
Die pow(x, y) Funktion habe ich nachprorammiert. Da ging es hauptsächlich um die Grenzfallbehandlung der Inf/Nan und bisschen was anderes noch dazu. Diese ist aber nicht die Ursache. Ich denke das liegt an der Rundung für die Darstellung der Ausgabe..
Zu diesem Schluss komme ich, weil dieses Verhalten auch dann gezeigt wird, wenn in der Eingabe oder einfach in der Berechnung statt einer Funktion, eine zahl wie 1e-225 eingebe. Dort wird diese zu 9.99999999999999e-226
umformatiert.
Die Internet-Recherche sagt mir, dass 1e-225 wohl im double-float IEEE754 nicht direkt abgebildet werden kann. Die Software geht intern dann wohl zur nächsten darstellbaren float-zahl (1e-225 - epsilon) über.
Python kann das bis zur bestimmen precision aber problemlos abbilden. Wenn die precision überschritten wird, dann wird es mit round_to_next_near (wahrscheinlich wieder in epsilon schritten) gerundet.
Python ist hier also "zu genau".

Wenn ich deine Zahl 9.997528812204408592154943746...E+300 anschaue, dann überkommt mich das Gefühl, dass die Software (Annahme!) zwar intern mit Decimal oder eben mit der vollen IEEE754 Genauigkeit rechnet (rechnen könnte), dann aber zur Darstellung und einem Kopieren & Weiterverwenden (markieren -> strg+c) den Wert so verändert, dass die precision geringer wird, der "full-precision" float dann auf die geringere precision wie auch immer gerundet, gekappt oder sonstwie gestutzt wird, und diese "gestutzte" Zahl nicht mehr exakt im float darstellbar ist. Und dann folgt eben nextafter(x, +inf/-inf).

Das komische ist halt, was passiert denn bei der Darstellung eines sehr großen oder auch sehr kleinen Werts??
Sagen wir, die Darstellungs-funktion der Software bekommt 9.997528812204408592154943746...E+300 eingefuttert.
Damit 9.99753000000001e+300 daraus wird, müsste doch:
1. 9.997528812204408592154943746...E+300 auf die 5. Nachkommastelle gerundet werden
# 9.9975288... -> 9.99753
2. *Magic*
3. Resultat der Magie -> 9.99753000000001e+300

Beim Schritt 2 könnte ich mir die Verringerung der precision der Original-zahl vorstellen. Sagen wir mal, das wird einfach an der Nachkommastelle 20 abgeschnitten:
x = 9.9975288122044085921, Exponent +300
Nun könnte hier etwas passieren ala,
if darstellbar_im_float(x):
round_significant(x, 5)
else:
nextafter(round_significant(x, 5), 5)

... Also ich denke, der rechnet intern mit hoher precision, fällt dann aber auf irgendwas zurück, was die Copy&Paste Funktionalität unterstützt. Es macht zumindest für die Software anscheinend keinen Sinn riesige Zahlen zu kopieren, weil auch mit relativ kurzen wissenschaftlichen Darstellungen gearbeitet werden kann oder Ganzzahlen bis 12 Stellen "ausreichend" sind...

mein Kopf... :lol:

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Montag 17. Juni 2024, 12:10
von __blackjack__
Anmerkungen zur `round_significant()`-Funktion: Die sollte nicht implizit `None` liefern. Und vielleicht auch nicht wenn eine Zeichenkette übergeben wurde. Aus sollte die nicht mal Zahlen und mal Zeichenketten als Ergebnis haben.

Die `10**n`-Werte kann man als literale Zahl `1en` schreiben.

Ungetestet:

Code: Alles auswählen

def round_significant(number, precision):
    if math.isfinite(number):
        number = number.real  # May be complex number comming in?
        if 1e-4 < abs(number) < 1e15:
            number_decimal = decimal.Decimal(str(number))
            result_decimal = round(
                number_decimal, precision - number_decimal.adjusted() - 1
            )
            return str(
                float(result_decimal)
                if result_decimal.as_tuple().exponent < 0
                else result_decimal
            )
        elif number == 0:
            return "0"
        else:
            return np.format_float_scientific(
                number,
                unique=False,
                precision=precision - 1,
                exp_digits=3,
                trim="-",
            )
    else:
        return None
CPython's `float` verwendet intern C ``double`` als Datentyp, und das sollte in der Regel „IEEE754 double“ sein. Also das IEEE754 etwas nicht darstellen kann was Python dann aber kann, ist eher ausgeschlossen.

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Montag 17. Juni 2024, 12:13
von Sirius3
Wenn DEINE Software auf 6 signifikante Stellen rundet, dann macht das Deine Software, IEEE754 hat 15 signifikante Stellen, und das ist das, was Python macht. Und auch wenn es viele Rundungen gibt, es bleibt bei deutlich mehr als 6 Stellen Genauigkeit.
Was willst Du konkret rechnen? Welche Mathematik steckt dahinter? Du mußt ja wissen, was das exakte Ergebnis sein soll, um sagen zu können, ob die Rundungsfehler relevant sind, oder nicht.

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Montag 17. Juni 2024, 19:54
von DeaD_EyE
Gleitkommazahlen können nicht zaubern.
Mathematiker stellen gerne Aufgaben, die man mit einem Computer niemals berechnen kann.
Entweder es ist ungenau aufgrund der Gleitkommazahlen oder der RAM reicht für die Ganzzahl nicht aus.

Code: Alles auswählen

import math
import sys

v1 = sys.float_info.max
v2 = sys.float_info.max + sys.float_info.min


print(math.isclose(v1, v2))
print(v1.hex())
print(v2.hex())
Runden ist nochmal ein Thema für sich.
So ziemlich jede Programmiersprache nutzt das wissenschaftliche Runden.

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Donnerstag 20. Juni 2024, 15:54
von tony_pythony
ja das ist ja das Problem.
Python hält sich an den Standard. Die Software sollte es auch. Zumindest ist es das was ich so sehe, wie es werkelt:
Es ist ein "mathematik" Programm, das math. Ausdrücke aus einer Kurvenanpassung generiert. Relativ Performant und mit Multicore. Intern ist es ein 64-bit und irgendein Zip-gepacktes Executable. Es benutzt keine Lokalisierung. Auch werden die Standard-Rechenmethoden der Mathematik ordentlich umgesetzt - mit Grenzwerten, die die Software strikt inne hält. Da geht nichts über die double-Precision hinaus, wenn ich mit Zahlen spiele die größer sind, als das was im double float dargestellt werden kann. Auch, wenn ich irgendwelche Problem-Formeln aus dem Internet dort einfüttere, kommt das "richtige" Ergebnis heraus (zum Beispiel 1.2 - 0.9 = 0.3. Python: 0.29999999999999993...). Ich versuche dieses Verhalten mit Python nachzubilden.

An dem Beispiel "1.2 - 0.9":
Ist ein bekanntes Float-Problem. Mit Dezimalzahlen wird eine Wurst draus.

Code: Alles auswählen

import decimal
print(decimal.Decimal('1.2') - decimal.Decimal('0.9'))    # returns Decimal('0.3'), ok
print(float(decimal.Decimal('1.2') - decimal.Decimal('0.9')))   # convert to float, returns 0.3, ok
Die Funktionen habe ich alle bereits implementiert. Die Testläufe zeigen 100% Übereinstimmung, auch mit nan's und inf's in den Bereichen x < 10^200 | x > 10^-200. Darüber hinaus gibt es dann die sporadischen Rundungsfehler, die ziemlich nach Float Rounding aussehen. Bei den x > 10^200 verstehe ich es ja, Aber die 0 < x < 10^-200 lösen doch im double float superb auf!

Aber was macht das Programm?
1. Es erhält irgendwelche wohlformatierten Zahlenreihen (Ganz- und Kommazahlen mit Punkt, wissenschaftliche in e/E Schreibweise, + Sonderdinger siehe unten)
2. Davon ausgehend, dass Eingaben geprüft werden müssen, läuft da irgendein Parser drüber und filtert alles was nicht eine interne Funktion, andere Variable (benannte Spalte im Datensatz), eine Zahl, ein Datum (im bestimmten Format) oder Key-Values (als Klassifizierungen für Statistik oder so), oder math. Operator /parentheses, ist. Und ich denke hier liegt auch die Ursache für das komische Runden.
3. Nachdem Bereinigen werden die Daten dann Excel-ähnlich tabellarisch dargestellt. Können ab hier auch wieder kopiert und zum Beispiel Excel, Numpy Dataframe - überall - verwendet werden. Daraus schließe ich, die Arbeitsdaten sind ok und Standard.

Zu dem Parser:
Meine Beobachtung ist mit Zahlenreihen, dass Zahlen bis zur einer bestimmten Stellenanzahl genau so dargestellt werden, wie sie ankommen. Wird eine bestimmte Stellenanzahl überschritten, dann erfolgt die Konvertierung zum Wissenschaftlichen Format. Für Zahlen 0 < x < 1 geschieht dies früher als für Zahlen x=0 | x > 1.
Da jegliche reele Zahl hineingefüttert werden kann, aber nicht jede im double float dargestellt werden kann, erfolgt dann die interne Umwandlung zum Float mit double precision. Die zu Floats umgewandelten Werte werden dann für die Berechnungen genutzt. Und zwar so, wie sie angezeigt werden.

=> Diese Konvertierung muss das Problem sein. Es ist also das Konvertieren, das irgendwas strikt nach mathematischen Regeln macht, was Python dann aber intern anders handha(b)t (=> round(2.5) # 2, erwartet 3)

Dass die Werte zu Floats umgewandelt werden und nicht etwa als literals/decimals verwendet werden, sehe ich hier mit:

Code: Alles auswählen

import decimal
rows = [999.9, 999.8, 999.7, 999.6, 999.4, 999.5, 999.3, 999.2, 999.1]
for el in rows:
    print(el)
    dec_pow_ = pow(decimal.Decimal('2'), decimal.Decimal(str(el)))
    flt_pow = float(pow(2, el))
    print('flt_pow', flt_pow)
    print('dec_pow', dec_pow)
    print('flt_dec', float(dec_pow))
##
999.9
flt_pow 9.997528812204251e+300
dec_pow 5.7420724503073328E+300
flt_dec 5.742072450307333e+300
999.8
flt_pow 9.32802421562621e+300
dec_pow 5.7420724503073328E+300
flt_dec 5.742072450307333e+300
999.7
flt_pow 8.703354339034053e+300
dec_pow 5.7420724503073328E+300
flt_dec 5.742072450307333e+300
...
...
Die mit floats durchgeführte pow(2, float) Berechnung liefert die Vorkomma- und, bis zur 5 Nachkommastelle (rundung auf die signifikante 5 Nachkommastelle), auch die Nachkommazahlen übereinstimmend. Die mit decimalen durchgeführte Berechnung, und auch die folgende float Umwandlung derer, zeigt landläufig als "das Richtigere" angesehene an. Was aber ein Unterschied zu den Sollwerten ist. Deswegen kann geschlossen werden, dass die Software dann intern auch nur mit floats und nicht mit deci's arbeitet. Da geht es nicht um die Exaktheit :lol:

Die in der Software durchgeführten Berechnungen können ebenfalls auch zu irgendwelchen Zahlen führen, die aber dann nicht darstellbar sind, weil intern nur floats verwendet werden. Ich nehme an, wenn die Daten floats sind, die Funktionen mit floats arbeiten, wird zwischenschrittlich ebenfalls float verwendet und auch werden floats verschachtelt an die Funktionen übergeben. Zumindest legt mir das meine Beobachtung nahe, dass meine Funktionen in Python, durchwegs alle mit floats und dem modul math umgesetzt, gleiche (auf die 5. Stelle gerundete) Werte liefern.

Ich bin mir nicht 100% im Klaren, ob ich nur die gleichen gerundeten Werte sehe und zu dem Schluss komme "meins ist das gleiche, wie die Software". Aber selbst wenn ich zum beispiel die pow(x, y) mehrmals verschachtele so wie pow(pow(2, pow(2, 4)), 45) kommt das gleiche Ergebnis heraus. Das heisst, eigentlich alles ok mit der Berechnung. Nur der Anfang und das Ende, also das allererste "sehen" der Daten (Umwandlung, keine Rundung auf die 5.te) und die Ausgabe (eventuelle Umwandlung, Rundung auf die signifikante 5.te) sind das, wo das Verhalten, das ich nachbauen möchte, auftaucht. Ansonsten, wenn das Resultat keine Umwandlungsprobleme macht, dann funktioniert meine Rundungs-funktion round_signifikant(x, p) in Python genau so, wie es die Software macht.

Wenn ich zum Bleistift in der Software mache:
a = 1e-217 => 9.99999999999999e-218 | DBL_FLT* und Rundung <- ich verstehen & machen will
equal(a, a) => 1 | True
equal(9.99999999999999e-218, 9.99999999999999e-218) => 1 | True
equal(a, 1e-217) => 0 | False
equal(9.99999999999999e-218, 1e217) => 0 | False
equal(9.999999999999999e-218, 1e-217) => 1 | True
equal(9.99999999999999e-218, a) => 0 | False Whyyyyyyy
equal(9.999999999999999e-218, a) => 0 | False :shock: Whyyyyyyy

Ich glaub da ist noch mehr buggy ... :D :lol:

*DBL_FLT.
die 999999... ist ein Round-Off Verhalten des floats. 1e-217 liegt wohl der Meinung des Parsers nach nicht im double-Precision@(exp "-720", base 2)-Bereich liegt bzw. ist der allererste/allerletzte Wert im Intervall des Exp -720 base2 [1.8130221999122200E-217, 9.0651109995611200E-218]. Genau das möchte ich verstehen. Warum denkt der, der wert pow(10, -217) ist nicht darstellbar, sodass dieser round-off gesetzt wird???

Python:

Code: Alles auswählen

a = pow(10, -217)  # 1e-217   base_10
b = math.log2(a)   # -720.8583965905576    @exponent base_2
c = pow(2, b)          # 1.0000000000000081e-217  fast genau 1e-217

## Resolution / Step-Size / Epsilon@-720
epsilon_at_exponent = abs(((pow(2,-720)/2)-pow(2,-720))/pow(2, 52))    # 2.0128589904991042e-233
Das müsste doch reichen :lol:
Desweiteren, wenn ich Python frage, was nextafter(1e-217, +inf) und nextafter(1e-217, -inf) ist:

Code: Alles auswählen

# als zahl übergeben, interne konvertierung zu float in python
np.nextafter(1e-217, np.inf)    # 1.0000000000000003e-217
np.nextafter(1e-217, -np.inf)   # 9.999999999999999e-218     <- A-HA!

# direkt aus der pow() function genommen
np.nextafter(pow(2, math.log2(pow(10, -217))), np.inf)    # 1.0000000000000083e-217  <- ok?
np.nextafter(pow(2, math.log2(pow(10, -217))), -np.inf)   # 1.000000000000008e-217    <- Hä??? Ok, log2 ist ungenau.

### arggh *tisch fliegt*
print(''.join([chr(x) for x in [73, 108, 32, 115, 116, 117, 112, 105, 100, 111, 33]]))
Was versteht denn mein müder Kopf nicht :(

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Donnerstag 20. Juni 2024, 17:15
von __blackjack__
@tony_pythony: Also das erste Beispiel ist die Formatierung einer Gleitkommazahl in Python, denn da kommt natürlich auch in Python nicht 0.3 raus:

Code: Alles auswählen

In [24]: print(format(float(decimal.Decimal('1.2') - decimal.Decimal('0.9')), ".50f"))
0.29999999999999998889776975374843459576368331909180
Python benutzt für die Ausgabe von Gleitkommazahlen seit 3.1 einen Algorithmus der die Zeichenkettendarstellung möglichst kurz hält. Vielleicht/wahrscheinlich verwendet Deine andere Software was anderes zum umwandeln von Gleitkommazahlen in Zeichenketten‽

Dann gibt es auch noch so schräge Sachen wie das Intelprozessoren bei der FPU intern bei Gleitkommazahlen mit 80 Bit rechnen (können), egal was man da mal reingefüttert hat. SSE2-Instruktionen aber ”nur” mit IEE754-Genauigkeit. Und auch beim Umwandeln von Zeichenketten in Gleitkommazahlen kann man sich was eigenes basteln, statt das von der C-Bibliothek zu verwenden. Und verschiedene C-Bibliotheken können da auch unterschiedliche Wege gehen. Ich denke um da letztlich jeden Randfall genau gleich zu haben, wirst Du exakt das gleiche wie die andere Software machen müssen: bis hinunter auf die Wahl der Assembleranweisungen mit denen am Ende die Rechnungen durchgeführt werden. Beziehungsweise die emulieren wenn das auch auf anderen Prozessoren laufen können soll, die andere Befehlssätze haben.

Sirius3 hat die Frage ja schon mal gestellt: Sind diese Ungenauigkeiten überhaupt relevant?

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Donnerstag 20. Juni 2024, 17:37
von tony_pythony
Ich habe das Gefühl der Parser mag keine trailing 0's. Das mag meine round_significant(x, p) auch nicht.
Bei 1e-217 müsste er ja aber 1.000000000000*10^-217 anzeigen, weil normalisiert (oder war das denormalisiert?? ... das mit einem enzigen Integer vor dem dezimalpunkt).
und statt einfach 1e-217 anzuzeigen, verschluckt er sich irgendwie. Denn, die 1e-217 kann er ja prinzipiell anzeigen:

A. Wenn das erste mal 1e-217 in eine Datenzelle reingeschrieben wird, folgt der Schluckauf und der zeigt
9.99999999999998e-218

B. Wenn A editiert und zu
9.99999999999999e-218
geändert wird, wird es wieder runtergerundet auf
9.99999999999998e-218.

B.1 Gerade entdeckt, dass wenn A zu:
9.99999999999997e-218
geändert wird, dann springt er auf:
9.99999999999996e-218

B.2 wenn B.1 dann zu:
9.99999999999995e-218
geändert wird, dann springt er auf:
9.99999999999994e-218
... und so weiter. Dem gefällt es, dass hinten eine gerade Zahl ist.

Das könnte erklären, warum er keine 1e-217 / Most accurate representation* = 1.00000000000000008202868690748E-217 mag:
(-> btw:

Code: Alles auswählen

"{:.29e}".format(1e-217)   #  1.00000000000000008202868690748e-217
ist das gleiche wie von der binaryconvert.com. Mein Python rechnet also wie irgendjemand sein Server sein Zeug :D Es geht also alle stellen zu benutzen, nur die nach der 15. sind nicht mehr korrekt, aber dennoch wiederholbar. Ha Haaaa!!! Geil! )

B.3 Wenn B.2 zutrifft und pow(2, math.log2(pow(10, -217)))
1.0000000000000081e-217
liefert, sind das 16 Stellen nach dem Punkt dy Python mir anzeigt. Laut dem Standard werden so 15-16 dps garantiert. Angenommen, die Software mag 14 Stellen und hat 2 letzte als Guarding Bits für die "richtige" Rundung, dann:
1.0000000000000081e-217 wird gerundet zu:
1.00000000000001xxe-217
Aber der mag keine ungeraden zahlen hinten und rundet ab Richtung -inf !!!! Heidenei!!!

:? Gerade ausprobiert.. 1.00000000000001e-217 ... schluckt er.. auch die 1.00000000000000e-217, wird dann zu 1e-217 :lol:

C. wird A eine weitere dezimalstelle hinzugefügt:
9.999999999999998e-218
wird 1e-217 angezeigt

hmmm hmmm hmmmm..... so ein Depp!!! ...Wenn mans nicht selber macht!!! :lol:


* https://www.binaryconvert.com/result_do ... 5050049055

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Donnerstag 20. Juni 2024, 18:06
von tony_pythony
__blackjack__ hat geschrieben: Donnerstag 20. Juni 2024, 17:15 @tony_pythony: Also das erste Beispiel ist die Formatierung einer Gleitkommazahl in Python, denn da kommt natürlich auch in Python nicht 0.3 raus:

Code: Alles auswählen

In [24]: print(format(float(decimal.Decimal('1.2') - decimal.Decimal('0.9')), ".50f"))
0.29999999999999998889776975374843459576368331909180
Python benutzt für die Ausgabe von Gleitkommazahlen seit 3.1 einen Algorithmus der die Zeichenkettendarstellung möglichst kurz hält. Vielleicht/wahrscheinlich verwendet Deine andere Software was anderes zum umwandeln von Gleitkommazahlen in Zeichenketten‽

Dann gibt es auch noch so schräge Sachen wie das Intelprozessoren bei der FPU intern bei Gleitkommazahlen mit 80 Bit rechnen (können), egal was man da mal reingefüttert hat. SSE2-Instruktionen aber ”nur” mit IEE754-Genauigkeit. Und auch beim Umwandeln von Zeichenketten in Gleitkommazahlen kann man sich was eigenes basteln, statt das von der C-Bibliothek zu verwenden. Und verschiedene C-Bibliotheken können da auch unterschiedliche Wege gehen. Ich denke um da letztlich jeden Randfall genau gleich zu haben, wirst Du exakt das gleiche wie die andere Software machen müssen: bis hinunter auf die Wahl der Assembleranweisungen mit denen am Ende die Rechnungen durchgeführt werden. Beziehungsweise die emulieren wenn das auch auf anderen Prozessoren laufen können soll, die andere Befehlssätze haben.

Sirius3 hat die Frage ja schon mal gestellt: Sind diese Ungenauigkeiten überhaupt relevant?
Sag das nicht :lol:
Es gab da mal eine GUI-less Serveranwendung zu, die dann auf win/lin/mac installiert werden konnte und die Clients (das was ich habe) dann in der Local Cloud sich die Werte ziehen konnten oder verteilt rechnen liessen. Das würde ich gerne auch nachbauen, aber nach dem ich die Rundungen verstanden habe :lol: Ich frag dann wieder hier um Hilfe.

Nee, ich denke das ist schlichtes C++ mit der BOOST Bibliothek. BOOST für C++ hat solche Sachen drin. Wobei mich dünkt, dass Grenzfallbehandlung eigens nach mathematischen Definitionen und Regeln gemacht wurde und diese Regeln eben Mathematik-Regeln sind haha (was kann pow(x,y) überhaupt zum essen bekommen, wie soll round(2.5) runden? So wie in Python? Nein! So wie jeder vernünftige Mensch in der Schule lernt.)
Sicherlich, ist auch bisschen ordentlich Hirn reingeflossen, weil multithreading & Multicore. Haben auch bisschen so Mathematiker geschrieben. Deswegen denke ich, keine Hochperformance, aber effizienz was die Alghos anbetrifft. Glaub die Interne Berechnung ist Dataframe ähnlich - kommt mir irgendwie so ähnlich vom Workflow vor...

Daten füttern, Daten werden bearbeitet und vorgehalten, es folgt wie bei numpy die array Operationen und dann die Ausgabe. Da wird auch vielleicht Latex-Bib (?) für das rendern der mathematischen Schreibweise benutzt. Ich denke nicht, dass so tief geschaut werden muss.
Das Verhalten ist reproduzierbar und irgendwie auch nachvollziehbar, nur es zu erkennen ...

Schon allein der Hirnkrampf, dass Base10 zahlen reingeschrieben werden und der erstmal den Exponenten im Base2 zieht und dann die 2 mit dem Exponenten potenziert nur um danach diese zahl darzustellen und dabei dann auch noch irgendwelche Hirnkrampf-Regeln zu verwenden - das muss einem strikten, regelbeachtendemnHirn entsprungen sein. Da werden keine Amazon-Intel-Optimierungen drin sein ... (wobei eine Cloudanbindung zu AWS fürs CloudComputing mit Aufwandsabrechnung auch drin war :D )

Nee, ich denke, das ist einfach nur purer Standard C++ ordentlich programmiert. Die Bugs sind auch Features. Das was ich inzwischen so sehe, zeigt eigentlich reproduzierbar...

.. Heut Abend sag Ich "Schatz, bring mir bitte ein Bier. ABER DOUBLE FLOTT!


EDIT:
ACHSO!! :lol: :lol: :lol: Ich sehe gerade, in meinem Eröffnungspost beschrieb ich die Software und sagte dann "ich möchte das nachbauen". NEIN!!!! HAHAHAHA!!!
Die Software zeigt mir eine mathematische Formel an. Diese nehme ich und gehe dann in mein Python, füttere die ein und verwende es dann um zum Beispiel Kurven aus Daten zu erstellen. Oder schau mir irgendwelche Daten an... Prinzipiell geht es um "Genauigkeit" im Float Bereich. Wenn sich bei mir Python-seitig beim benutzen der math. Formeln alles so verhält, wie das Programm es macht, dann habe ich mein Ziel erreicht. Regression und alles was das Ding macht, will ich gar nicht. Bin auch kapazitativ dazu nicht fähig. Null.
Nur, es sollte zumindest mal so genau sein, dass ich vergleichen kann, ob ich denn überhaupt alles richtig rechne. Die Modulo zum Beispiel ist nicht die wie in python x%y, math.mod() ... das Programm macht:
quotient = x // y
remainder = x - (quotient * y)

Bis ich das herausgefunden habe, habe ich so ziemlich viele mod_s() durchgetestet, und wusste dabei nicht mal, ob meine Berechnung der Berechnung der Software entspricht, weil die Software halt bei der Ausgabe etwas anders macht, wie zum Beispiel dieses komische Runden... zum speihen und schreien!!

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Donnerstag 20. Juni 2024, 19:22
von Sirius3
Wenn Du eine Black-Box hast, die auf nicht definierte Weise rundet, dann ist diese Software für wissenschaftliches Rechnen nicht geeignet. Höchstens für BWLer.

Formel machen das, wie es die Mathematik beschreibt. Beim Rechnen mit Computern kommen halt noch Rundungsfehler obendrauf. In den meisten Fällen sind die klein und vernachlässigbar.
Welches Argument hast Du, dass das bei Dir nocht so ist?

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Donnerstag 20. Juni 2024, 20:05
von tony_pythony
Sirius3 hat geschrieben: Donnerstag 20. Juni 2024, 19:22 Wenn Du eine Black-Box hast, die auf nicht definierte Weise rundet, dann ist diese Software für wissenschaftliches Rechnen nicht geeignet. Höchstens für BWLer.

Formel machen das, wie es die Mathematik beschreibt. Beim Rechnen mit Computern kommen halt noch Rundungsfehler obendrauf. In den meisten Fällen sind die klein und vernachlässigbar.
Welches Argument hast Du, dass das bei Dir nocht so ist?
Ja es ist "momentan" eine Blackbox. Weil ich es noch nicht nachvollziehen kann, welche Rundungsschritte vollzogen werden. Excel gibt auch das gleiche Ergebnis bis auf die 14te Stelle, wenn =potenz(2; log(potenz(10; -217); 2)) mache. Da ist es relativ einfach, da stur gerundet. Hier kommen halt zwei, drei Ausnahmen hinzu. Es ist nicht eine Blackbox von der Komplexität einer LLM-Blackbox. Und ja, Vielleicht ist es für BWLer, aber die BWLer möchten auch irgendwann die herausgegebenen Formeln anderweitig benutzen und für die bringt es nichts diese einfach in Python reinzuhauen und eval() drüber laufen zu lassen, weil Python sich anders verhält als Mathematik (zum Beispiel rechts-assozierte potenzen, statt links wie überall, runden von 2.5 auf 3 statt auf die 2 in Python, nan statt inf für Grenzfälle, etc...).
Wie gesagt, die Funktionen sind nicht das Problem. Die habe ich alle ordentlich funktionierend nachgebaut. Alles im Wertebereich 10^-200 - 10^200 ist richtig.
Nur danach fängt es an.. Und man sieht doch, es ist reproduzierbar und nicht random, weswegen dort auch ausgetestet werden kann und dann auch nachgebaut. Wahrscheinlich rundet es nach dem ROUND_CEILING / ROUND_UP für postive exponente und ROND_FLOOR / ROUND_DOWN für negative Exponente, Und dann wenn die letzte Zahl ODD ist nextafter(x, -inf), wenn EVEN dann so..oder so..

Wenn etwas sich komisch, aber immer gleich Verhält, dann ist es nur schwer herauszufinden was es macht, aber nicht unmöglich :)

Es ist hat schon relativ wichtig... wenn ich zum Beispiel pow(2, erf(..)) mache, dann zeigt mir Python mein maschinen-Epsilon als Resultat an. Die software gibt aber 0 aus. Jetzt ist die frage, hat es aufgerundet, weil 1 Epsilon is_too_close() oder ist in der erf() der Fuchs drin? Austesten meiner Python erf() implementierung gab halt von 20k+ Werten nur einen falsch aus. Dieser eine ist an sich Kategorie "sch**ss drauf". Problem wird dann draus, wenn die pow(2, epsilon) macht, statt pow(2, 0). Somit kann auch keinem einzigen Wert geglaubt werden, weil "vielleicht ist es ja der eine".

Die Formeln machen was Formeln machen. Dss Programm macht auch das was die Formeln machen. Nur halt die komischen Rundungen :) Ich bleibe dran. Vielleicht sieht jemand ein Muster im Verhalten... :)

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Donnerstag 20. Juni 2024, 20:16
von sparrow
tony_pythony hat geschrieben: Donnerstag 20. Juni 2024, 20:05weil Python sich anders verhält als Mathematik
Ich denke, in dem Thread sollte klar geworden sein, dass das kein Python-Problem ist.
Und es entbehrt nicht eines gewissen Humors, dass du ausgerechnet das _mathematische_ Runden als "anders als Methematik" bezeichnest.

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Donnerstag 20. Juni 2024, 20:37
von Sirius3
Wie überall in der Mathematik ist 2**3**2 gleich 2**(3**2), so ist das beim Potenzieren, halt Mathematik.
Und jeder, der was vom Runden versteht, rundet nicht wie man es in der Schule lernt, weil das statistisch zu zu großen Werten führt.
Und wann eine Rechnung nan ergibt und wann inf ist auch klar Definiert.
Das ist ja das schöne an der Mathematik, wie auch an Python, dass es klare Regeln gibt.

Und Blackbox ist das Gegenteil von klaren Regeln und damit unbrauchbar.

Wenn das Ergebnis so stark von einem Rundungsfehler abhängt, dann hast Du generell ein Problem.
Du kannst davon ausgehen, dass math.erf so implementiert ist, dass der Rundungsfehler minimal ist, und 2**eps ist ~1 und damit maximal ein Epsilon von 2**0 entfernt.

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Donnerstag 20. Juni 2024, 21:27
von __blackjack__
@tony_pythony: Ich weiss jetzt nicht ob ich Deine Antwort nicht verstehe oder Du meine nicht.

Die Umwandlung von Zeichenkette in interne Gleitkomma-Darstellung und von der internen Gleitkomma-Darstellung in eine Zeichenkette ist Aufgabe der Software. Am einfachsten macht man es sich wenn man die Funktionalität der C-Bibliothek des Systems verwendet. Womit man natürlich nicht so wirklich die Kontrolle darüber hat, denn das hängt dann vom System, eventuell vom C-Compiler, und der Version vom C-Compiler und/oder seiner Standardbibliothek ab. Beziehungsweise C++- statt C-Compiler, wobei das ja meistens *ein* Softwarepaket ist was beide Compiler stellt. Aber nicht immer.

Diese Umwandlung hängt am Ende auch davon ab wie die Rechnungen mit den Gleitkommazahlen konkret durchgeführt werden. Das heisst selbst wenn der Compiler/die Bibliothek den gleichen C- oder C++-Code dafür enthält, können da je nach Compiler-Flags leicht andere Ergebnisse heraus kommen. Bei Intel-Prozessoren kann man beispielsweise die traditionelle x87-FPU rechnen lassen, die intern mit mehr Bits rechnet als Double hat, oder mit SSE SIMD Befehlen die sich auf Double-Genauigkeit beschränken und schneller sind. Da muss man am Programm gar nichts für ändern, da reicht es dem Compiler zu sagen, dass man Mathe gerne “schnell“ hätte. Unter Umständen reicht auch ganz generell das man eine Optimierungstufe haben will, wo dass dann halt mit dabei ist wenn der Zielprozessor das hergibt.

Gleitkommazahlen funktionieren leider eben nicht so einfach wie man das aus der Mathematik kennt. In keiner Programmiersprache. In der Mathematik ergibt 10 mal 0,1 addieren den Wert 1. Bei Gleitkommazahlen kann man 0,1 nicht exakt darstellen. Die Näherung ist etwas grösser als 0,1:

Code: Alles auswählen

In [612]: format(0.1, ".50f")
Out[612]: '0.10000000000000000555111512312578270211815834045410'
Das heisst wenn man das 10 mal aufaddiert, kommt nach normaler Mathematik ein Wert heraus der etwas grösser als 1 ist. Überraschung:

Code: Alles auswählen

In [613]: total = 0

In [614]: for _ in range(10):
     ...:     total += 0.1
     ...: 

In [615]: total
Out[615]: 0.9999999999999999
In der Mathematik ist es egal in welcher Reihenfolge man Zahlen addiert. Ups:

Code: Alles auswählen

In [616]: numbers = [random.random() for _ in range(1000)]

In [617]: numbers.sort()

In [618]: sum(numbers) == sum(reversed(numbers))
Out[618]: False
Wenn man also eine Formel nach mathematischen Regeln umstellt, die das Ergebnis nicht verändern dürften, dann kann auf dem Rechner mit Gleitkommazahlen ein abweichendes Ergebnis zwischen den beiden Rechnungen herauskommen.

Wenn ich das richtig sehe, dann weichen Deine Ergebnisse im allerletzten Bit der Mantisse ab. Ich würde sagen das darf man einfach nicht als Problem ansehen, weil das irgendwie erwartbar ist, oder zumindest nicht wirklich überraschend. Wenn Du tatsächlich *diese* Genauigkeit *brauchst*, dann sind Double einfach der falsche Datentyp.

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Donnerstag 20. Juni 2024, 23:20
von tony_pythony
Sirius3 hat geschrieben: Donnerstag 20. Juni 2024, 20:37 Wie überall in der Mathematik ist 2**3**2 gleich 2**(3**2), so ist das beim Potenzieren, halt Mathematik.
nicht überall und nicht überall gleich. Sogar die std. Math-Notation schreibt da nichts vor. Verschiedene Programmiersprachen haben das auch verschieden. Kurze übersicht:
https://codeplea.com/exponentiation-ass ... ty-options

Und ich brauche halt 2**3**2 = (2**3)**2 und nicht 2**3**2 = 2**(3**2), so wie Python das macht. Weil mein Program linksassoziativ ist. Python ist rechts.
Und jeder, der was vom Runden versteht, rundet nicht wie man es in der Schule lernt, weil das statistisch zu zu großen Werten führt.

Ja? Auch, wenn Bankier's Rounding benötigst? Sag das mal der Finanzindustrie, die rundet die ganze Zeit falsch. Oder CEILING/FLOOR? Oder IEEE754:
The IEEE 754 standard defines four rounding modes: round-to-nearest, round towards positive, round towards negative, and round towards zero.
Und wann eine Rechnung nan ergibt und wann inf ist auch klar Definiert.
Das ist ja das schöne an der Mathematik, wie auch an Python, dass es klare Regeln gibt.
Nein.Es gibt für jede Domain eigene Regeln. Es ist definiert was nan/+-inf/ +-0 im Gleitkomma des IEEE754 Standards sind. Aber je nach dem wo du bist, brauchst andere Grenzfallbehandlung und andere Grenzen. In Python wirft math.exp(710) ein Overflow. Ich benötige aber ein +inf. Oder mach mal in Python 2**5024. Mach das mal in C++. Oder mach in Python
math.pow(-2, -653.6)
pow(-2, -653.6)
-2**(-653.6)
drei Power funktionen. Drei Ergebnisse. Einmal ein DomainError, einmal eine Complex-Num und einmal ein Float. Mathematica, MatLab, Excel, Octave .. jeder hat seine eigenen Konventionen. Ich habe nun meine in Form der Software. Und das versuche ich in Python zu überführen. Was schlägst du alternativ vor?

Und Blackbox ist das Gegenteil von klaren Regeln und damit unbrauchbar.
-> Die gibt es ja. Nur ist nicht bekannt gegeben, welche. Mathematisch sind die Regeln klar. Eindeutige Grenzfallbehandlung. Nachvollziehbare und "standardisierte" Grenzen, wie sie in der Literatur zu finden sind. Alles gegeben. Das Problem ist nur bei der Eingabe und bei der Ausgabe. Das minimiert die Komplexität gewaltig, da die internen Funktionen auch überprüft und bereits von mir nachimplementiert wurden.
Wenn das Ergebnis so stark von einem Rundungsfehler abhängt, dann hast Du generell ein Problem.
Vielleicht ist es kein Rundungsfehler, sondern eine Rundungsstrategie mit klar definiertem Verhalten, was Python aber so nicht macht. Zum Beispiel mit Guarding Bits um eben Rundungsfehler zu minimieren, was aber weniger nachkommastellen nach sich zieht.. Da das Rundungsverhalten bestimmtes Muster zeigt, denke ich, es ist zu entdecken. Oder das Programm hält sich an die gute Praxis "(float1 - float 2) < Tolerance beim Vergleich von zwei floats" zu verwenden, statt float1 == float2 Und ich sehe es nur nicht, weil er es vorher auf die 5te-signifikante rundet. Peu a Peu lichtet sich der Nebel aber...
Du kannst davon ausgehen, dass math.erf so implementiert ist, dass der Rundungsfehler minimal ist, und 2**eps ist ~1 und damit maximal ein Epsilon von 2**0 entfernt.
Ja das macht die Software genau so. 2^(2^(-52)) = 1 und auch 2^0 = 1. Meine erf() macht es genau so wie die Software und wie Python (über 20k Werte, nur einer falsch -> wegen Ausgabe). Die math.erf() is so implemetiert, wie sie mathematisch definiert ist: eine Sigmoid-Funktion. Mit Grenzen asymptotisch gegen 1 und -1. Da gibt es kein "dass der Rundungsfehler minimal ist". Der ist solely von der float precision abhängig, die zur Verfügung steht. Oder von der Verwendung der Dezimal-Arithmetik mit arbitrary precision. Bei mir reicht die Float double.precision, weil das Programm das auch so macht. Überprüft. Auch die Sigmoid selbst ist Standardwerk. Da gibts nichts falsch zu machen. Nur zu entscheiden ob schnell und dreckig oder langsam...

Ich bekomme eine mathematische Formel aus der Software. Die muss ich irgendwie parsen / pyparsing. Der parser beachtet die ganzen Precedence-Regeln, so wie die Software es macht (linksseitig) und wie ich sie festgelegt habe. Nach dem Parsen bekomme ich einen Stack, der rückwärts abgearbeitet wird und damit eigentlich die polish notation durchgefahren wird. Das ist aber Parser-Sache. Mir läuft es rein. Danach habe ich die ganzen Funktionen nachgebaut and, not, xor, round, pow, sqrt, erf, erfc, greater, less, log, exp... 40 stk ... alles. Weil Python seine Sache macht und das Programm seine. Jetzt habe ich es so, wie das Programm macht in Python.

Der Rundungsfehler der veeery large und der veeeery small values steht nicht am Pranger. Der ist da. Dem Gleitkomma sei dank. Aber der Umgang damit ist das Problem. Python geht anders um, als das was ich sehe. Und nur das ist was zu lösen ist. Nicht die math. Funktionen. Diese werden dann endgültig überprüft, wenn die Rundung nachvollziehbar richtig funzt. Solange das nicht ist, kann ich allem trauen aber bei Kontrolle sich den Kopf zerkratzen, warum so oder warum so - oder ich traue erst mal gar nichts. Das mache ich jetzt eben. Ich sehe was falsch ist. Ich sehe wann es auftritt. Das versuche ich nachzumachen. Einfach so. Da kann ich mich auf den Boden legen und schreien, was dem Programm einfällt sich nicht an Python-Standards zu halten und aufhören, oder ich nutze Python um etwas zu bauen. ...

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Donnerstag 20. Juni 2024, 23:52
von tony_pythony
@tony_pythony: Ich weiss jetzt nicht ob ich Deine Antwort nicht verstehe oder Du meine nicht.
:D Ich verstehe deine und bin mir auch bewusst, dass verschiedene FPUs software/hardware, Compiler, Compiler-Versionen, Programmiersprachen, .., es anders machen können und machen.
Aber ich sehe das Problem nicht, weil ich nur normal 64bit Floats habe, die sich strikt nach dem Standard verhalten. Nur ist momentan noch nicht bekannt welche Rundungsstrategie für die Last Places an 15./16./17. stelle bei der Eingabe verwendet werden. Und ob darüber hinaus noch odd/even eine Rolle spielt. Wenn das bekannt ist, wird sich die Rundung der Ausgabewerte klären. Es wird wahrscheinlich die gleiche Methodik sein. ... Vielleicht drücke ich mich auch zu undeutlich aus ;)
Gleitkommazahlen funktionieren leider eben nicht so einfach wie man das aus der Mathematik kennt. In keiner Programmiersprache.
War bekannt. Die Float-Arithmetik hat mich aber nie sonderlich tangiert. Jetzt macht sie es.. Aber ist logisch.
Wenn man also eine Formel nach mathematischen Regeln umstellt, die das Ergebnis nicht verändern dürften, dann kann auf dem Rechner mit Gleitkommazahlen ein abweichendes Ergebnis zwischen den beiden Rechnungen herauskommen.


Der Fall wird nie auftauchen, da die Software vielleicht in arrays rechnet, aber alles im GUI ist nicht array basiert. Dort gibt es auch keine Möglichkeit Arrays zu erzeugen, ausser mit sma(x, y), simple moving average, die sich die Werte aus dem Datensatz selbst holt. Ist bereits nachimplementiert, kein Problem.
Wenn ich das richtig sehe, dann weichen Deine Ergebnisse im allerletzten Bit der Mantisse ab. Ich würde sagen das darf man einfach nicht als Problem ansehen, weil das irgendwie erwartbar ist, oder zumindest nicht wirklich überraschend. Wenn Du tatsächlich *diese* Genauigkeit *brauchst*, dann sind Double einfach der falsche Datentyp.
Ja die sind glaub ich über die letzten 3 Bits der Mantisse verteilt. Je nach Exponent sehe ich eine Übereinstimmung mit Decimal.from_float(float) manchmal bis hin zu der 17 Stelle. Morgen schreib ich mal ein loop um die Precision der decimals zu variieren und schaue mal wie die 6 Rundungsstrategien sich auswirken. Heute eine gute Beobachtung gemacht mit dem "even/odd im letzten Bit" :)

jetzt aber mal ins bett, ABER DOUBLE FLOTT!!! :) hahaha aber double float :lol: :lol: :lol: ich kann nicht mehr.

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Freitag 21. Juni 2024, 01:19
von __blackjack__
@tony_pythony: Du hast gezeigt wie das verschiedene Programmiersprachen handhaben, aber in der Mathematik schreibt man das ja nicht mit einem binären Operator sondern mit einem hochgestellten Operanden, und da wäre das komisch wenn

Code: Alles auswählen

  n + 1
a
nicht rechtsassoziativ wäre. Oder wenn *das* rechtsassoziativ wäre, der folgende Ausdruck dann aber plötzlich linksassoziativ:

Code: Alles auswählen

  k
 n
a
Das C++-Beispiel ist schräg. Da überlädt der Autor den Exor-Operator und nimmt dessen Assoziativität als Grundlage für die Behauptung die Exponentation in C++ wäre linksassoziativ. Dann ist Python auch linksassoziativ, denn genau das gleiche Beispiel kann man auch in Python hinbiegen:

Code: Alles auswählen

class K(int):
    def __xor__(self, other):
        return K(self**other)


def main():
    a = K(2)
    b = K(2)
    c = K(3)
    print(a ^ b ^ c)  # => 64


if __name__ == "__main__":
    main()
Du sagst erst Python rundet falsch um jetzt zu sagen: Oh schau mal es kommt darauf an was man braucht. Also rundet Python nicht falsch. Oder jede Sprache muss falsch runden wenn sie einen nicht dazu zwingt immer anzugeben *wie* man runden möchte‽

Das mit der Summe war *ein* Beispiel. Wenn *das* in Deiner Blackbox nicht vorkommt — woher weisst Du das eigentlich? — dann kann es das in anderer Form geben.

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Freitag 21. Juni 2024, 08:08
von Sirius3
Wenn man die Liste der Links-assoziativen Programme anschaut, dann sind das Excel und Matlab. Beide bekannt für kreative Interpretation von Mathematik. Aber klar, wenn Du ein bestimmtes Verhalten nachprogrammieren willst, und das KLAR definiert ist, dann mußt Du das wohl berücksichtigen. Dazu schreibt man ja einen Parser, der diese Syntax berücksichtigt. Und das Ergebnis ist ein Baum, dem die Assiziativität egal ist.
Es ist gute Praxis, dass wenn etwas schief geht, eine Exception geworfen wird, so dass der Programmierer entscheiden kann, wie damit umzugehen ist:

Code: Alles auswählen

try:
    ergebnis = math.exp(230)
except OverflowError:
    ergebnis = float('inf')

try:
    ergebnis = (-2) ** -653.6
except ValueError:
    ergebnis = float('nan')

try:
    ergebnis = 6 / 0
except ZeroDivisionError:
    ergebnis = 42
Wenn die Regeln nicht bekannt sind, dann ist das so gut wie keine Regel.

Re: Hilfe beim nachvollziehen vom komischen floats Rundungen

Verfasst: Freitag 21. Juni 2024, 10:51
von tony_pythony
@__blackjack__:
Ja das habe ich alles auch. Ich bekomme eine Expression wie

"b = min(a, (-108)^(-108) - a)" oder
"d = sgn(a + (step((((b*(sinh(c))) + (max((a), d)) + (min((or((a), d)), (min((acos(f)), (min((f * a), (atan2((d + a), (b)))))))))) - a))) - if(xor(d, a), tanh(f), gauss(g)))"

Das parse ich dann. Weil Python rechts-assoziativ ist und die Formelschreibweise das Caret ^ statt ** nutzt, kann ich nicht einfach str-replace und dem eval() übergeben. Die Klammern entsprechend händisch zu setzen ist nicht möglich. Bei manchen Datensätzen habe ich mehrere 20k Formeln, die ich dann auf den Fit prüfe und den Müll verwerfe. Ausserdem müsste ich sowas wie "if", "and", "xor" ... überladen oder str.replace machen. -> Einfach zum Stack geparsed, eine dict mit den function-names als keys und lamda/my_functions als value, und dann nur noch entsprechende Logik zum Stack-abarbeiten. Fertig.
(Die extra-Klammern in der Formel, wie in "atan2((d + a), (b))", sind mit Absicht drin. In der kurzen Version (ala simplify wie es in mathematica oder in sympy zur Reduktion der Formelkomplexität benutzt wird) kann ich die import-Funktion des Programms nicht nutzen. Diese erwartet ein bestimmtes Format wo jede Identität klar und deutlich abgegrenzt ist. Deswegen lass ich den.)
Das C++-Beispiel ist schräg.

Da kann ich nichts machen. Ich kenne das halt so, dass die Operatorassoziativität Probleme macht und überall unterschiedlich sein kann :) Dass manches ange- und anderes nicht angepasst werden kann oder hin und hergewandelt, ist auch klar. Ich habe jetzt die Anforderung ein anpassbares System Python an ein nicht-anpassbares System anzupassen.
Du sagst erst Python rundet falsch um jetzt zu sagen: Oh schau mal es kommt darauf an was man braucht. Also rundet Python nicht falsch. Oder jede Sprache muss falsch runden wenn sie einen nicht dazu zwingt immer anzugeben *wie* man runden möchte
Aus meiner Sicht rundet Python falsch. Ich brauche x, bekomme aber y. Aus deiner Sicht rundet Python richtig, nur nicht so, wie ich es brauche.. Ich denke, wir können uns auf den Standpunkt einigen, dass Python und wahrscheinlich alle anderen Sprachen die Floats so runden, wie es der Standard vorsieht, mit seinen 4 verschiedenen Empfehlungen der Rundungsstrategien für die letzten Bits. .. Je nach System, FPU, Intel/IBM/C64.. etc, ergeben sich hier auch noch weitere Unterschiede. Und genau der Fall liegt ja vor. Welches der vier Rundungsstrategien benutzt Python für die floats und die letzten Bits der Mantisse out-of-the-box und welches brauche ich?
Angenommen ich zieh mit Python auf den Texas Calculator - die allererste Rechnung ist nicht so gerundet, wie der Calculator es selbst macht, dann kann gesagt werden "auf dem Zielsystem rundet Python falsch". Das sind aber Wortklaubereien wie "das Glas ist halb-voll" / "Das Glas ist halb-leer" - Beides sagt "Das Glas hat die Hälfte seinen Volumens gefüllt", nur die benötigte Konvention entscheidet, welche Formulierung die "richtige" ist. Die andere ist dann "falsch" und nicht "nicht passend". "Der Optimist unter den Pessimisten redet falsch aus Sicht der Pessimisten".
Und dann kommt noch dazu, dass Tricks notwendig sind, um Python etwas "neues" beizubringen, was auch im im ieee754 Standard beschrieben ist (round-to-nearest, round towards positive, round towards negative, round towards zero). Da muss eine user-defined function her, weil Python das vom Haus aus anders macht. Dieses anders ist aus meiner Sicht falsch, weil nicht das richtige ist.
Das mit der Summe war *ein* Beispiel. Wenn *das* in Deiner Blackbox nicht vorkommt — woher weisst Du das eigentlich? — dann kann es das in anderer Form geben.
Das sehe ich. Die Blackbox hat keine user-defined functions und bietet auch keine built-in function "sum" (oder sinnverwandt). Das einzige was "arrays" benutzt sind die Durchschnittsbildner sma(), smm(), wma(), ..., und die bekommen eine "alles bis x Schritte zurück"- Angabe als Argument, was im Endeffekt wie bei Numpy einfach das Array entsprechend hoch oder runter shifted und die entsprechenden rows für die Berechnung des Durchschnitts liefert. Relativ einfach gehalten. Das Teil frisst nur Werte und gibt dann auch nur Wert/nan/+inf/-inf/0 zurück. Es werden keine Arrays von Werten zurückgegeben.
Aber ja, sma(a, 5) == sma(b, 5]) -> True, wenn a=[1, 2, 3, 4, 5] und b=[5, 4, 3, 2, 1]. Bei dem weighted modified average wma() kommt es auf die Reihenfolge an, aber das wird durch die Reihenfolge im Datensatz bestimmt, wann was kommt und welches weighting dann die der Reihenfolge nach ankommenden Werte bekommen. Relativ straight forward und auch schon umgesetzt. Es ist wie bei der Time-series-Analyse. Der Datensatz ist als eine Reihenfolge zu betrachten und zu verarbeiten. Von daher kann ich mit ziemlicher Sicherheit sagen: Ich weiss es :P