Pi Calculator output issues

101 views Asked by At

I have created a simple python program that calculates pi by counting the number of 1 pixel points that fit in a circle. At first I ran the program with the following values:

from tkinter import *

tk = Tk()
canvas = Canvas(tk, width=400, height=400)
canvas.pack()

canvas.create_oval(100,100,400,400)

global pi_count
pi_count = 0

for x in range(100,400):
    for y in range(100,400):
        point = canvas.create_oval(x,y,(x+1),(y+1))
        c = canvas.coords(point)
        sc = list(c)
        xc = sc[1]
        yc = sc[2]
        fxc = int(xc)
        fyc = int(yc)
        dist = ((fxc - 250)**2 + (fyc - 250)**2)**0.5
        if dist > 150:
            print("outside of the circle")
        else:
            pi_count += 1
        print(pi_count)

pi = (pi_count/150**2)
print(pi)

This gave me the output 3.1412888889.

Relatively pleased with this result, I changed the oval dimensions to (100,100,500,500) and changed all other values(radius, for loops etc.) accordingly.

However this larger circle and presumably more accurate area produced a more inaccurate estimate of 3.140675.

Why is this, and how can I fix the calculator to give more accurate estimates?

EDIT

Here is edited and improved code that is easier to test:

from tkinter import *

MIN_POS = 100
MAX_POS = 300
center = (MAX_POS + MIN_POS)/2
radius = (MAX_POS - MIN_POS)/2

tk = Tk()
canvas = Canvas(tk, width=MAX_POS, height=MAX_POS)
canvas.pack()

canvas.create_oval(MIN_POS,MIN_POS,MAX_POS,MAX_POS)

global pi_count
pi_count = 0

for x in range(MIN_POS,MAX_POS):
    for y in range(MIN_POS,MAX_POS):
        point = canvas.create_oval(x,y,(x+1),(y+1))
        c = canvas.coords(point)
        sc = list(c)
        xc = sc[1]
        yc = sc[2]
        fxc = int(xc)
        fyc = int(yc)
        dist = ((fxc - center)**2 + (fyc - center)**2)**0.5
        if dist > radius:
            print("outside of the circle")
        else:
            pi_count += 1
        print(pi_count)

pi = (pi_count/radius**2)
print(pi)

I would appreciate it if anyone with a fast computer can run this program with Max_pos being raised in steps of 100.

1

There are 1 answers

1
Jongware On

This method is very inaccurate. The results from testing one radius can be quite a lot more or less accurate than 1 less or more:

100 -> 3.141700 ~ 1.000034
125 -> 3.140928 ~ 0.999788
150 -> 3.141378 ~ 0.999932
175 -> 3.141518 ~ 0.999976
200 -> 3.140725 ~ 0.999724
225 -> 3.141393 ~ 0.999936
250 -> 3.141136 ~ 0.999855
275 -> 3.140826 ~ 0.999756
300 -> 3.141078 ~ 0.999836
325 -> 3.141311 ~ 0.999910
350 -> 3.140939 ~ 0.999792
375 -> 3.141582 ~ 0.999997
400 -> 3.141406 ~ 0.999941

The reason is that you are testing integral values only, and so these tend to round down. While increasing the radius does improve accuracy, it should be impossible to get π with enough decimals until you cross the limits of Python's float accuracy itself – I have no idea how large the radius would need to be for that.

For your value 150, testing 149 and 151 as well shows neither are 'better'; both are much worse!

150 -> 3.141378 ~ 0.999932
 -1 -> 3.140804 ~ 0.999749
 +1 -> 3.140783 ~ 0.999742

Even for a very large radius such as 10,000, you still get 3.141591 ~ 0.999999 (that took a while to calculate).

Tested with the following code (without using the graphical display). Note there is a tiny bug in your code, which also influences the result! You run a range from (100,400), which you might as well write as (0,300). Subtracting the radius, you have (-150,150) – but Python's range function runs from start to less than stop. That means you are calculating -150..-1, then 0, then 1..149, and there is a small bias to the left.

(However, taking that into account does not visibly improve the accuracy.)

import math

def calc_pi(radius):
    pi_count = 0
    rad2 = radius*radius
    for x in range(-radius,radius+1):
        x2 = x*x
        for y in range(-radius,radius+1):
            dist = x2 + y*y
            if dist <= rad2:
                pi_count += 1
    return float(pi_count)/radius**2

for i in range(100,401,25):
    result = calc_pi(i)
    print("%d -> %f ~ %f" % (i, result, result/math.pi))