Let me start out by warning that this post does not deal strictly with 
Forte, so you purists can go ahead and click the delete button now.

OK, I got to thinking about PI.  I just knew there was an algorithm that 
could reach a reasonably close value at something less that several 
million iterations.  So, by searching through some really old notes -- 
and Google (not a tool that was available when I was going through 
college, sigh) -- I found one.

Below is the best algorithm for calculating PI I have yet come across. 
 It will generate pi to the limits of double floating point in only five 
iterations.

Well, almost.  Running the app generates the following output:

Generated PI in 5 iterations.
The generated value of pi is    3.141592653589794
The Math.PI value of pi is      3.141592653589793
Actual PI to 53 places is       
3.14159265358979323846264338327950288419716939937510582

As you can see, the answer in the last digit is off by one.  But this is 
to be expected.  We are trying to reach PI by generating a sequence of 
values, each value closer than the one before.  By the end of the fifth 
iteration, we have reached the limit of our precision to adjust the 
value.  That is, the next adjustment would be generated by using an 
amount smaller than the smallest amount we can represent with double. 
 So no matter what precision is available to us, our calculation can 
only get us to within the last digit or two.  (Using float arrives at an 
"exact" value, because it happens to round to the correct digit.)  

To illustrate this, I rewrote the procedure using BigDecimal.  I did 
this because 1) I had never used BigDecimal before and want to at least 
play with it and because 2) being "between jobs," I have gobs of time so 
why not.

I will attach the BigDecimal source separately, as it is a bit longer (I 
had to include a sqrt function for BigDecimal!).  But the output is this:

Generated PI in 6 iterations.
Actual PI to 53 places is       
3.14159265358979323846264338327950288419716939937510582
The generated value of pi is    
3.14159265358979323846264338327950288419716939937461
The Math.PI value of pi is      3.141592653589793

So this tells me I have found a good algorithm.  Going from 15 decimal 
places to 50 required only one additional iteration to arrive at the 
answer within the limits of that precision.  Not bad.  This algorithm 
can't be used to generate millions of places of PI, but it can probably 
whip out a few hundred thousand in no time at all (maybe take all of 8 
loops).

If anyone has any critiques of my use of BigDecimal, any suggestions for 
improvement, please send them to me.  I now see there are more uses for 
BigDecimal than I had thought originally.  For example, in this instance 
of calculating PI, I could have calculated it using BigDecimal at, say 
16 places.  It would still have taken only 5 iterations and could have 
returned the value as a double accurate all the way out.  So if you are 
going to be doing calculations where the 14th and 15th places of your 
double value are going to be significant, do the calculations in 
BigDecimal and convert the final answer back to double.

Another benefit.  In the original algorithm, the loop is exited when C 
(capC in the code) reaches zero.  When I wrote it that way in the double 
version, the loop never exited.  The value would reach 
1.232595164407831E-32, something very close to zero, and would stay 
there.  As double is supposed to be able to handle as small a number as 
2.0e-149, the problem is obviously in the calculation of C rather than 
the representation of C.  So I compare the result of each loop to the 
previous result and exit when they are the same.  This actually causes 
the loop to iterate one extra time.  Big deal.

This problem does not occur using BigDecimal.  The interim calculations 
actually increase the scale as needed.  The value C ultimately reaches 
zero (or at least zero out to whatever scale is chosen) and the loop 
exits.  As minor as this problem was in this particular instance, it 
could be more significant in other applications (such as balancing very 
large amounts of money to the penny).

I have been converted.

Tomm


=================== source ======================
/*
 * BrentSalamin.java
 *
 * Created on September 23, 2002, 1:54 PM
 */

/**
 *
 * @author  Tomm Carr
 */
public class BrentSalamin {
   
    private static final double INITIAL_A = 1.0,
                                INITIAL_B = 1.0 / Math.sqrt( 2.0 ),
                                INITIAL_R = 1.0,
                                INITIAL_S = 0.5;
   
   
    public double getPi() {
        double    retVal    = 0,
                lastPi,
                smallA    = INITIAL_A,
                smallB    = INITIAL_B,
                smallR    = INITIAL_R,
                smallS    = INITIAL_S,
                capA,
                capB,
                capC,
                capR,
                capS,
                capP;
        int        iterations = 0;
       
        while (true) {
            iterations++;
            lastPi = retVal;
            capA = (smallA + smallB) / 2;
            capB = Math.sqrt( smallA * smallB );
            capC = Math.pow( capA - smallB, 2.0 );
            capR = 2 * smallR;
            capS = smallS - capC * capR;
            retVal = (2 * capA * capA) / capS;
            if (retVal == lastPi)
                // Keep looping until we have calculated pi to as much 
precision as we can
                break;
            smallA = capA;
            smallB = capB;
            smallR = capR;
            smallS = capS;
        }
        while (lastPi != retVal);
       
        // track loop iterations and print for illustration only
        System.out.println( "Generated PI in " + iterations + " 
iterations." );
        return retVal;
    }
   
   
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        BrentSalamin piCalc = new BrentSalamin();
        double pi = piCalc.getPi();
       
        System.out.println( "The value of pi is " + pi );
    }
   
}

