PI Time

PI Time is a tool designed to measure the time it takes to calculate π to a specified number of digits of precision. This calculator was created using OpenAI’s GPT-4.0 and is written in C, targeting Debian-based Linux distributions, including Ubuntu and Raspbian. Like the Simple C calculator, this tool uses the Gauss-Legendre algorithm to calculate PI.

Code

// ==========================================
// Pi Time Calculation Program - Version 1.0
// Author: Chat GPT-4.0 (Prompting and quality testing by Derick Schaefer)
// Date: 9/1/2024
// Description: This program calculates the value of Pi to a specified number of decimal places 
//              and returns the time it takes to complete the calculation using the Gauss-Legendre 
//              Algorithm. It uses high-precision arithmetic with the MPFR library and timestamps 
//              the duration of the computation.
// ==========================================

// ==========================================================================
// Headers
// ==========================================================================

// MPFR Library for high-precision floating-point arithmetic
#include <mpfr.h>

// Standard Input/Output for printing results and error messages
#include <stdio.h>

// Standard Library for memory management and utility functions
#include <stdlib.h>

// String Library for handling string comparisons and manipulations
#include <string.h>

// Time Library for measuring and calculating the time duration of Pi computation
#include <time.h>

// ==========================================================================
// Time Printing Functions
// ==========================================================================

/**
 * Function: print_time
 * --------------------
 * Prints the current time in the format HH:MM:SS:MS.
 *
 * Parameters:
 *  label - A string label to print before the time (e.g., "Start Time" or "End Time").
 *  ts    - A pointer to a `timespec` structure containing the time to be printed.
 *
 * The function extracts the time from the `timespec` and formats it into
 * hours, minutes, seconds, and milliseconds for easy readability.
 */
void print_time(const char *label, struct timespec *ts) {
    struct tm *tm_info = localtime(&(ts->tv_sec));  // Convert time to local time format
    char buffer[30];                                // Buffer to hold formatted time string
    strftime(buffer, 30, "%H:%M:%S", tm_info);      // Format time as HH:MM:SS
    printf("%s: %s:%03ld\n", label, buffer, ts->tv_nsec / 1000000);  // Print time with milliseconds
}

/**
 * Function: print_elapsed_time
 * ----------------------------
 * Calculates and prints the elapsed time between two `timespec` structures.
 * It breaks down the time into days, hours, minutes, seconds, and milliseconds.
 *
 * Parameters:
 *  start  - A pointer to the `timespec` structure representing the start time.
 *  end    - A pointer to the `timespec` structure representing the end time.
 *  digits - The number of digits of Pi that were calculated.
 *
 * The function ensures that nanoseconds are properly accounted for and
 * provides a user-friendly output of the total calculation time.
 */
void print_elapsed_time(struct timespec *start, struct timespec *end, int digits) {
    long seconds = end->tv_sec - start->tv_sec;            // Calculate the difference in seconds
    long nanoseconds = end->tv_nsec - start->tv_nsec;      // Calculate the difference in nanoseconds

    // Adjust for negative nanoseconds
    if (nanoseconds < 0) {
        nanoseconds += 1000000000;  // Add one second's worth of nanoseconds
        seconds--;                  // Subtract one second from the total
    }

    // Break down the total time into days, hours, minutes, seconds, and milliseconds
    int days = seconds / 86400;     // Calculate days
    seconds %= 86400;               // Remaining seconds after calculating days
    int hours = seconds / 3600;     // Calculate hours
    seconds %= 3600;                // Remaining seconds after calculating hours
    int minutes = seconds / 60;     // Calculate minutes
    seconds %= 60;                  // Remaining seconds after calculating minutes
    long milliseconds = nanoseconds / 1000000;  // Convert nanoseconds to milliseconds

    // Print the formatted elapsed time
    printf("Calculating PI to %d digits of precision took %d Days, %d Hours, %d Minutes, %ld Seconds, and %ld Milliseconds.\n",
           digits, days, hours, minutes, seconds, milliseconds);
}

// ==========================================================================
// Function: calculate_pi
// ==========================================================================

/**
 * Function: calculate_pi
 * ----------------------
 * Calculates Pi to a specified number of decimal places using the Gauss-Legendre Algorithm.
 * The function utilizes high-precision arithmetic with the MPFR library to ensure accuracy.
 *
 * Parameters:
 *  pi     - An mpfr_t variable to store the result of the Pi calculation.
 *  digits - The number of decimal places to calculate for Pi.
 *
 * The function goes through an iterative process to refine the values of Pi
 * based on the algorithm and uses multiple variables to maintain precision
 * throughout the calculation.
 */
