StartseitePi berechnenKettenbruch 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.

Ausblenden

\frac{4}{\pi}=1+\frac{1^2}{3+\frac{2^2}{5+\frac{3^2}{\ddots}}}

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:

\frac{4}{\pi}=2\,n-1+\frac{n^2}{2\,(n+1)-1+\frac{(n+1)^2}{2\,(n+2)-1+\frac{(n+2)^2}{\ddots}}}

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:

\frac{\ln(2^{52})}{\ln(10)}=15,654

Dezimalstellen.

Für knapp 16 Dezimalstellen reicht es also aus, die Rekursionstiefe auf etwa:

\frac{15,654}{0,76555}=20,448

also 21 Teilbrüche zu begrenzen.

  1. #include <stdio.h>
  2. #include <math.h>


  3. /**
  4.  @ brief *
  5.  Berechnet den n-ten Teilbruch des Kettenbruchs.
  6.  
  7.  @param n
  8.  Gibt den aktuell zu berechnenden Teilbruch an.
  9.  @param max_n
  10.  Gibt die maximale Rekursionstiefe an.
  11.  @return
  12.  Fuer n=1 ist der Rueckgabewert rund 4/Pi.
  13.  */
  14. double kettenbruch( int n, int max_n );


  15. //main_______________________________________________________________
  16. int main( int argc, char** argv )
  17.   // Pi naehern //////////////////////////////////////////////////////
  18.   double Pi = 4.0 / kettenbruch( 1, 21 );
  19.  
  20.   // Ergebnis mit 15 Dezimalstellen (14 Nachkommastellen)
  21.   // ausgeben.
  22.   printf( "%.14lf\n", Pi );
  23.  
  24.   return 0;
  25. }//main


  26. //kettenbruch________________________________________________________
  27. double kettenbruch( int n, int max_n ) {
  28.   double Result;
  29.  
  30.   if ( n > max_n ) {
  31.   // Die maximale Rekursiostiefe ist erreicht
  32.   // -> Abbruch
  33.   Result = 1.0;
  34.   }
  35.   else {
  36.   Result = 2*n-1 + n*n / kettenbruch( n+1, max_n );
  37.   }
  38.  
  39.   return Result;
  40. }//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.