/*
 * BrentSalamin.java
 *
 * Created on September 23, 2002, 1:54 PM
 */

import java.math.BigDecimal;

/**
 *
 * @author  Tomm Carr
 */
public class BrentSalaminBD {
        
        private static final int        DB_SCALE        = 50,
                                                                DB_MODE         = 
BigDecimal.ROUND_HALF_DOWN;
        
        // BDFUDGE is used by the sqrt function.  So, if you move these declarations 
around, 
        // make sure BDFUDGE is declared before BDSQRT2.
        private static final BigDecimal BDFUDGE // A value that is 'close enough' to 
zero
                        = new BigDecimal( 
"0.00000000000000000000000000000000000000000000000001" );
        private static final BigDecimal BD0             // Zero to DB_SCALE decimal 
places
                        = new BigDecimal( 0.0 ).setScale( DB_SCALE );
        private static final BigDecimal BD1             // One to DB_SCALE decimal 
places
                        = new BigDecimal( 1.0 ).setScale( DB_SCALE );
        private static final BigDecimal BD2             // Two to DB_SCALE decimal 
places
                        = new BigDecimal( 2.0 ).setScale( DB_SCALE );
        private static final BigDecimal BDSQRT2 // sqrt(2) to DB_SCALE decimal places
                        = sqrt( BD2 );

        private static final BigDecimal INITIAL_A = BD1;
        private static final BigDecimal INITIAL_B = BD1.divide( BDSQRT2, DB_MODE );
        private static final BigDecimal INITIAL_R = BD1;
        private static final BigDecimal INITIAL_S = BD1.divide( BD2, DB_MODE );
        
        
        public BigDecimal getPi() {
                BigDecimal      retVal  = new BigDecimal(0.0),
                                        smallA  = INITIAL_A,
                                        smallB  = INITIAL_B,
                                        smallR  = INITIAL_R,
                                        smallS  = INITIAL_S,
                                        capA,
                                        capB,
                                        capC,
                                        capR,
                                        capS,
                                        capP;
                int                     iterations = 0;

                while (true) {
                        iterations++;
                        capA = smallA.add(smallB).divide( BD2, DB_MODE );
                        capB = sqrt( smallA.multiply( smallB ) );
                        capC = sqr( capA.subtract( smallB ));
                        capR = smallR.multiply( BD2 ).setScale( DB_SCALE, DB_MODE );
                        capS = smallS.subtract( capC.multiply( capR ) ).setScale( 
DB_SCALE, DB_MODE );
                        retVal = BD2.multiply(( capA.multiply( capA )).divide( capS, 
DB_MODE )).setScale( DB_SCALE, DB_MODE );

                        if (capC.compareTo( BD0 ) == 0) {
                                // Keep looping until we have calculated pi to as much 
precision as we can.
                                // In the case of BigDecimals, precision is limited 
only by the scale. 
                                // The algorithm will loop until 'C' (capC) goes to 
zero (BD0).  Goes to 
                                // zero, that is, beyond the scale we are using.
                                break;
                        }
                        smallA = capA;
                        smallB = capB;
                        smallR = capR;
                        smallS = capS;
                }
                
                // track loop iterations and print for illustration only
                System.out.println( "Generated PI in " + iterations + " iterations." );
                return retVal;
        }
        
        
        /**
         * @param args the command line arguments
         */
        public static void main(String[] args) {
                BrentSalaminBD piCalc = new BrentSalaminBD();
                BigDecimal pi = piCalc.getPi();
                
                System.out.println( "Actual PI to 53 places 
is\t3.14159265358979323846264338327950288419716939937510582" );
                System.out.println( "The generated value of pi is\t" + pi );
                System.out.println( "The Math.PI value of pi is\t" + Math.PI );
        }
        
        // A few necessary BigDecimalMath routines
        
        // Returns the square of the value
        private static BigDecimal sqr( BigDecimal a ) {
                return a.multiply( a ).setScale( DB_SCALE, DB_MODE );
        }
        
        // Returns the absolute value of the value
        private static BigDecimal abs( BigDecimal a ) {
                BigDecimal ans = a;
                if (a.compareTo( BD0 ) < 0) {
                        ans = a.negate();
                }
                return ans;
        }
        
        // Returns the square root of the value.  Uses the old "guess and adjust" 
method.
    private static BigDecimal sqrt( BigDecimal w ) {
        return sqrt2( w, BD1 );
    }

        private static BigDecimal sqrt2( BigDecimal w, BigDecimal g ) {
        BigDecimal newGuess = g.subtract( guess( w, g ).divide( g.multiply( BD2 ), 
DB_MODE ));
        if (closeEnough( newGuess, g ))
            return newGuess;
        else
            return sqrt2( w, newGuess ); // Recurse with a better guess
    }

    private static BigDecimal guess( BigDecimal w, BigDecimal g ) {
        return sqr( g ).subtract( w ).setScale( DB_SCALE, DB_MODE );
    }

    private static boolean closeEnough( BigDecimal a, BigDecimal b ) {
        return (BDFUDGE.compareTo( abs( a.subtract( b ))) >= 0);
    }
}


To change your JDJList options, please visit: http://www.sys-con.com/java/list.cfm

Reply via email to