void calculate_pi(mpfr_t pi, unsigned long int digits) {
    
    // Set precision based on digits (4 bits per decimal + extra bits for rounding)
    mpfr_prec_t precision = digits * 4 + 128;  // Extra bits to avoid rounding issues

    // ==================================================================
    // Variable Initialization
    // ==================================================================
    
    mpfr_t a, b, t, p, a_next, b_next, t_next, pi_approx;  // Intermediate variables for Gauss-Legendre Algorithm

    // Initialize each variable with the given precision
    mpfr_init2(a, precision);
    mpfr_init2(b, precision);
    mpfr_init2(t, precision);
    mpfr_init2(p, precision);
    mpfr_init2(a_next, precision);
    mpfr_init2(b_next, precision);
    mpfr_init2(t_next, precision);
    mpfr_init2(pi_approx, precision);

    // ==================================================================
    // Initial Values
    // ==================================================================
    
    mpfr_set_ui(a, 1, MPFR_RNDN);        // Set a_0 = 1
    mpfr_sqrt_ui(b, 2, MPFR_RNDN);       // Set b_0 = sqrt(2)
    mpfr_ui_div(b, 1, b, MPFR_RNDN);     // Set b_0 = 1/sqrt(2)
    mpfr_set_ui(t, 1, MPFR_RNDN);        // Set t_0 = 1/4
    mpfr_div_ui(t, t, 4, MPFR_RNDN);
    mpfr_set_ui(p, 1, MPFR_RNDN);        // Set p_0 = 1

    // ==================================================================
    // Iterative Process
    // ==================================================================
    
    // Perform 10 iterations to improve precision (increase iterations for higher precision)
    for (int i = 0; i < 10; i++) {
        
        // Calculate the next 'a' value: a_next = (a + b) / 2
        mpfr_add(a_next, a, b, MPFR_RNDN);
        mpfr_div_ui(a_next, a_next, 2, MPFR_RNDN);

        // Calculate the next 'b' value: b_next = sqrt(a * b)
        mpfr_mul(b_next, a, b, MPFR_RNDN);
        mpfr_sqrt(b_next, b_next, MPFR_RNDN);

        // Calculate the next 't' value: t_next = t - p * (a - a_next)^2
        mpfr_sub(t_next, a, a_next, MPFR_RNDN);
        mpfr_pow_ui(t_next, t_next, 2, MPFR_RNDN);
        mpfr_mul(t_next, t_next, p, MPFR_RNDN);
        mpfr_sub(t_next, t, t_next, MPFR_RNDN);

        // Update 'p' for the next iteration: p = 2 * p
        mpfr_mul_ui(p, p, 2, MPFR_RNDN);

        // Update values for the next iteration
        mpfr_set(a, a_next, MPFR_RNDN);
        mpfr_set(b, b_next, MPFR_RNDN);
        mpfr_set(t, t_next, MPFR_RNDN);
    }

    // ==================================================================
    // Final Pi Calculation
    // ==================================================================
    
    // Calculate Pi using the formula: pi = (a + b)^2 / (4 * t)
    mpfr_add(pi_approx, a, b, MPFR_RNDN);  // pi_approx = a + b
    mpfr_pow_ui(pi_approx, pi_approx, 2, MPFR_RNDN);  // pi_approx = (a + b)^2
    mpfr_mul_ui(t, t, 4, MPFR_RNDN);  // t = 4 * t
    mpfr_div(pi, pi_approx, t, MPFR_RNDN);  // pi = (a + b)^2 / (4 * t)

    // ==================================================================
    // Cleanup
    // ==================================================================
    
    // Clear memory for all MPFR variables used
    mpfr_clear(a);
    mpfr_clear(b);
    mpfr_clear(t);
    mpfr_clear(p);
    mpfr_clear(a_next);
    mpfr_clear(b_next);
    mpfr_clear(t_next);
    mpfr_clear(pi_approx);
}

// ==========================================================================
// Main Function
// ==========================================================================

/**
 * Function: main
 * --------------
 * The entry point of the Pi Time calculation program. This function parses command-line
 * arguments, initializes precision, calculates Pi using the `calculate_pi` function, 
 * and outputs the time taken to compute Pi.
 *
 * Parameters:
 *  argc - Number of command-line arguments.
 *  argv - Array of command-line arguments (including program name).
 *
 * Usage:
 *  <number_of_digits> [--silent]
 *  - number_of_digits: Number of digits of Pi to calculate.
 *  - --silent: Optional flag to suppress the Pi output (prints only the time).
 *
 * Returns:
 *  0 on successful execution, 1 on invalid input or error.
 */
