Ich habe bereits verschiedentlich über esoterische numerische Probleme bei der Arbeit mit Computern berichtet.
Durch ein Posting auf Mastodon bin ich auf einen Artikel aufmerksam geworden, der einen Algorithmus diskutiert, der die einzelnen Stellen der Zahl PI auf beliebige Länge generieren kann - einen der sogenannten Spigot-Algorithmen (für engl. Wasserhahn), deren Eigenart es ist, das gewünschte Ergebnis Stelle für Stelle "herauströpfeln" zu lassen".
Ich wollte daraus meinen nächsten Generator für Testdaten machen.
Daher wollte ich zunächst die korrekte Funktion testen und erzeugt einige Stellen mittels des Algorithmus:
Spigot: 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273724587006606315588174881520920962829254091715364367892590360011330530548820466521384146951941511609433057270365759591953092186117381932611793105118548074462379962749567351885752724891227938183011949129833673362440656643086021394946395224737190702179860943702770539217176293176752384674818467669405132000568127145263560827785771342757789609173637178721468440901224953430146549585371050792279689258923542019956112129021960864034418159813629774771309960518707211349999998372978049951059731732816096318595024459455346908302642522308253344685035261931188171010003137838752886587533208381420617177669147303598253490428755468731159562863882353787593751957781857780532171226806613001927876611195909216420198
Danach wollte ich herausfinden, ob der Algorithmus (zumindest in den ersten Stellen) das gleiche Ergebnis liefert, wie es die Konstante in java.lang.Math tut und wandelte diese Konstante ebenso wie den generierten String in eine Instanz der Klasse: java.math.BigDecimal um, wofür ich den jeweiligen Konstruktor benutzte - die Differenz aus beiden sollte nach dem Komma so viele Nullen enthalten, wie die BigDecimal-Darstellung der Konstanten Stellen hatte. Allerdings war dem nicht so:
Spigot: 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273724587006606315588174881520920962829254091715364367892590360011330530548820466521384146951941511609433057270365759591953092186117381932611793105118548074462379962749567351885752724891227938183011949129833673362440656643086021394946395224737190702179860943702770539217176293176752384674818467669405132000568127145263560827785771342757789609173637178721468440901224953430146549585371050792279689258923542019956112129021960864034418159813629774771309960518707211349999998372978049951059731732816096318595024459455346908302642522308253344685035261931188171010003137838752886587533208381420617177669147303598253490428755468731159562863882353787593751957781857780532171226806613001927876611195909216420198
BigDecimal: 3.141592653589793115997963468544185161590576171875
Difference: 0.000000000000000122464679914735317722606593227500105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273724587006606315588174881520920962829254091715364367892590360011330530548820466521384146951941511609433057270365759591953092186117381932611793105118548074462379962749567351885752724891227938183011949129833673362440656643086021394946395224737190702179860943702770539217176293176752384674818467669405132000568127145263560827785771342757789609173637178721468440901224953430146549585371050792279689258923542019956112129021960864034418159813629774771309960518707211349999998372978049951059731732816096318595024459455346908302642522308253344685035261931188171010003137838752886587533208381420617177669147303598253490428755468731159562863882353787593751957781857780532171226806613001927876611195909216420198
Man sieht, dass die beiden bereits sehr früh voneinander abweichen. Nun stellte sich die Frage: Ist der Algorithmus bzw. meine Implementierung fehlerhaft oder etwa Java? Wie sich herausstellt - keins von beidem, denn offenbar geht etwas bei der Umwandlung eines double Wertes in eine Instanz java.math.BigDecimal schief - zu sehen, wenn man beides gegenüber stellt:
Spigot: 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273724587006606315588174881520920962829254091715364367892590360011330530548820466521384146951941511609433057270365759591953092186117381932611793105118548074462379962749567351885752724891227938183011949129833673362440656643086021394946395224737190702179860943702770539217176293176752384674818467669405132000568127145263560827785771342757789609173637178721468440901224953430146549585371050792279689258923542019956112129021960864034418159813629774771309960518707211349999998372978049951059731732816096318595024459455346908302642522308253344685035261931188171010003137838752886587533208381420617177669147303598253490428755468731159562863882353787593751957781857780532171226806613001927876611195909216420198
Double: 3.141592653589793
BigDecimal: 3.141592653589793115997963468544185161590576171875
Difference: 0.000000000000000122464679914735317722606593227500105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273724587006606315588174881520920962829254091715364367892590360011330530548820466521384146951941511609433057270365759591953092186117381932611793105118548074462379962749567351885752724891227938183011949129833673362440656643086021394946395224737190702179860943702770539217176293176752384674818467669405132000568127145263560827785771342757789609173637178721468440901224953430146549585371050792279689258923542019956112129021960864034418159813629774771309960518707211349999998372978049951059731732816096318595024459455346908302642522308253344685035261931188171010003137838752886587533208381420617177669147303598253490428755468731159562863882353787593751957781857780532171226806613001927876611195909216420198
Exact: 3.141592653589793238462643383279502884197
Tatsächlich ist der Wert für alle Stellen des double Wertes korrekt - die Abweichung beginnt danach (Man sieht das auch am exakten Wert von PI, den ich der Anschauung und Überprüfung wegen nochmal aus dem Internet besorgt hatte). Wie kommt das aber nun zustande? Darf man double-Werte nicht in Instanzen der Klasse java.math.BigDecimal umwandeln? warum existieren dann aber die entsprechenden Methoden und Konstruktoren? Zumal es so ist, dass bei der Kostruktion der Instanz von java.math.BigDecimal aus dem double Wert über den Umweg von toString() das erwartete Ergebnis entsteht.
Des Rätsels Lösung erscheint, wenn man sich die Precision ausgeben lässt, die Java der aus dem double Wert erzeugten Instanz von java.math.BigDecimal zuweist - die ist nämlich tatsächlich 49!
Nutzt man stattdessen den Konstruktor, der es erlaubt, eine Instanz von java.math.MathContext zu übergeben und konfiguriert diesen so, dass die Precision mit 16 festgelegt ist unterbindet man das Halluzinieren (schöne Grüße an LLMs!) von Dezimalstellen, die gar nicht da sind...
Das funktioniert übrigens auch bei anderen Zahlen - Die Konstruktion einer Instanz vom Typ java.math.BigDecimal mit einem double Wert von 0.3333333333333333 ergibt eine Instanz, deren Wert 0.333333333333333314829616256247390992939472198486328125 beträgt - daher bei der Umwandlung von double nach java.math.BigDecimal immer dran denken, explizit die Precision festzulegen - das gilt auch bei Zahlen, die durchaus kürzer sein können: die Umwandlung von 0.42 ergibt ohne Angabe der gewünschten Precision 0.419999999999999984456877655247808434069156646728515625 als java.math.BigDecimal!
Das lässt mich bei nochmaligem Drübernachdenken den alten Lehrsatz nochmal aufgreifen: Wenn ihr mit dem Computer rechnet, rechnet in Eurer Domäne - also rechnet nicht mit 0.000000000xyz Metern, sondern mit xyz.0 Nanometern!!
Selbstverständlich sind diese Erkenntnisse auch sofort in meine ArchUnit-Regeln eingegangen!
Vorhaben 2020
03.01.2020
Genau wie letztes Jahr habe ich auch dieses Jahr wieder ein "Listche" verfasst, um mir all die interessanten Vorhaben zu notieren, die ich mit mittlerem zeitlichen Horizont anzugehen gedenke.
Weiterlesen...Android Basteln C und C++ Chaos Datenbanken Docker dWb+ ESP Wifi Garten Geo Go GUI Gui Hardware Java Jupyter Komponenten Links Linux Markdown Markup Music Numerik OpenSource PKI-X.509-CA Python QBrowser Rants Raspi Revisited Security Software-Test sQLshell TeleGrafana Verschiedenes Video Virtualisierung Windows Upcoming...
In eigener Sache...
Weiterlesen...Nach dem ersten Teil von mir als interessant eingestufter Vorträge des Chaos Communication Congress 2024 hier nun die Nachlese
Weiterlesen...Nach dem So - wie auch im letzten Jahr: Meine Empfehlungen für Vorträge vom Chaos Communication Congress 2024 - vulgo: 38c3:
Weiterlesen...Manche nennen es Blog, manche Web-Seite - ich schreibe hier hin und wieder über meine Erlebnisse, Rückschläge und Erleuchtungen bei meinen Hobbies.
Wer daran teilhaben und eventuell sogar davon profitieren möchte, muss damit leben, daß ich hin und wieder kleine Ausflüge in Bereiche mache, die nichts mit IT, Administration oder Softwareentwicklung zu tun haben.
Ich wünsche allen Lesern viel Spaß und hin und wieder einen kleinen AHA!-Effekt...
PS: Meine öffentlichen Codeberg-Repositories findet man hier.