π ≠ PI (?)

vorhergehende Artikel in: Java Numerik
30.12.2024

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!

Artikel, die hierher verlinken

Wiederverwendung von Regeln für ArchUnit

31.01.2025

Ich habe neulich wieder einmal über neue Regeln für ArchUnit berichtet. Ich organisiere meine Regeln so, dass ich Dateien mit Regeldefinitionen habe und in konkreten Paketen dann Test-Klassen erstelle, in denen einige der global definierten Regeln angewendet werden

Java, SecurityManager, CyberSecurity

25.01.2025

Ich hatte neulich schon einmal begonnen, über die Nutzung des Java SecurityManager zur Erhöhung der Sicherheit von Java-Anwendungen zu schreiben.

Alle Artikel rss Wochenübersicht Monatsübersicht Codeberg Repositories Mastodon Über mich home xmpp


Vor 5 Jahren hier im Blog

  • Derangement - theoretische Betrachtungen

    23.12.2020

    Ich habe bereits über meine Implementierung eines Derangement berichtet - hier noch einige theoretische Nachbetrachtungen...

    Weiterlesen

Neueste Artikel

  • Asymmetrische Kryptographie

    Ich habe mich mit der Idee schon länger getragen: Nochmal einen Rundumschlag zu asymmetrischer Kryptographie zu machen. Dabei werde ich mich auf Demonstrationen der einzelnen Konzepte und Operationen mit Beispielcode konzentrieren und zu jedem der vorgestellten Konzepte mehr oder weniger ausführlich bezüglich der Einsatzszenarien und Vor- und Nachteile Stellung beziehen

    Weiterlesen
  • Eingereichter Vortag zum 39C3 - Gentlemen - check your architecture!

    Dieser Vortrag wurde zum 39C3 eingereicht und abgelehnt. Ich möchte einige Open-Source-Frameworks für verschiedene Programmiersprachen vorstellen, mit denen sich Architekturregeln pro Projekt oder Organisation festlegen und mithilfe gängiger Testinfrastrukturen durchsetzen lassen.

    Weiterlesen
  • Chatkontrolle vermeiden ist gleichzeitig unwichtig und nicht genug!

    Das ist ein Abstract eines Vortrages, den ich auf dem 39C3 halten wurde, der aber abgelehnt wurde

    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.