Condoning Another Pi Day

Condoning Another Pi Day
Photo by Elena Sambros / Unsplash

11/24 wasn't just Black Friday. It became another Pi day around here.

We had pie for breakfast, leftover from Thanksgiving. We had Quiche, an egg pie, for lunch. Finally, for dinner, we had ourselves some pizza pie from a local woodfire place that was delicious. For dessert, yep, more pie.

Pie day needed to be condoned, however.

1124

Those numbers definitely appear in Pi. I posit that all numeric sequence appear in Pi somewhere due to the nature of infinity and all.

I cheated and found them in a couple of sources that store the value of Pi to millions of places and doing a simple text search

It's there.

But where?

I guessed it was in the first 10k to 100k places.

But I wanted to tell you, dear reader, that it was Pi day due to 1124 appearing at position n.

Calculating Pi?

Could I calculate that myself?

I could use the Chudnovsky algorithm.

There's a python script in the Wiki page:

import decimal


def binary_split(a, b):
    if b == a + 1:
        Pab = -(6*a - 5)*(2*a - 1)*(6*a - 1)
        Qab = 10939058860032000 * a**3
        Rab = Pab * (545140134*a + 13591409)
    else:
        m = (a + b) // 2
        Pam, Qam, Ram = binary_split(a, m)
        Pmb, Qmb, Rmb = binary_split(m, b)
        
        Pab = Pam * Pmb
        Qab = Qam * Qmb
        Rab = Qmb * Ram + Pam * Rmb
    return Pab, Qab, Rab


def chudnovsky(n):
    P1n, Q1n, R1n = binary_split(1, n)
    return (426880 * decimal.Decimal(10005).sqrt() * Q1n) / (13591409*Q1n + R1n)


print(chudnovsky(2))  # 3.141592653589793238462643384

So, of course I could.

This method wouldn't yield the precision I need.

You could do something like this to get to a certain precision:

from decimal import Decimal, getcontext
import math

def calculate_pi(precision):
    """
    Calculate pi using the Chudnovsky algorithm to the specified precision.
    """
    getcontext().prec = precision + 1  # Set precision
    C = 426880 * Decimal(math.sqrt(10005))
    M = 1
    L = 13591409
    X = 1
    K = 6
    S = L

    for i in range(1, precision):
        M = (M * (K ** 3 - 16 * K)) // i ** 3 
        L += 545140134
        X *= -262537412640768000
        S += Decimal(M * L) / X
        K += 12

    pi = C / S
    return str(pi)[:precision]

# Calculate Pi to 100 digits
pi_100_digits = calculate_pi(100)
pi_100_digits

pi_100000_digits = calculate_pi(100000)
pi_100000_digits

It's not the most efficient solution. I think I might have made repl.it mad when I tried to get 100,000 digits with the above. I gave up trying to even get to 10,000 digits in other 20 minutes of waiting.

It's obviously not efficient - just look at it!

So, that didn't seem like an easy enough path and I really didn't want to spend all that much time / money on solving this problem. Also, I'm not a Python expert by any means.

Search a Pre-calculated Dataset

There are sources of this data around the internet, one need only do a little searching.

A couple of examples can be found here and here, so, easy enough, I have me some pi to search.

To find the digits with an existing dataset I figured I could:

  • Obtain a Pi Dataset - That was easy
  • Read the Dataset - pick a programming language and read the dataset - Doesn't seem too hard either - Python, Rust, Go, etc. all seem like they'd be well suited
  • Search for the Sequence - This also seems relatively easy, most languages have built in search methods so it's not like I need to implement a fancy search algorithm myself, this isn't some leetcode challenge
  • Output the Position - This also seems relatively easy, find the first instance of the sequence I'm seeking and output its position. Bingo.

So, back to repl.it I went looking to find the sequence in a pre-calculated dataset, like a chump who was too lazy to calculate Pi. Who has time to calculate Pi? I know it can be calculated but others have already done so, so I guess that's good enough for me.

My first attempt was to whip up something in Rust because I'm always looking for excuses to use Rust. At first I used reqwest and tokio but, unfortunately, that resulted in some OpenSSL issues due to the dependency on OpenSSL that Repl.it didn't meet and, so, I opted for a least resistant path and switched to a more simplistic blocking solution by using ureq and boom💥:

Sequence found at position: 25705

The code is simple:

fn find_sequence_in_url(url: &str, sequence: &str) -> Result<Option<usize>, Box<dyn std::error::Error>> {
    let response = ureq::get(url).call()?.into_string()?;

    Ok(response.find(sequence))
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = "https://www.mathsisfun.com/numbers/images/pi-million.txt";
    let sequence = "1124";

    match find_sequence_in_url(url, sequence)? {
        Some(position) => println!("Sequence found at position: {}", position),
        None => println!("Sequence not found"),
    }

    Ok(())
}

But it's effective enough.

So, now I know that yesterday was another Pi day and that pattern appears only 25705 places into to Pi.

So, literally every day can be Pi day!

Enjoy your Pie!