int main(int argc, char *argv[]) {
    
    // Check if the correct number of arguments is provided
    if (argc < 2 || argc > 3) {
        printf("Usage: %s <number_of_digits> [--silent]\n", argv[0]);
        return 1;  // Exit with error code if usage is incorrect
    }

    // Parse command-line arguments
    int num_digits = atoi(argv[1]);  // Convert first argument to the number of digits
    int silent_mode = (argc == 3 && strcmp(argv[2], "--silent") == 0);  // Check if --silent flag is provided

    // ==================================================================
    // Time Measurement and Start Time Output
    // ==================================================================
    
    struct timespec start, end;  // Structs to hold the start and end times
    clock_gettime(CLOCK_REALTIME, &start);  // Get the current time (start time)
    print_time("Start Time", &start);       // Print the start time

    // ==================================================================
    // Pi Calculation
    // ==================================================================

    // Initialize MPFR variable for Pi with higher precision
    mpfr_t pi;
    mpfr_init2(pi, num_digits * 4 + 128);  // Set precision to digits * 4 + extra bits

    // Call the function to calculate Pi with the given number of digits
    calculate_pi(pi, num_digits);

    // ==================================================================
    // Optional Pi Output
    // ==================================================================

    // If not in silent mode, print the calculated Pi value
    if (!silent_mode) {
        char *pi_str = malloc(num_digits + 3);  // Allocate memory for the Pi string
        mpfr_sprintf(pi_str, "%.*Rf", num_digits, pi);  // Convert Pi to string with desired precision
        printf("%s\n", pi_str);  // Print Pi
        free(pi_str);  // Free allocated memory for Pi string
    }

    // ==================================================================
    // Time Measurement and End Time Output
    // ==================================================================

    clock_gettime(CLOCK_REALTIME, &end);  // Get the current time (end time)
    print_time("End Time", &end);         // Print the end time

    // ==================================================================
    // Elapsed Time Output
    // ==================================================================

    print_elapsed_time(&start, &end, num_digits);  // Calculate and print the total elapsed time

    // ==================================================================
    // Cleanup
    // ==================================================================

    mpfr_clear(pi);  // Free MPFR memory used for Pi calculation

    return 0;  // Return 0 to indicate successful execution
}   

Compile & Use

gcc -o pitime pitime.c -lmpfr -lgmp
Usage: ./pitime <number_of_digits> [--silent]

./pitime 100
./pitime 100 --silent

./pitime 1000000 --silent
Start Time: 16:55:07:833
End Time: 16:55:09:233
Calculating PI to 1000000 digits of precision took 0 Days, 0 Hours, 0 Minutes, 1 Seconds, and 400 Milliseconds.

Click HERE for instructions setting up a Linux environment

History & Inspiration

This calculator is designed to evaluate and compare the performance of different platforms, operating systems, and processors as they calculate π to various levels of precision.

AI Prompts

The following were given to Chat-GPT:

  1. Analyze Simle C code and make the following modfications
  2. Remove the delay input and all function in the code related to it
  3. Print on the screen before starting the PI calculation Start Time: HH:SS:MM (hour, second, milisecond)
  4. After PI is calculated and output on the screen, Print the end time the same way
  5. Then on a new line calculate and print the elapsed time in human readable format of “Calculating PI to digits of precision took Days, Hours, Minutes, Seconds, and Milliseconds .”
  6. The modified pi program works great.  I’d like one last request.  Can we add a –silent switch which would still display the start, end, and total time calculations but NOT display all of the digits of PI

This is what it understood:

Program Overview:

This C program calculates the value of PI to a specified number of digits using the Gauss-Legendre algorithm. The program allows for two modes: normal and silent. In normal mode, the program displays the calculated digits of PI. In silent mode, the program suppresses the display of PI digits but still provides the start time, end time, and elapsed time for the calculation.

Detailed Requirements:

1. Inputs:

Number of Digits: The first command-line argument specifies the number of digits of PI to calculate.

Optional –silent Switch: An optional second command-line argument (–silent) can be provided to suppress the display of PI digits.

2. Output:

Start Time: The program should print the start time of the PI calculation in the format HH:MM:SS:MS (hours, minutes, seconds, milliseconds).

PI Digits:

• In Normal Mode: The program prints the calculated digits of PI.

• In Silent Mode: The program does not print the calculated digits of PI.

End Time: The program should print the end time of the PI calculation in the same format as the start time.

Elapsed Time: The program calculates and prints the elapsed time in a human-readable format: “Calculating PI to <X> digits of precision took <X> Days, <X> Hours, <X> Minutes, <X> Seconds, and <X> Milliseconds.”

3. Calculation Method:

• The program calculates PI using the Gauss-Legendre algorithm.

• It iterates through the series until it approximates the desired number of digits.

4. Precision Handling:

• The program sets the precision for GMP calculations based on the number of digits specified (with a safety margin).

5. Error Handling:

• The program checks for valid command-line input. If the input is incorrect (missing number of digits or extra arguments without –silent), it displays a usage message and exits.

6. Performance:

• The program is designed to efficiently calculate PI for a potentially large number of digits, though it uses a basic algorithm that may not be optimal for extremely high precision.