/******************************************************
 *
 *			fp2ratio.c
 *
 *	Floating-point number to ratio approximation.
 *
 *	Usage: fp2ratio number
 *
 *	Compiler: Microsoft C 6.0 using inline
 *	floating-point emulator (option /FPi).
 *
 *****************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

/* Maximum argument significant digits. */
#define MAX_SIG_DIGITS	    9

#define ARRAY_SIZE          2
#define I		( i     ) % ARRAY_SIZE
#define I_MINUS_1	( i - 1 ) % ARRAY_SIZE
#define I_MINUS_2       ( i     ) % ARRAY_SIZE

typedef	unsigned long     Ulong;
typedef	long double       Ldouble;


int main( int argc, char **argv )
{
   int     i, dec_digits, sign;
   Ulong   n0, n, d0, d, temp;
   Ulong   p[ARRAY_SIZE] = { 0, 1 };
   Ulong   q[ARRAY_SIZE] = { 1, 0 };
   double  x;
   Ldouble percent_err;

   /* Check for one command-line argument. */
   if ( argc != 2 )
   {
      fprintf(stderr, "Usage: %s number\n", argv[0]);
      exit( EXIT_FAILURE );
   }
   else /* argc == 2 */
      x = strtod( argv[1], (char **) NULL );

   /* Handle zero and negative arguments. */
   if ( x < 0.0 )
   {
      sign = -1;
      x    = -x;
   }
   else if ( x == 0.0 )
   {
      puts( "\n0:\n" );
      puts( "          0 / 1             Exactly!" );
      exit( EXIT_SUCCESS );
   }
   else /* x > 0.0 */
      sign = 1;

   /* Check for out-of-range arguments. */
   if ( x >= pow( 10.0, (double) MAX_SIG_DIGITS ) )
   {
      fprintf( stderr, "%s: Magnitude is", argv[1] );
      fprintf( stderr, " too large.\n" );
      exit( EXIT_FAILURE );
   }
   else if ( x <=
      pow( 10.0, (double) -MAX_SIG_DIGITS) / 2.0 )
   {
      fprintf( stderr, "%s: Magnitude is", argv[1] );
      fprintf( stderr, " too small.\n" );
      exit( EXIT_FAILURE );
   }

   /* Determine the argument's radix-10 ratio. */
   d0 = (Ulong) pow( 10.0, (double) MAX_SIG_DIGITS -
        ((x < 1.0) ? 0.0 : floor( 1.0 + log10( x ))));
   n0 = (Ulong) ( ( x * (double) d0 ) + 0.5 );
   printf( "\n%.*g:\n\n", MAX_SIG_DIGITS,
           (double) n0 / (double) d0 );

   /* Iteratively determine integer ratios. */
   for ( i = 2, d = d0, n = n0 ; ;
         i++, temp = d, d = n % d, n = temp )
   {
      p[I] = n / d * p[I_MINUS_1] + p[I_MINUS_2];
      q[I] = n / d * q[I_MINUS_1] + q[I_MINUS_2];

      /* Print ratios with non-zero numerators. */
      if ( p[I] != 0 )
      {
         printf("%11ld / %-10lu", sign * p[I], q[I]);
         if ( n % d == 0 )
         {
            printf( "    Exactly!\n" );
            break;
         }

         /*
	  *  Compute the ratio's percent error
	  *  (taking care to avoid significance
	  *  loss when subtracting nearly equal
	  *  values):
	  *
	  *    percent error =
	  *
	  *
	  *	 100 * ( p[i] * d0  -  q[i] * n0 )
	  *    -------------------------------------
	  *		     q[I] * n0
	  *
	  *  Display in %f format with at least two
	  *  significant digits.
	  */
         percent_err  = (Ldouble) p[I] * (Ldouble) d0;
         percent_err -= (Ldouble) q[I] * (Ldouble) n0;
	 percent_err *= 100.0L;
	 percent_err /= (Ldouble) q[I] * (Ldouble) n0;

         dec_digits =
            ( fabs( (double) percent_err ) >= 10.0 ) ?
            1 : 1 + ( int ) ( fabs( floor(
            log10( fabs( (double) percent_err )))));
         printf( "%+*.*Lf%%\n", dec_digits + 6,
                 dec_digits, percent_err );
      }
   }

   exit( EXIT_SUCCESS );
}

