Jump to content

Continued fraction convergents

From Rosetta Code
Continued fraction convergents is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Given a positive real number, if we truncate its continued fraction representation at a certain depth, we obtain a rational approximation to the real number. The sequence of successively better such approximations is its convergent sequence.

Problem:

  • Given a positive rational number , specified by two positive integers , output its entire sequence of convergents.
  • Given a quadratic real number , specified by integers , where is not a perfect square, output the first convergents when given a positive number .

The output format can be whatever is necessary to represent rational numbers, but it probably should be a 2-tuple of integers.

For example, given , sincethe program should output .

A simple check is to do this for the golden ratio , that is, , which should output .

Print the results for 415/93, 649/200, , , and the golden ratio.

References and related tasks


FreeBASIC

Function convergents(x As Double, maxcount As Integer) As String
    Dim As Double epsilon = 1e-9
    Dim As Integer components(maxcount)
    Dim As String rationals = ""
    Dim As Integer cont = 0
    Dim As Double fpart, ipart
    
    While cont < maxcount
        ipart = Int(x)
        fpart = x - ipart
        components(cont) = ipart
        cont += 1
        If Abs(fpart) < epsilon Then Exit While
        x = 1 / fpart
    Wend
    
    Dim As Integer numa = 1, denoma = 0, numb = components(0), denomb = 1
    rationals &= Str(numb)
    rationals &= Iif(denomb <> 1, "/" & Str(denomb), " ")
    For i As Integer = 1 To cont - 1
        Dim As Integer comp = components(i)
        Dim As Integer temp_num = numa + comp * numb
        Dim As Integer temp_den = denoma + comp * denomb
        numa = numb
        denoma = denomb
        numb = temp_num
        denomb = temp_den
        rationals &= Str(numb)
        rationals &= Iif(denomb <> 1, "/" & Str(denomb), " ")
    Next
    Return rationals
End Function

Function parseFraction(fraction As String) As Double
    Dim As Integer posic = Instr(fraction, "/")
    If posic > 0 Then
        Dim As Double numerator = Val(Left(fraction, posic - 1))
        Dim As Double denominator = Val(Mid(fraction, posic + 1))
        Return numerator / denominator
    Else
        Return Val(fraction)
    End If
End Function

Dim As String tests(5, 2) = { _
{"415/93", "415/93"}, _
{"649/200", "649/200"}, _
{"sqrt(2)", Str(Sqr(2))}, _
{"sqrt(5)", Str(Sqr(5))}, _
{"golden ratio", Str((Sqr(5) + 1) / 2)} }

Print "The continued fraction convergents for the following (maximum 8 terms) are:"
For i As Integer = 0 To 4
    Dim As String s = tests(i, 0)
    Dim As Double x = parseFraction(tests(i, 1))
    Print Space(15 - Len(s)) & s & " = " & convergents(x, 8)
Next

Sleep
Output:
The continued fraction convergents for the following (maximum 8 terms) are:
         415/93 = 4 9/2 58/13 415/93
        649/200 = 3 13/4 159/49 490/151 649/200
        sqrt(2) = 1 3/2 7/5 17/12 41/29 99/70 239/169 577/408
        sqrt(5) = 2 9/4 38/17 161/72 682/305 2889/1292 12238/5473 51841/23184
   golden ratio = 1 2 3/2 5/3 8/5 13/8 21/13 34/21

Julia

