Site icon Embarcadero RAD Studio, Delphi, & C++Builder Blogs

How to View Digits of Pi in Real-Time with Delphi for Pi Day 2024

Here’s a guest blog post by Jim McKeeth, Embarcadero MVP and US Director of GDK Software. To celebrate the recent π day 2024 with a Delphi flavor I revisited the previous sample I made with Rudy’s Big Numbers library adding the functionality to view the digits of π in real time as they are being generated. Previously it would generate any number of digits you wanted, but you could only see them when it was all done.

What is Rudy’s Big Numbers Library?

Originally developed by the late Rudy Velthuis, the library adds support to Delphi for Big Integer, Big Decimal, and Big Rational numbers to Delphi. How big? Well, how much memory do you have? There really isn’t an upper limit.

The library is really easy to work with, and performs surprisingly well. There are a variety of samples, demos, and unit tests. The BigRational type is marked as experimental, and BigIntegers tends to perform better than BigDecimals.

How to Generate Pi with Delphi?

Last year I created the sample to calculate Pi with Delphi. It supports both the Chudnovsky and Bailey Borwein Plouffe (BBP) algorithms. Since BBP is faster, and it is a spigot that generates the digits in sequence, I thought it made more sense to add a call back to that algorithm. The callback returns 64 digits at a time, but you could easily modify that chunk size.

Watching the Digits of Pi in Real-Time

I find watching the digits of Pi appear both Zen like and exciting. Currently only the console app supports the callback function, but I plan to update the FireMonkey app in the future too.

The Bailey-Borwein-Plouffe Pi Code in Delphi

The updated code with the callback function.

function BBPpi(Places: UInt64; CallBack: TChunkCallback = nil): TDigits;
// Bailey-Borwein-Plouffe
begin
  SetLength(Result, Places);

  var idx: Uint64 := 0;
  var q := BigInteger.One;
  var r := BigInteger.Zero;
  var t := BigInteger.One;
  var k := BigInteger.One;
  var n := BigInteger.Create(3);
  var l := BigInteger.Create(3);

  var buffer: TDigits;
  SetLength(buffer, CallbackChunkSize);
  var bufferIdx: Integer := 0;

  while true do
  begin
    if 4*q+r-t < n*t then
    begin
      result[idx] := n.AsInt64; // It is just a byte
      inc(idx);

      if Assigned(Callback) then
      begin
        buffer[bufferIdx] := n.AsInt64;
        inc(bufferIdx);
        // Check if buffer is full, then call callback and reset bufferIdx
        if bufferIdx = CallbackChunkSize then
        begin
          Callback(buffer); // Call the callback with the buffer
          bufferIdx := 0; // Reset buffer index
        end;
      end;

      if idx >= places then break;
      var newR := 10 * (r - n * t);
      n := (10 * (3 * q + r)) div t - 10 * n;
      q := q * 10;
      r := newR;
    end
    else
    begin
      var newR := (2 * q + r) * l;
      var newN := (q * (7 * k)+2+(r * l)) div (t * l);
      q := q * k;
      t := t * l;
      l := l + 2;
      k := k + 1;
      n := newN;
      r := newR;
    end;
  end;

  // Handle remaining buffer
  if (bufferIdx > 0) and Assigned(CallBack) then
  begin
    SetLength(buffer, bufferIdx); // Resize buffer to actual used size before callback
    CallBack(buffer);
  end;
end;

And here is an example of calling the code and then outputting the digits one at a time to the console

var firstChunk: Boolean = True;
procedure WritelnCallBack(Chunk: TDigits);
begin
  var digits: String;
  if firstChunk then
  begin
    digits := DigitsToString(Chunk).Insert(1,'.');
    firstChunk := False;
  end
  else
    digits := DigitsToString(Chunk);

  // slow down the output for demonstration purposes
  for var i := low(digits) to High(digits) do
  begin
    Write(digits[i]);
    sleep(100);
  end;
end;

const Places = 10000;
begin
  BBPpi(Places, WritelnCallBack);
  Writeln;
end.

It currently uses an anonymous method reference, which I know introduces a slight amount of overhead, but I’ve not benchmarked it to see how much or if it is worth changing the callback type. I figured since the purpose was to view the digits then a little slow down was necessary.

I also added a unit test to validate that the output from the callback matched the result of the function call.

procedure BBPpiTest.CompareCallbackToResult;
begin
  var CallBackString := '';
  var CallBackStringBuilder: TChunkCallBack := procedure(Chunk: TDigits)
  begin
    CallBackString := CallBackString + DigitsToString(Chunk);
  end;

  var calcPi := DigitsToString(BBPpi(100, CallBackStringBuilder));
  Assert.AreEqual(calcPi, CallBackString);
end;

Where Can I Get that Cool Vaporwave Athena Pi Day Wallpaper?

I’m glad you asked! I upscaled it to 4K, and there are a few variations….


Do you want to try some of these examples for yourself? Why not download a free trial of the latest version of RAD Studio with Delphi?

This article was written by an Embarcadero MVP.

Exit mobile version