Search code examples
c++cmathpi

What is the best rational approximation to pi using a 64-bit numerator and denominator?


What's the most accurate rational pair for Pi representable with two 64 bit integers? Feel free to include other int types if you'd like.

Here's what I came up with, but I'm sure it can get more accurate since the denominator can get substantially bigger - I'm just thinking in base-10. I'm pretty sure the numerator should be something like uint64 max.

// c++
inline constexpr auto pi_num = 3141592653589793238ull;
inline constexpr auto pi_den = 1000000000000000000ull;
// c
const unsigned long long pi_num = 3141592653589793238ull;
const unsigned long long pi_den = 1000000000000000000ull;

Solution

  • You can use continued fractions to get excellent approximations of an irrational number. If you haven't encountered continued fractions before, it's a way of writing a number as nested series of fractions of the form

    a sample continued fraction

    Adding in more and more terms into a continued fraction gives a better and better approximation as a rational number.

    The continued fraction of π is

    [3; 7, 15, 1, 292, 1, 1, 1, 2, 1, 3, 1, 14, 2, 1, 1, 2, 2, 2, 2, 1, 84, 2, 1, 1, 15, 3, 13, 1, 4, 2, 6, 6, 99, 1, 2, 2, 6, 3, 5, 1, 1, 6, 8, 1, 7, 1, 2, 3, 7, 1, 2, 1, 1, 12, 1, 1, 1, 3, 1, 1, 8, 1, 1, 2, 1, 6, 1, 1, 5, 2, 2, 3, 1, 2, 4, 4, 16, 1, 161, ...]
    

    and so we can write a little Python script to compute approximations based on this continued fraction representation, which is shown here:

    from fractions import *
    
    digits = [3, 7, 15, 1, 292, 1, 1, 1, 2, 1, 3, 1, 14, 2, 1, 1, 2, 2, 2, 2, 1, 84, 2, 1, 1, 15, 3, 13, 1, 4, 2, 6, 6, 99, 1, 2, 2, 6, 3, 5, 1, 1, 6, 8, 1, 7, 1, 2, 3, 7, 1, 2, 1, 1, 12, 1, 1, 1, 3, 1, 1, 8, 1, 1, 2, 1, 6, 1, 1, 5, 2, 2, 3, 1, 2, 4, 4, 16, 1, 161]
    
    for i in range(len(digits)):
        # Start with the last digit
        f = Fraction(digits[i]);
    
        # Keep rewriting it as term + 1 / prev
        for j in range(i-1, -1, -1):
            f = digits[j] + 1 / f
        
        # Stop if we overshoot
        if f.numerator >= 2**64 or f.denominator >= 2**64: break
        
        # Print the approximation we found
        print(f)
    

    This prints continued fractions with better and better approximations until we overshoot what fits in a 64-bit integer. Here's the output:

    3
    22/7
    333/106
    355/113
    103993/33102
    104348/33215
    208341/66317
    312689/99532
    833719/265381
    1146408/364913
    4272943/1360120
    5419351/1725033
    80143857/25510582
    165707065/52746197
    245850922/78256779
    411557987/131002976
    1068966896/340262731
    2549491779/811528438
    6167950454/1963319607
    14885392687/4738167652
    21053343141/6701487259
    1783366216531/567663097408
    3587785776203/1142027682075
    5371151992734/1709690779483
    8958937768937/2851718461558
    139755218526789/44485467702853
    428224593349304/136308121570117
    5706674932067741/1816491048114374
    6134899525417045/1952799169684491
    30246273033735921/9627687726852338
    66627445592888887/21208174623389167
    430010946591069243/136876735467187340
    2646693125139304345/842468587426513207
    

    This last approximation is the best approximation of π, I believe, that fits into 64-bit integers. (It's possible that there's a better one that appears between this denominator and the next denominator that you'd get that overflows a 64-bit integer, but this is still pretty close!) Therefore, you'd want

    const uint64_t pi_num   = 2646693125139304345u;
    const uint64_t pi_denom = 842468587426513207u;
    

    This source reports that this approximation is accurate to 37 decimal places (!):

    3.14159265358979323846264338327950288418 (approximation)
    3.14159265358979323846264338327950288419 (actual)
    

    This should be more than enough for what you're aiming to do. (Unless, of course, you're trying to set a record for finding digits of π or something like that. ^_^)