11
\$\begingroup\$

I've written a little program to calculate pi using the Nilakantha series:

For this formula, take three and start alternating between adding and subtracting fractions with numerators of 4 and denominators that are the product of three consecutive integers which increase with every new iteration. Each subsequent fraction begins its set of integers with the highest one used in the previous fraction. Carry this out even a few times and the results get fairly close to pi. (http://www.wikihow.com/Calculate-Pi)

I don't really understand other ways of calculating pi, or they take too long to perform.

echo "Enter scale please"
read SCALE
VALUE=2
PI=3
FITNESS=1
while true
do
PI=$(echo "scale=$SCALE;$PI+(4/($VALUE*($VALUE+1)*($VALUE+2)))-(4/(($VALUE+2)*($VALUE+3)*($VALUE+4)))" | bc)
VALUE=$(($VALUE+4))
FITNESS=$(($FITNESS+1))
echo "###############"
echo "--> $FITNESS // $VALUE"
echo "$PI"
done

I would really like to know how to detect when I get the same output multiple times, so I can tell the program to terminate when it has reached the most accurate version of pi possible.

Also if you have other suggestions on how to improve the code and/or know a better way of calculating pi (with some explanation), I would like to hear.

\$\endgroup\$
13
\$\begingroup\$

I see a few things that could allow you to improve your program. First, though, I don't consider myself a bash expert, so there may well be better ways of doing these things.

Use a "shebang" line

As this question points out, you should always use a "shebang" line for your bash scripts. So the first line would be:

#!/usr/bin/env bash

Pass values as arguments

Rather than prompting for the SCALE value, it's generally better to use a command line argument. That way, the script can be reused by other shell scripts.

Provide a stopping mechanism

As each term is calculated, eventually, it will be equal to zero given the passed scale. This suggests a mechanism for stopping: check each term for 0 before adding it.

Indent do and while loops

I don't know of a bash style guide (there probably is one!) but I like to see the contents of loops indented to make it easier to read.

Putting it all together

Here's a modification of your script with all of these suggestions implemented:

bashpi.sh

#!/usr/bin/env bash
SCALE=$1
VALUE=2
PI=0
FITNESS=1
DELTA=3
while [ $(echo "$DELTA==0" |bc) != "1" ]
do
    PI=$(echo "$PI+$DELTA" | bc)
    DELTA=$(echo "scale=$SCALE;(4/($VALUE*($VALUE+1)*($VALUE+2)))-(4/(($VALUE+2)*($VALUE+3)*($VALUE+4)))" | bc)
    VALUE=$(($VALUE+4))
    FITNESS=$(($FITNESS+1))
    echo "###############"
    echo "--> $FITNESS // $VALUE"
    echo "$PI"
done

To better understand how this works, you can replace the three echo statements with this one:

echo "DELTA = ${DELTA} --> ${FITNESS} // ${VALUE} : ${PI}"

Sample output

With ./bashpi.sh 4, and the modified echo above I get this output:

DELTA = .1333 --> 2 // 6 : 3
DELTA = .0064 --> 3 // 10 : 3.1333
DELTA = .0012 --> 4 // 14 : 3.1397
DELTA = .0003 --> 5 // 18 : 3.1409
DELTA = .0001 --> 6 // 22 : 3.1412
DELTA = .0001 --> 7 // 26 : 3.1413
DELTA = .0001 --> 8 // 30 : 3.1414
DELTA = 0 --> 9 // 34 : 3.1415
\$\endgroup\$
5
  • \$\begingroup\$ I really appreciate the suggestions, but I don't completely understand how you've implemented the stopping feature, SCALE simply is how many numbers exist after the decimal point. There's probably something I'm not seeing here... \$\endgroup\$
    – insanikov
    Jun 21 '15 at 18:17
  • \$\begingroup\$ It's a bit subtle. Basically, a value of 0.001 (=1e-3) with a scale of 3 is nonzero, but a value of 0.0005 (=5e-4) is considered to be equal to zero at a scale of 3, so when the next term becomes less than smaller than 1e-${SCALE}, the printable part of ${PI} is unlikely to change except in the least significant digit. A refinement would be to use ${SCALE}+1 in the while loop but it doesn't make much difference and this is slightly easier to understand. \$\endgroup\$
    – Edward
    Jun 21 '15 at 21:08
  • \$\begingroup\$ I understand it now, I would have never come up with this. Thank you for the explanation. \$\endgroup\$
    – insanikov
    Jun 22 '15 at 13:52
  • \$\begingroup\$ Regarding "I don't know of a bash style guide" there's the Google Shell Style Guide; it's not perfect but it's a good starting point. \$\endgroup\$
    – dimo414
    Apr 30 '20 at 4:33
  • \$\begingroup\$ I'd also suggest SCALE=${1:?} or SCALE=${1:-4} to fail-fast or fall-back to a default value if a scale isn't passed on the command line. \$\endgroup\$
    – dimo414
    Apr 30 '20 at 4:36
1
\$\begingroup\$

Two things I'd add to Edward's answer, both related to bc.

  1. You can use herestrings to pass strings directly to a program's stdin, instead of echo |. So bc <<<"...".
  2. You can format the expressions passed to bc to be more readable; it doesn't have to be one big blob. You need to \-escape expressions that span multiple lines, though.

Borrowing from Edward's answer:

#!/usr/bin/env bash
SCALE=${1:?Must provide a scale to compute up to}
VALUE=2
PI=0
FITNESS=1
DELTA=3
while [ "$(bc <<<"$DELTA==0")" != "1" ]; do
    PI=$(bc <<<"$PI+$DELTA")
    DELTA=$(bc <<<"scale=$SCALE; \
      (4 / (${VALUE} * (${VALUE}+1) * (${VALUE}+2))) - \
      (4 / ((${VALUE}+2) * (${VALUE}+3) * (${VALUE}+4)))")
    VALUE=$((VALUE+4))
    FITNESS=$((FITNESS+1))
    printf '###############\n--> %s // %s\n%s' "$FITNESS" "$VALUE" "$PI"
done

Also, bc is powerful enough that you could try implementing this whole algorithm in it, rather than jumping in and out of Bash (which involves creating numerous subprocesses and is therefore likely a lot of overhead). See man bc if you haven't already.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.