function convergents(x::Real, maxcount::T) where T <: Integer
    components = T[]
    rationals = Rational{T}[]
    for _ in 1:maxcount
        fpart, ipart = modf(x)
        push!(components, T(ipart))
        fpart == 0 && break
        x = inv(fpart)
    end
    numa, denoma, numb, denomb = T(1), T(0), T(components[begin]), T(1)
    push!(rationals, numb // denomb)
    for comp in components[begin+1:end]
        numa, denoma, numb, denomb = numb, denomb, numa + comp * numb, denoma + comp * denomb
        push!(rationals, numb // denomb)
    end
    return rationals
end   

const tests = [("415/93", 415//93), ("649/200", 649//200), ("sqrt(2)", sqrt(2)),
               ("sqrt(5)", sqrt(5)), ("golden ratio", (sqrt(5) + 1) / 2)]

println("The continued fraction convergents for the following (maximum 8 terms) are:")
for (s, x) in tests
    println(lpad(s, 15), " = ", convergents(x, 8))
end
Output:
The continued fraction convergents for the following (maximum 8 terms) are:
         415/93 = Rational{Int64}[4, 9//2, 58//13, 415//93]
        649/200 = Rational{Int64}[3, 13//4, 159//49, 649//200]
        sqrt(2) = Rational{Int64}[1, 3//2, 7//5, 17//12, 41//29, 99//70, 239//169, 577//408]
        sqrt(5) = Rational{Int64}[2, 9//4, 38//17, 161//72, 682//305, 2889//1292, 12238//5473, 51841//23184]
   golden ratio = Rational{Int64}[1, 2, 3//2, 5//3, 8//5, 13//8, 21//13, 34//21]

Perl

Translation of: Julia
# 20240906 Perl programming solution

use strict;
use warnings;

sub convergents {
   my ($x, $maxcount)   = @_;
   my ($epsilon, @comp) = 1e-9;
   for (1..$maxcount) {
      push(@comp, my $ix = int($x)); 
      my $fpart = $x - $ix;
      abs($fpart) < $epsilon ? ( last ) : ( $x = 1 / $fpart )
   }
   my ($numa, $denoma, $numb, $denomb) = ( 1, 0, $comp[0], 1 );
   my @return = ( "$numb/$denomb" ) ;
   for my $i (1 .. $#comp) {
      (  $numa, $denoma, $numb                  , $denomb                     )
      = ($numb, $denomb, $numa + $comp[$i]*$numb, $denoma + $comp[$i]*$denomb );
      push(@return, "$numb/$denomb") 
   }
   @return
}

my @tests = ( 
   [ "415/93", 415/93 ],   [ "649/200", 649/200 ], [ "sqrt(2)", sqrt(2) ], 
   [ "sqrt(5)", sqrt(5) ], [ "golden ratio", (sqrt(5) + 1) / 2 ]
);

print "The continued fraction convergents for the following (maximum 8 terms) are:\n";
foreach my $test (@tests) {
   my ($s, $x) = @$test;
   printf "%15s = %s\n", $s, join(' ', convergents($x, 8));
}

You may Attempt This Online!

Output:
The continued fraction convergents for the following (maximum 8 terms) are:
         415/93 = 4/1 9/2 58/13 415/93
        649/200 = 3/1 13/4 159/49 490/151 649/200
        sqrt(2) = 1/1 3/2 7/5 17/12 41/29 99/70 239/169 577/408
        sqrt(5) = 2/1 9/4 38/17 161/72 682/305 2889/1292 12238/5473 51841/23184
   golden ratio = 1/1 2/1 3/2 5/3 8/5 13/8 21/13 34/21

Phix

Translation of: Wren
with javascript_semantics
requires("1.0.5") -- mpq_get_d() added
include mpfr.e

procedure cfcRat(integer m, n)
    sequence p = {mpq_init(0), mpq_init(1)},
             q = {mpq_init(1), mpq_init(0)},
             s = {},
             t = sprintf("%d/%d",{m,n})
    mpq r = mpq_init_set_si(m, n),
      rem = mpq_init_set(r)
    while true do
        mpq whole = mpq_init_set_si(trunc(mpq_get_d(rem)))
        mpq {pn, qn, sn} = mpq_inits(3)
        mpq_mul(pn,whole,p[-1])
        mpq_add(pn,pn,p[-2])
        mpq_mul(qn,whole,q[-1])
        mpq_add(qn,qn,q[-2])
        mpq_div(sn,pn,qn)
        p &= pn
        q &= qn
        s &= {mpq_get_str(sn)}
        if mpq_cmp(r,sn)=0  then exit end if
        mpq_sub(rem,rem,whole)
        mpq_inv(rem,rem)
    end while
    printf(1,"%14s = %s\n",{t,join(s)})
end procedure

procedure cfcQuad(string d, integer a, b, m, n, k)
    sequence p = {0, 1},
             q = {1, 0},
             s = {}
    atom rem = (sqrt(a)*b + m) / n
    for i=1 to k do
        integer whole = trunc(rem),
                pn = whole * p[-1] + p[-2],
                qn = whole * q[-1] + q[-2]
        mpq sn = mpq_init_set_si(pn, qn)
        p &= pn
        q &= qn
        s &= {mpq_get_str(sn)}
        rem = 1/(rem-whole)
    end for
    printf(1,"%14s = %s\n",{d,join(s)})
end procedure

printf(1,"The continued fraction convergents for the following (maximum 8 terms) are:\n")
cfcRat(415,93)
cfcRat(649,200)
cfcQuad("sqrt(2)",2, 1, 0, 1, 8)
cfcQuad("sqrt(5)",5, 1, 0, 1, 8)
cfcQuad("golden ratio",5, 1, 1, 2, 8)
Output:
The continued fraction convergents for the following (maximum 8 terms) are:
        415/93 = 4 9/2 58/13 415/93
       649/200 = 3 13/4 159/49 649/200
       sqrt(2) = 1 3/2 7/5 17/12 41/29 99/70 239/169 577/408
       sqrt(5) = 2 9/4 38/17 161/72 682/305 2889/1292 12238/5473 51841/23184
  golden ratio = 1 2 3/2 5/3 8/5 13/8 21/13 34/21

Python

Translation of: FreeBASIC
#!/usr/bin/python3

def convergents(x, maxcount):
    epsilon = 1e-9
    components = []
    for _ in range(maxcount):
        ix = int(x)
        components.append(ix)
        fpart = x - ix
        if abs(fpart) < epsilon:
            break
        x = 1 / fpart

    numa, denoma = 1, 0
    numb, denomb = components[0], 1
    rationals = [f"{numb}/{denomb}" if denomb != 1 else f"{numb}"]
    for comp in components[1:]:
        numa, denoma, numb, denomb = numb, denomb, numa + comp * numb, denoma + comp * denomb
        rationals.append(f"{numb}/{denomb}" if denomb != 1 else f"{numb}")
    return rationals


if __name__ == "__main__":
    tests = [
        ("415/93", 415/93), 
        ("649/200", 649/200), 
        ("sqrt(2)", 2**0.5), 
        ("sqrt(5)", 5**0.5), 
        ("golden ratio", (5**0.5 + 1) / 2)
    ]

    print("The continued fraction convergents for the following (maximum 8 terms) are:")
    for s, x in tests:
        print(f"{s:>15} = {' '.join(convergents(x, 8))}")
Output:
Same as FreeBASIC entry.

Raku

Translation of: Julia
# 20240210 Raku programming solution

sub convergents(Real $x is copy, Int $maxcount) {
   my @components = gather for ^$maxcount {
      my $fpart = $x - take $x.Int;
      $fpart == 0 ?? ( last ) !! ( $x = 1 / $fpart )
   }
   my ($numa, $denoma, $numb, $denomb) = 1, 0, @components[0], 1;
   return [ Rat.new($numb, $denomb) ].append: gather for @components[1..*] -> $comp {
      ( $numa, $denoma, $numb                , $denomb                  ) 
      = $numb, $denomb, $numa + $comp * $numb, $denoma + $comp * $denomb;
      take Rat.new($numb, $denomb);
   }
}

my @tests = [ "415/93", 415/93, "649/200", 649/200, "sqrt(2)", sqrt(2),
              "sqrt(5)", sqrt(5), "golden ratio", (sqrt(5) + 1) / 2     ];

say "The continued fraction convergents for the following (maximum 8 terms) are:";
for @tests -> $s, $x {
   say $s.fmt('%15s') ~ " = { convergents($x, 8).map: *.nude.join('/') } ";
}
Output:
The continued fraction convergents for the following (maximum 8 terms) are:
         415/93 = 4/1 9/2 58/13 415/93 
        649/200 = 3/1 13/4 159/49 649/200 
        sqrt(2) = 1/1 3/2 7/5 17/12 41/29 99/70 239/169 577/408 
        sqrt(5) = 2/1 9/4 38/17 161/72 682/305 2889/1292 12238/5473 51841/23184 
   golden ratio = 1/1 2/1 3/2 5/3 8/5 13/8 21/13 34/21 

You may Attempt This Online!

Sidef

func num2cfrac(n, r) {
    gather {
        r.times {
            n = 1/((n - take(n.floor.int)) || break)
        }
    }
}

func convergents(x, n) {
    var cfrac = num2cfrac(x, n)

    var(n1, n2) = (0, 1)
    var(d1, d2) = (1, 0)

    gather {
        for z in (cfrac) {
            (n1, n2) = (n2, n2*z + n1)
            (d1, d2) = (d2, d2*z + d1)
            take(n2/d2)
        }
    }
}

var tests = ["415/93", 415/93, "649/200", 649/200, "sqrt(2)", sqrt(2),
             "sqrt(5)", sqrt(5), "golden ratio", (sqrt(5) + 1) / 2   ]

var terms = 8
say "The continued fraction convergents for the following (maximum #{terms} terms) are:"
tests.each_slice(2, {|s,x|
    printf("%15s = %s\n", s, convergents(x, terms).map { .as_frac }.join(' '))
})
Output:
The continued fraction convergents for the following (maximum 8 terms) are:
         415/93 = 4/1 9/2 58/13 415/93
        649/200 = 3/1 13/4 159/49 649/200
        sqrt(2) = 1/1 3/2 7/5 17/12 41/29 99/70 239/169 577/408
        sqrt(5) = 2/1 9/4 38/17 161/72 682/305 2889/1292 12238/5473 51841/23184
   golden ratio = 1/1 2/1 3/2 5/3 8/5 13/8 21/13 34/21

Wren

Library: Wren-rat

The following is loosely based on the Python code here. If a large number of terms were required for quadratic real numbers, then one might need to use 'arbitrary precision' arithmetic to minimize round-off errors when converting between floats and rationals.

import "./rat" for Rat

var cfcRat = Fn.new { |m, n|
    var p = [0, 1]
    var q = [1, 0]
    var s = []
    var r = Rat.new(m, n)
    var rem = r
    while (true) {
        var whole = rem.truncate
        var frac  = rem.fraction
        var pn = whole * p[-1] + p[-2]
        var qn = whole * q[-1] + q[-2]
        var sn = pn / qn
        p.add(pn)
        q.add(qn)
        s.add(sn)
        if (r == sn) break
        rem = frac.inverse
    }
    return s
}

var cfcQuad = Fn.new { |a, b, m, n, k|
    var p = [0, 1]
    var q = [1, 0]
    var s = []
    var rem = (a.sqrt * b + m) / n
    for (i in 1..k) {
        var whole = rem.truncate
        var frac  = rem.fraction
        var pn = whole * p[-1] + p[-2]
        var qn = whole * q[-1] + q[-2]
        var sn = Rat.new(pn, qn)
        p.add(pn)
        q.add(qn)
        s.add(sn)
        rem = 1 / frac
    }
    return s
}

System.print("The continued fraction convergents for the following (maximum 8 terms) are:")
System.print("415/93  = %(cfcRat.call(415, 93))")
System.print("649/200 = %(cfcRat.call(649, 200))")
System.print("√2      = %(cfcQuad.call(2, 1, 0, 1, 8))") 
System.print("√5      = %(cfcQuad.call(5, 1, 0, 1, 8))")
System.print("phi     = %(cfcQuad.call(5, 1, 1, 2, 8))")
Output:
The continued fraction convergents for the following (maximum 8 terms) are:
415/93  = [4/1, 9/2, 58/13, 415/93]
649/200 = [3/1, 13/4, 159/49, 649/200]
√2      = [1/1, 3/2, 7/5, 17/12, 41/29, 99/70, 239/169, 577/408]
√5      = [2/1, 9/4, 38/17, 161/72, 682/305, 2889/1292, 12238/5473, 51841/23184]
phi     = [1/1, 2/1, 3/2, 5/3, 8/5, 13/8, 21/13, 34/21]
Cookies help us deliver our services. By using our services, you agree to our use of cookies.