Startseite → Pi berechnen → Kettenbruch in C programmiert
Kettenbruch in C programmiert
Vielen Dank an Andreas Noe für die folgenden Ausführungen und die beiden C-Programme.
Das Programm verwendet für die Näherung von Pi den Kettenbruch, den Johann Heinrich Lambert 1770 veröffentlichte.
Ein Teilbruch besteht also aus der Summe der n-ten ungeraden Zahl und dem Quotienten von n2 und dem nächsten Teilbruch. Für n = 1:
Diese rekursive Vorschrift kann mit einer rekursiven Funktion berechnet werden. Mit wachsender Rekursionstiefe werden die Änderungen am Gesamtergebnis stetig kleiner. Laut Wikipedia wächst die Genauigkeit des Ergebnisses mit jedem Schritt um ca. 0,76555 Stellen. Damit kann die Berechnung abgebrochen werden, wenn die gewünschte Genauigkeit erreicht ist.
In dieser Version des Programms wird die Berechnung mit dem Datentyp double durchgeführt. Ein double-Wert besteht aus 64Bit. Davon entfallen 52Bit auf die Mantisse, ein Bit auf das Vorzeichen und elf Bit auf den Exponenten. Die für double erreichbare Genauigkeit liegt also bei etwa:
Dezimalstellen.
Für knapp 16 Dezimalstellen reicht es also aus, die Rekursionstiefe auf etwa:
also 21 Teilbrüche zu begrenzen.
- #include <stdio.h>
- #include <math.h>
- /**
- @ brief *
- Berechnet den n-ten Teilbruch des Kettenbruchs.
-
- @param n
- Gibt den aktuell zu berechnenden Teilbruch an.
- @param max_n
- Gibt die maximale Rekursionstiefe an.
- @return
- Fuer n=1 ist der Rueckgabewert rund 4/Pi.
- */
- double kettenbruch( int n, int max_n );
- //main_______________________________________________________________
- int main( int argc, char** argv )
- // Pi naehern //////////////////////////////////////////////////////
- double Pi = 4.0 / kettenbruch( 1, 21 );
-
- // Ergebnis mit 15 Dezimalstellen (14 Nachkommastellen)
- // ausgeben.
- printf( "%.14lf\n", Pi );
-
- return 0;
- }//main
- //kettenbruch________________________________________________________
- double kettenbruch( int n, int max_n ) {
- double Result;
-
- if ( n > max_n ) {
- // Die maximale Rekursiostiefe ist erreicht
- // -> Abbruch
- Result = 1.0;
- }
- else {
- Result = 2*n-1 + n*n / kettenbruch( n+1, max_n );
- }
-
- return Result;
- }//kettenbruch
Das zweite Programm funktioniert nach dem gleichen Prinzip, nur, dass der Datentyp double durch den Gleitkommatyp aus der GMP Bibliothek ersetzt wird.
In C kann man keine Strukturen als Rückgabetyp verwenden. Deshalb erfolgt die Rückgabe per Referenzparameter (Zeiger). Beim Aufruf des Programms gibt man an, wie viele Dezimalstellen berechnet werden sollen. Die letzte Stelle wird gerundet (leider). Unter Linux kann man etwa 40000 Dezimalstellen berechnen. Auf meinem guten alten Core2 Duo (Das Programm kann nur eine CPU verwenden.) braucht es dafür etwa 200s. In dem Bild habe ich ein paar Versuche aufgezeichnet.
Unter Windows kann man etwa 19000 Stellen berechnen, bevor der Stack zu Ende geht. Unter Linux ist die Größe des Stacks ein Systemparameter und kann zur Laufzeit durch einen Systemaufruf geändert werden. Unter Windows gibt man die gewünschte Größe des Stacks dem Compiler als Parameter mit.
- #include<stdlib.h>
- #include<stdio.h>
- #include<string.h>
- #include<gmp.h>
- #include<math.h>
- typedef unsigned long int ulong;
- ulong ulBitWidth; //!< Genauigkeit der Gleitkommazahlen.
- char *strUsageTemplate = "usage: %s DECIMAL_PLACES\n\n\DECIMAL_PLACES A decimal number greater than 0.\n";
- /**
- @ brief *
- Berechnet den n-ten Teilbruch des Kettenbruchs.
-
- @param n
- Gibt den aktuell zu berechnenden Teilbruch an.
- @param max_n
- Gibt die maximale Rekursionstiefe an.
- @param ptrResult
- Referenzparameter fuer den Rueckgabewert. Fuer n=1 ist der
- Rueckgabewert rund 4/Pi.
- */
- void continuedFraction( ulong n,
- ulong max_n,
- mpf_t* ptrResult );
- //main_______________________________________________________________
- int
- main( int argc, char **argv ) {
- ulong ulDecimalPlaces; //!< Anzahl der gewuenschten
- //Dezimalstellen.
- ulong ulRecursionDepth; //!< Benoetigte Rekursionstiefe.
- mpf_t mpfPi; //!< Ergebnis.
-
- // Anzahl der gewuenschten Dezimalstellen ermitteln ////////////////
- if ( argc < 2 ) {
- fprintf( stderr,
- strUsageTemplate,
- argv[0]
- );
-
- exit( 1 );
- }
-
- if ( 1 > sscanf( argv[1], "%lu", &ulDecimalPlaces ) ) {
- fprintf( stderr,
- strUsageTemplate,
- argv[0]
- );
-
- exit( 2 );
- }
-
- // Benoetigte Rekursionstiefe berechnen ////////////////////////////
- ulRecursionDepth = 3+(ulong) ceil((double)ulDecimalPlaces/0.76555);
-
- // Benoetigte Genauigkeit der Gleitkommazahlen berechnen ///////////
- ulBitWidth = (ulong) ceil(
- (double)ulDecimalPlaces * log( 10.0 ) / log( 2.0 ) );
-
- /* Die Genauigkeit (Bitbreite der Mantisse) wird auf ein Vielfaches
- * von 32 aufgerundet. **/
- if ( ulBitWidth & 0x0000001F ) { // wenn 0 != ulBitWidth modulo 32
- ulBitWidth >>= 5; // ulBitWidth = ulBitWidth / 32
- ulBitWidth += 1; // ulBitWidth = ulBitWidth + 1
- ulBitWidth <<= 5; // ulBitWidth = ulBitWidth * 32
- }
-
- // Ergebnisvariable initialisieren ////////////////////////////////
- mpf_init2( mpfPi, ulBitWidth );
-
- // Kettenbruch berechnen ( Zwischenergebnis = Pi/4 ) //////////////
- continuedFraction( 1, ulRecursionDepth, &mpfPi );
-
- // Ergebnis = 4 / Zwischenergebnis = Pi ///////////////////////////
- mpf_ui_div( mpfPi, 4, mpfPi );
-
- // Ergebnis ausgeben //////////////////////////////////////////////
- // ( Die letzte Stelle wird von gmp_printf() gerundet. )
- gmp_printf( "%.*Ff\n", ulDecimalPlaces-1, mpfPi );
-
- return 0;
- }//main
- //continuedFraction__________________________________________________
- void
- continuedFraction( ulong n,
- ulong max_n,
- mpf_t* ptrResult ) {
-
- if ( n == max_n ) {
- // Die maximale Rekursiostiefe ist erreicht -> Abbruch //////////
- mpf_set_ui( *ptrResult, 1 );
- }
- else {
- // Lokale Variablen anlegen und initialisieren //////////////////
- mpf_t mpfSummand;
- mpf_t mpfFraction;
- mpf_t mpfDenominator;
-
- mpf_init2( mpfSummand, ulBitWidth );
- mpf_init2( mpfFraction, ulBitWidth );
- mpf_init2( mpfDenominator, ulBitWidth );
-
- //Rueckgabewert berechnen/////////////////////////////////////////
-
- /*
- * ptrResult = 2*n - 1 + n^2 / continuedFraction( n+1, max_n )
- * \______________ ______________/
- * V
- * |
- * +--> mpfDenominator
- */
- continuedFraction( n+1, max_n, &mpfDenominator );
-
- /*
- * ptrResult = 2*n - 1 + n^2 / mpfDenominator
- * \ /
- * |
- * +------------------------> mpfFraction
- */
- mpf_set_ui( mpfFraction, n*n );
-
- /*
- * ptrResult = 2*n - 1 + mpfFraction / mpfDenominator
- * \____________ _____________/
- * V
- * |
- * +------------> mpfFraction
- */
- mpf_div( mpfFraction, mpfFraction, mpfDenominator );
-
- /*
- * ptrResult = 2*n - 1 + mpfFraction
- * |
- * +---------------------------------> mpfSummand
- */
- mpf_set_ui( mpfSummand, n );
-
- /*
- * ptrResult = 2*mpfSummand - 1 + mpfFraction
- * \____ _____/
- * V
- * |
- * +------------------------------> mpfSummand
- */
- mpf_mul_ui( mpfSummand, mpfSummand, 2 );
-
- /*
- * ptrResult = mpfSummand - 1 + mpfFraction
- * \_____ ______/
- * V
- * |
- * +-----------------------------> mpfSummand
- */
- mpf_sub_ui( mpfSummand, mpfSummand, 1 );
-
- /*
- * ptrResult = mpfSummand + mpfFraction
- */
- mpf_add( *ptrResult, mpfSummand, mpfFraction );
- }
-
- }//continuedFraction