Good rational approximations of pi

22

1

Write a program that prints out all the good rational approximations of pi with denominator < 1000000, in increasing denominator order. a/b is a "good rational approximation" of pi if it is closer to pi than any other rational with denominator no bigger than b.

The output should have 167 lines total, and start and end like this:

3/1
13/4
16/5
19/6
22/7
179/57
...
833719/265381
1146408/364913
3126535/995207

Shortest program wins.

Keith Randall

Posted 2011-12-09T22:22:52.467

Reputation: 19 865

Answers

23

Golfscript, 71 70 69 chars

2\!:^2^..292^15.2/3]{(.)2/.9>+{\+.((}*;.}do;;]-1%{^0@{2$*+\}/"/"\n}/;

(Assumes that you don't pass it anything on stdin)

I don't want to hear any more whinging by people who don't have built-in constants for pi. I don't even have floating point numbers!

See http://en.wikipedia.org/wiki/Continued_fraction#Best_rational_approximations for the background.

# No input, so the stack contains ""
2\!:^2^..292^15.2/3]
# ^ is used to store 1 because that saves a char by allowing the elimination of whitespace
# Otherwise straightforward: stack now contains [2 1 2 1 1 1 292 1 15 7 3]
# Pi as a continued fraction is 3+1/(7+1/(15+1/(...)))
# If you reverse the array now on the stack you get the first 10 continuants followed by 2
# (rather than 3)
# That's a little hack to avoid passing the denominator 1000000

{
    # Stack holds: ... [c_n c_{n-1} ... c_0]
    (.)2/.9>+
    # Stack holds ... [c_{n-1} ... c_0] c_n (1+c_n)/2+((1+c_n)/2 > 9 ? 1 : 0)
    # (1+c_n)/2 > 9 is an ad-hoc approximation of the "half rule"
    # which works in this case but not in general
    # Let k = (1+c_n)/2+((1+c_n)/2 > 9 ? 1 : 0)
    # We execute the next block k times
    {
        # ... [c_{n-1} ... c_0] z
        \+.((
        # ... [z c_{n-1} ... c_0] [c_{n-1} ... c_0] z-1
    }*
    # So we now have ... [c_n c_{n-1} ... c_0] [(c_n)-1 c_{n-1} ... c_0] ...
    #                    [(c_n)-k+1 c_{n-1} ... c_0] [c_{n-1} ... c_0] c_n-k
    ;
    # Go round the loop until the array runs out
    .
}do

# Stack now contains all the solutions as CFs in reverse order, plus two surplus:
# [2 1 2 1 1 1 292 1 15 7 3] [1 2 1 1 1 292 1 15 7 3] ... [6 3] [5 3] [4 3] [3] [2] []
# Ditch the two surplus ones, bundle everything up in an array, and reverse it
;;]-1%

# For each CF...
{
    # Stack holds ... [(c_n)-j c_{n-1} ... c_0]
    # We now need to convert the CF into a rational in canonical form
    # We unwind from the inside out starting with (c_n)-j + 1/infinity,
    # representing infinity as 1/0
    ^0@
    # ... 1 0 [c_n-j c_{n-1} ... c_0]
    # Loop over the terms of the CF
    {
        # ... numerator denominator term-of-CF
        2$*+\
        # ... (term-of-CF * numerator + denominator) numerator
    }/

    # Presentation
    "/"\n
    # ... numerator "/" denominator newline
}/

# Pop that final newline to avoid a trailing blank line which isn't in the spec
;

Peter Taylor

Posted 2011-12-09T22:22:52.467

Reputation: 41 901

1Well, technically GolfScript has both floating point numbers and constant for PI. It's called "#{Math.PI}". – Konrad Borowski – 2012-05-28T17:52:20.680

2@GlitchMr, in what way is a string a floating point number? – Peter Taylor – 2012-08-17T12:17:29.567

I'd really like to see this unrolled with comments. – primo – 2012-11-29T11:23:08.547

Amazing. The very first line 2\!:^2^..292^15.2/3] already blew my mind. – primo – 2012-12-10T15:27:46.383

@PeterTaylor Tied. Can we do better?

– Eelvex – 2014-02-20T06:09:51.333

11

Mathematica, 67 63

This isn't going to be fast, but I believe it is technically correct.

Round[π,1/Range@1*^6]//.x_:>First/@Split[x,#2≥#&@@Abs[π-{##}]&]

Round[π, x] gives the closest fraction to π in steps of x. This is "listable" so Round[π,1/Range@1*^6] does this for all fractions down to 1/10^6 in order. The resulting list with many "bad" rational approximations is then repeatedly (//.) processed by removing any elements which are farther from π than the preceding one.

Mr.Wizard

Posted 2011-12-09T22:22:52.467

Reputation: 2 481

Mathematica beating GolfScript. Neat. – SpellingD – 2012-11-29T17:47:53.510

In 61: Select[Round[f=Pi,1/Range@1*^6],If[#<f,f=#;True]&@Abs[#-Pi]&] ... but useless given the dominant bias – Dr. belisarius – 2014-02-21T03:28:59.410

Yarr, Matie. Thar be magic in this code. – Michael Stern – 2014-02-21T03:31:51.940

Pretty cool, but I can't test it because I don't have Mathematica. – Keith Randall – 2011-12-20T17:35:18.277

@Keith, here's the logic. Round[Pi, x] gives the closest fraction to Pi in steps of x. This is "listable" so Round[Pi,1/Range@1*^6] does this for all fractions down to 1/10^6 in order. The resulting list with many "bad" rational approximations is then repeatedly (//.) processed by removing any elements which are farther from pi than the preceding one. – Mr.Wizard – 2011-12-20T18:41:10.947

7

Perl, 77 chars

$e=$p=atan2 0,-1;($f=abs$p-($==$p*$_+.5)/$_)<$e&&($e=$f,say"$=/$_")for 1..1e6

A minor challenge is that Perl doesn't have a built-in π constant available, so I first had to calculate it as atan2(0,-1). I'm sure this will be beaten by languages more suited for the job, but it's not bad for a language mainly designed for text processing.

Ilmari Karonen

Posted 2011-12-09T22:22:52.467

Reputation: 19 513

1You could change 999999 to 1e6 and save 3 chars. – Toto – 2011-12-10T10:28:42.540

@M42: Thanks! Down to 82 chars now. – Ilmari Karonen – 2011-12-10T16:05:50.243

Really nice, $= to get integer. Sorry, I can't upvote twice. – Toto – 2011-12-11T13:36:41.847

I can't get this to run: String found where operator expected at prog.pl line 1, near "say"$=/$_"" – Keith Randall – 2011-12-20T17:25:51.350

@KeithRandall: You need the -M5.01 switch (and Perl 5.10.0 or later) for the say command. Sorry for not mentioning that. – Ilmari Karonen – 2011-12-20T22:34:20.157

5

Python, 96 93 89 characters

a=b=d=1.
while b<=1e6:
 e=3.14159265359-a/b;x=abs(e)
 if x<d:print a,b;d=x
 a+=e>0;b+=e<0

Python, 95 93 characters, different algorithm

p=3.14159265359;d=1
for a in range(3,p*1e6):
 b=round(a/p);e=abs(p-a/b)
 if e<d:print a,b;d=e

note: It was less characters to write p=3.14159265359; than from math import*. Darn those wordy imports!

Steven Rumbalski

Posted 2011-12-09T22:22:52.467

Reputation: 1 353

1Some shortenings: 1.0 -> 1., 10**6 -> 1e6 – Keith Randall – 2011-12-10T06:34:19.480

I have updated with your improvements. Thanks much. – Steven Rumbalski – 2011-12-10T06:40:48.097

@KeithRandall, but the second of those makes the output violate the spec. – Peter Taylor – 2011-12-10T07:32:59.300

In second approach there is no need for variable p. That is 4 chars. – Ante – 2011-12-10T14:23:48.790

@PeterTaylor: I don't understand. How does it violate the spec? – Steven Rumbalski – 2011-12-11T00:48:20.127

@Ante: Added your improvement. Moved it to be my first answer. Thanks. – Steven Rumbalski – 2011-12-11T00:49:14.210

You can still save one char by using < instead of <=. – Ilmari Karonen – 2011-12-11T01:35:38.840

Maybe it's a version-of-Python thing, but for me it's printing the denominator with a trailing .0. Although actually it's separating the numerator and denominator with a newline rather than / anyway, so I don't suppose it makes much difference. – Peter Taylor – 2011-12-11T07:32:58.780

The output format isn't quite right - you need to lose the .0s and put in a /. – Keith Randall – 2011-12-20T17:29:08.850

4

JS (95 characters)

for(i=k=1,m=Math;i<1e6;i++)if((j=m.abs((x=m.round(m.PI*i))/i-m.PI))<k)k=j,console.log(x+'/'+i)

It does print 167 lines.

JiminP

Posted 2011-12-09T22:22:52.467

Reputation: 3 264

4

Ruby 1.9, 84 characters

m=1;(1..1e6).map{|d|n=(d*q=Math::PI).round;k=(n-q*d).abs/d;k<m&&(m=k;puts [n,d]*?/)}

Howard

Posted 2011-12-09T22:22:52.467

Reputation: 23 109

@Peter Taylor You're right. You have to use Ruby 1.9. – Howard – 2011-12-10T11:46:42.260

4

C99, 113 characters

main(d,n){double e=9,p=2*asin(1),c,a=1;for(;n=d*p+.5,c=fabsl(p-a*n/d),d<1e6;++d)c<e&&printf("%d/%d\n",n,d,e=c);}

Need to compile with -lm, and probably full of undefined behaviour, but it works for me.

Thomas

Posted 2011-12-09T22:22:52.467

Reputation: 579

2

Scala - 180 chars

import math._
def p(z:Int,n:Int,s:Double):Unit=
if(n==1e6)0 else{val q=1.0*z/n
val x=if(abs(Pi-q)<s){println(z+"/"+n)
abs(Pi-q)}else s
if(Pi-q<0)p(z,n+1,x)else p(z+1,n,x)}
p(3,1,1)

// ungolfed: 457

val pi=math.Pi
@annotation.tailrec
def toPi (zaehler: Int = 3, nenner: Int = 1, sofar: Double=1): Unit = {
  if (nenner == 1000000) () 
  else {
    val quotient = 1.0*zaehler/nenner
    val diff = (pi - quotient)
    val adiff= math.abs (diff)
    val next = if (adiff < sofar) {
      println (zaehler + "/" + nenner) 
      adiff 
    }
    else sofar
    if (diff < 0) toPi (zaehler, nenner + 1, next) 
    else toPi (zaehler + 1, nenner, next) 
  }  
}

The tailrec annotation is just a check, to verify, that it is tail-recursive, which is often a performance improvement.

user unknown

Posted 2011-12-09T22:22:52.467

Reputation: 4 210

I can't get this to work: pi.scala:1 error: not found: value math – Keith Randall – 2011-12-20T17:34:03.630

Are you using Scala 2.8? – user unknown – 2011-12-21T18:49:31.180

My scala says "unknown version", weird. On ideone.com, they use 2.8.0 and I still get errors. – Keith Randall – 2011-12-21T20:19:00.610

Try it at http://www.simplyscala.com/ - works for me. For scala-2.8, replacing math with Math might be sufficient. I mentioned simplyscala on this metathread, if you happen to search for it again: http://meta.codegolf.stackexchange.com/a/401/373

– user unknown – 2011-12-21T20:48:58.343

OK, that works. – Keith Randall – 2011-12-22T00:52:27.623

2

Mathematica 18 17 chars

I chose to use, as a measure of "best", the number of terms in a continued fraction representation of π. By this criterion, the best rational approximations of π are its convergents.

There are 10 convergents of π with a denominator less than one million. This is fewer than the requested 167 terms, but I am including it here because it may be of interest to others.

Convergents[π, 10] 

(* out *)
{3, 22/7, 333/106, 355/113, 103993/33102, 104348/33215, 208341/66317,
312689/99532, 833719/265381, 1146408/364913}

If you really want to see the denominator for the first convergent, it will cost an additional 11 characters:

Convergents[π, 10] /. {3 -> "3/1"}
(* out *)
{"3/1", 22/7, 333/106, 355/113, 103993/33102, 104348/33215,
208341/66317, 312689/99532, 833719/265381, 1146408/364913}

For those who are interested, the following shows the relations among the convergents, partial quotients, and continued fraction expression of convergents of π:

Table[ContinuedFraction[π, k], {k, 10}]
w[frac_] := Row[{Fold[(#1^-1 + #2) &, Last[#], Rest[Reverse[#]]] &[Text@Style[#, Blue, Bold, 14] & /@ ToString /@ ContinuedFraction[frac]]}];
w /@ FromContinuedFraction /@ ContinuedFraction /@ Convergents[π, 10]

continued fractions

Please excuse the inconsistent formatting of the continued fractions.

DavidC

Posted 2011-12-09T22:22:52.467

Reputation: 24 524

That's about half-way to a solution, but it's the easiest half. My GolfScript solution hard-codes a suitable representation of the the continued fraction in only 2 characters more. – Peter Taylor – 2012-08-17T12:23:43.923

But you didn't use continued fractions for your solution to this question, did you? – DavidC – 2012-08-17T17:21:15.733

Yes. It was the obvious way to do it. – Peter Taylor – 2012-08-17T19:42:13.300

In addition to being concise, this is much faster than most or all of the other solutions that have been posted. – Michael Stern – 2014-02-21T03:36:11.017

1

J, 69 65

New

]`,@.(<&j{.)/({~(i.<./)@j=.|@-l)@(%~(i:3x)+<.@*l=.1p1&)"0>:_i.1e3

Still a brute force approach but much faster and a tad shorter.

Old

A simple "brute force":

(#~({:<<./@}:)\@j)({~(i.<./)@j=.|@-l)@(%~(i:6x)+<.@*l=.1p1&)"0>:i.1e3

make a list of a/bs and then discard those that are farther from π for some b'<b.

Note: Change 1e3 to 1e6 for the full list. Go do something else and return later.

Eelvex

Posted 2011-12-09T22:22:52.467

Reputation: 5 204

1

C# 140 129 chars

double n=3,d=1,e=d;while(n<4e5){double w=n/d-Math.PI,a=Math.Abs(w);if(a<e){e=a;Console.WriteLine(n+"/"+d);}if(w>0)d++;else n++;}

Uncompressed code

var numerator = 3d;
var denominator = 1d;
var delta = 4d;
while (numerator < 4e5) 
{
    var newDelta = (numerator / denominator) - Math.PI;
    var absNewDelta = Math.Abs(newDelta);
    if (absNewDelta < delta)
    {
        delta = absNewDelta;
        Console.WriteLine(string.Format("{0}/{1}", numerator, denominator));
    }

    if (newDelta > 0)
    {
        denominator++;
    }
    else
    {
        numerator++;
    }
}

Jader Dias

Posted 2011-12-09T22:22:52.467

Reputation: 113

2var is not always your friend. By removing it in favour of double you gain the ability to merge declarations, lose the requirement to use double literals, and can save 16 chars. OTOH the question asks for a program, so you'll lose a few to adding a class declaration and a Main method. – Peter Taylor – 2012-12-11T09:36:15.020