Versuche

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.

  1. #include<stdlib.h>
  2. #include<stdio.h>
  3. #include<string.h>
  4. #include<gmp.h>
  5. #include<math.h>

  6. typedef unsigned long int ulong;

  7. ulong ulBitWidth; //!< Genauigkeit der Gleitkommazahlen.

  8. char *strUsageTemplate = "usage: %s DECIMAL_PLACES\n\n\DECIMAL_PLACES A decimal number greater than 0.\n";


  9. /**
  10.  @ brief *
  11.  Berechnet den n-ten Teilbruch des Kettenbruchs.
  12.  
  13.  @param n
  14.  Gibt den aktuell zu berechnenden Teilbruch an.
  15.  @param max_n
  16.  Gibt die maximale Rekursionstiefe an.
  17.  @param ptrResult
  18.  Referenzparameter fuer den Rueckgabewert. Fuer n=1 ist der
  19.  Rueckgabewert rund 4/Pi.
  20.  */
  21. void continuedFraction( ulong n,
  22.   ulong max_n,
  23.   mpf_t* ptrResult );


  24. //main_______________________________________________________________
  25. int
  26. main( int argc, char **argv ) {
  27.   ulong ulDecimalPlaces; //!< Anzahl der gewuenschten
  28.   //Dezimalstellen.
  29.   ulong ulRecursionDepth; //!< Benoetigte Rekursionstiefe.
  30.   mpf_t mpfPi; //!< Ergebnis.
  31.  
  32.   // Anzahl der gewuenschten Dezimalstellen ermitteln ////////////////
  33.   if ( argc < 2 ) {
  34.   fprintf( stderr,
  35.   strUsageTemplate,
  36.   argv[0]
  37.   );
  38.  
  39.   exit( 1 );
  40.   }
  41.  
  42.   if ( 1 > sscanf( argv[1], "%lu", &ulDecimalPlaces ) ) {
  43.   fprintf( stderr,
  44.   strUsageTemplate,
  45.   argv[0]
  46.   );
  47.  
  48.   exit( 2 );
  49.   }
  50.  
  51.   // Benoetigte Rekursionstiefe berechnen ////////////////////////////
  52.   ulRecursionDepth = 3+(ulong) ceil((double)ulDecimalPlaces/0.76555);
  53.  
  54.   // Benoetigte Genauigkeit der Gleitkommazahlen berechnen ///////////
  55.   ulBitWidth = (ulong) ceil(
  56.   (double)ulDecimalPlaces * log( 10.0 ) / log( 2.0 ) );
  57.  
  58.   /* Die Genauigkeit (Bitbreite der Mantisse) wird auf ein Vielfaches
  59.   * von 32 aufgerundet. **/
  60.   if ( ulBitWidth & 0x0000001F ) { // wenn 0 != ulBitWidth modulo 32
  61.   ulBitWidth >>= 5; // ulBitWidth = ulBitWidth / 32
  62.   ulBitWidth += 1; // ulBitWidth = ulBitWidth + 1
  63.   ulBitWidth <<= 5; // ulBitWidth = ulBitWidth * 32
  64.   }
  65.  
  66.   // Ergebnisvariable initialisieren ////////////////////////////////
  67.   mpf_init2( mpfPi, ulBitWidth );
  68.  
  69.   // Kettenbruch berechnen ( Zwischenergebnis = Pi/4 ) //////////////
  70.   continuedFraction( 1, ulRecursionDepth, &mpfPi );
  71.  
  72.   // Ergebnis = 4 / Zwischenergebnis = Pi ///////////////////////////
  73.   mpf_ui_div( mpfPi, 4, mpfPi );
  74.  
  75.   // Ergebnis ausgeben //////////////////////////////////////////////
  76.   // ( Die letzte Stelle wird von gmp_printf() gerundet. )
  77.   gmp_printf( "%.*Ff\n", ulDecimalPlaces-1, mpfPi );
  78.  
  79.   return 0;
  80. }//main


  81. //continuedFraction__________________________________________________
  82. void
  83. continuedFraction( ulong n,
  84.   ulong max_n,
  85.   mpf_t* ptrResult ) {
  86.  
  87.   if ( n == max_n ) {
  88.   // Die maximale Rekursiostiefe ist erreicht -> Abbruch //////////
  89.   mpf_set_ui( *ptrResult, 1 );
  90.   }
  91.   else {
  92.   // Lokale Variablen anlegen und initialisieren //////////////////
  93.   mpf_t mpfSummand;
  94.   mpf_t mpfFraction;
  95.   mpf_t mpfDenominator;
  96.  
  97.   mpf_init2( mpfSummand, ulBitWidth );
  98.   mpf_init2( mpfFraction, ulBitWidth );
  99.   mpf_init2( mpfDenominator, ulBitWidth );
  100.  
  101.   //Rueckgabewert berechnen/////////////////////////////////////////
  102.  
  103.   /*
  104.   * ptrResult = 2*n - 1 + n^2 / continuedFraction( n+1, max_n )
  105.   * \______________ ______________/
  106.   * V
  107.   * |
  108.   * +--> mpfDenominator
  109.   */
  110.   continuedFraction( n+1, max_n, &mpfDenominator );
  111.  
  112.   /*
  113.   * ptrResult = 2*n - 1 + n^2 / mpfDenominator
  114.   * \ /
  115.   * |
  116.   * +------------------------> mpfFraction
  117.   */
  118.   mpf_set_ui( mpfFraction, n*n );
  119.  
  120.   /*
  121.   * ptrResult = 2*n - 1 + mpfFraction / mpfDenominator
  122.   * \____________ _____________/
  123.   * V
  124.   * |
  125.   * +------------> mpfFraction
  126.   */
  127.   mpf_div( mpfFraction, mpfFraction, mpfDenominator );
  128.  
  129.   /*
  130.   * ptrResult = 2*n - 1 + mpfFraction
  131.   * |
  132.   * +---------------------------------> mpfSummand
  133.   */
  134.   mpf_set_ui( mpfSummand, n );
  135.  
  136.   /*
  137.   * ptrResult = 2*mpfSummand - 1 + mpfFraction
  138.   * \____ _____/
  139.   * V
  140.   * |
  141.   * +------------------------------> mpfSummand
  142.   */
  143.   mpf_mul_ui( mpfSummand, mpfSummand, 2 );
  144.  
  145.   /*
  146.   * ptrResult = mpfSummand - 1 + mpfFraction
  147.   * \_____ ______/
  148.   * V
  149.   * |
  150.   * +-----------------------------> mpfSummand
  151.   */
  152.   mpf_sub_ui( mpfSummand, mpfSummand, 1 );
  153.  
  154.   /*
  155.   * ptrResult = mpfSummand + mpfFraction
  156.   */
  157.   mpf_add( *ptrResult, mpfSummand, mpfFraction );
  158.   }
  159.  
  160. }//continuedFraction