                      /* fltval.c, 13 May 10 */

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

#if !HAVE_GMP
#include <float.h>
#endif

#include "global.h"
#include "errors.h"
#include "utils.h"
#include "fltval.h"


#if HAVE_LONG_DOUBLE
// Some C99 headers don't contain this declaration
extern long double strtold(const char *nptr, char **endptr);
#endif


const char *opt_fuzz_str = "0.0001";

fp_t default_fuzz_val;

// Having the following three variables global rather than
// local saves on GMP memory allocation/deallocation for
// every call to fp_almost_eq
static fp_t lower_bound,
	    upper_bound,
	    fuzz_val;


void fp_init(fp_t *var)
{
#if HAVE_GMP
    mpf_init(*var);
#elif HAVE_LONG_DOUBLE
    *var = 0.0L;
#else
    *var = 0.0;
#endif
}

void fp_print(fp_t numeric_val)
{
#if HAVE_GMP
   gmp_printf("%.20Fg", numeric_val);
#elif HAVE_LONG_DOUBLE
   #if MS_DOS
// Microsoft DLL does not support output of long double
      printf("%.16Lg", (double)numeric_val);
   #else
      printf("%.16Lg", numeric_val);
   #endif
#else
   printf("%.10g", numeric_val);
#endif

}


double fp_to_double(fp_t numeric_val)
{
#if HAVE_GMP
return mpf_get_d(numeric_val);
#else
return numeric_val;
#endif
}


void fp_to_string(DIGIT_CHAR *val_str,
		  fp_t numeric_val,
		  int signif_digits)
{
#if HAVE_GMP
DIGIT_CHAR work_str[MAX_NUM_DIGITS];
mp_exp_t exp;
int exp_len;

if (mpf_sgn(numeric_val) == 0) // mpf_get_str() returns "" for zero
   {
   strcpy(val_str, "0.0");
   return;
   }

mpf_get_str(work_str, &exp, 10, signif_digits-1, numeric_val);

// Assume returned value is not negative
if (exp == 0)
   sprintf(val_str, "0.%s", work_str);
else if (exp == 1)
   sprintf(val_str, "%c.%s", work_str[0], (work_str[1] ? &work_str[1] : "0"));
else
   {
   exp_len=3+log10((double)abs(exp-1));
   mpf_get_str(work_str, &exp, 10, signif_digits-1-exp_len, numeric_val);
   sprintf(val_str, "%c.%se%+.1d", work_str[0],
			   (work_str[1] ? &work_str[1] : "0"), (int)(exp-1));
   }
return;
#else

#if HAVE_LONG_DOUBLE
#if LDBL_DIG+6 > MAX_NUM_DIGITS
#error LDBL_DIG+6 > MAX_NUM_DIGITS : Need to incrase MAX_NUM_DIGITS
#endif

   #if MS_DOS
// Microsoft DLL does not support output of long double
    (void)sprintf(val_str, "%.*e", DBL_DIG, (double)numeric_val);
   #else
    (void)sprintf(val_str, "%.*Le", LDBL_DIG, numeric_val);
   #endif
#else
    (void)sprintf(val_str, "%.*e", DBL_DIG, numeric_val);
#endif

    return;
#endif

}


BOOL string_to_fp(fp_t * numeric_val,
		  const DIGIT_CHAR *mantissa,
		  int mant_len,
		  const DIGIT_CHAR *exponent)
{
DIGIT_CHAR *val_str;
const size_t exp_len = strlen(exponent);

val_str=get_heap(mant_len+exp_len+2);
if (val_str == NULL)
   {
   print_warning("Unable to allocate storage: string_to_fp");
   return FALSE;
   }
//printf("%d  %s\n", mant_len, exponent);
strncpy(val_str, mantissa, mant_len);
val_str[mant_len]=NULL_CHAR;
if (exp_len > 0)
   {
   strcat(val_str, "E");
   strcat(val_str, exponent);
   }

// printf("%s\n", val_str);

#if HAVE_GMP
if (mpf_set_str(*numeric_val, val_str, 10) != 0)
   {
//   print_warning("mpf_set_str failed: %s\n", val_str);
   free(val_str);
   return FALSE;
   }
#else
errno=0;
#if HAVE_LONG_DOUBLE
// (void)sscanf(val_str, "%Lg", numeric_val);
*numeric_val=strtold(val_str, NULL);
#else
//(void)sscanf(val_str, "%lg", numeric_val);
*numeric_val=strtod(val_str, NULL);
#endif
if ((errno != 0) || isinf(*numeric_val))
   return FALSE;
#endif

free(val_str);
return TRUE;
}


void fp_assign(fp_t * lhs, fp_t rhs)
{
#if HAVE_GMP
   mpf_set(*lhs, rhs);
#else
   *lhs = rhs;
#endif
}


static void fp_mult(fp_t * result, fp_t left_operand, fp_t right_operand)
{
#if HAVE_GMP
   mpf_mul(*result, left_operand, right_operand);
#else
   *result = left_operand * right_operand;
#endif

}


static void fp_add(fp_t * result, fp_t left_operand, fp_t right_operand)
{
#if HAVE_GMP
//   fp_print(left_operand); printf(" ");
//   fp_print(right_operand); printf(" ");
   mpf_add(*result, left_operand, right_operand);
//   fp_print(*result); printf("\n");
#else
   *result = left_operand + right_operand;
#endif
}

static void fp_sub(fp_t * result, fp_t left_operand, fp_t right_operand)
{
#if HAVE_GMP
//   fp_print(left_operand); printf(" ");
//   fp_print(right_operand); printf(" ");
   mpf_sub(*result, left_operand, right_operand);
//   fp_print(*result); printf("\n");
#else
   *result = left_operand - right_operand;
#endif
}


static int fp_cmp(fp_t left_operand, fp_t right_operand)
{
#if HAVE_GMP
   int cmp_status;

   cmp_status = mpf_cmp(left_operand, right_operand);

   return cmp_status;
#else
   if ((left_operand != left_operand) ||
       (right_operand != right_operand))  // check for Nan
       return -2;
   else if (left_operand < right_operand)
      return -1;
   else if (left_operand == right_operand)
      return 0;
   else
      return 1;
#endif

}


int fp_almost_eq(fp_t left_operand, fp_t right_operand, fp_t fuzz_percent)
{
// fp_print(left_operand); printf(" ");
// fp_print(right_operand); printf("\n");

// Take a percentage of the larger value as the fuzz factor
if (fp_cmp(left_operand, right_operand) < 0)
   fp_mult(&fuzz_val, right_operand, fuzz_percent);
else
   fp_mult(&fuzz_val, left_operand, fuzz_percent);

fp_sub(&lower_bound, left_operand, fuzz_val);
fp_add(&upper_bound, left_operand, fuzz_val);

// fp_print(fuzz_percent); printf(" ");
// fp_print(left_operand); printf(" ");
// fp_print(right_operand); printf("\n");

if ((fp_cmp(lower_bound, right_operand) <= 0) &&
    (fp_cmp(right_operand, upper_bound) <= 0))
   return 1;

return 0;
}


int fp_eq(fp_t left_operand, fp_t right_operand)
{
int cmp_res = fp_cmp(left_operand, right_operand) == 0;

return cmp_res;
}


void set_fuzz_val(fp_t *fuzz_val, const char *fuzz_str)
{
double fuzz_opt;
char *end_ptr;

errno=0;
fuzz_opt=strtod(fuzz_str, &end_ptr);
if ((fuzz_opt >= 1.0) || (errno != 0) ||
    ((end_ptr == fuzz_str) && (fuzz_opt == 0.0)))
   {
   print_warning("-fuzz %s is a nonsensical option\n", fuzz_str);
   fuzz_str="0.0001";
   }

string_to_fp(fuzz_val, fuzz_str, strlen(fuzz_str), "");
}


void init_fuzz_percent(const char *fuzz_str)
{
#if HAVE_GMP
mpf_set_default_prec(strlen(fuzz_str)*2);
#endif

opt_fuzz_str=fuzz_str;

fp_init(&fuzz_val);
fp_init(&lower_bound);
fp_init(&upper_bound);
fp_init(&default_fuzz_val);

set_fuzz_val(&default_fuzz_val, fuzz_str);

#if 000
test_fp();
#endif
}


#if 000
void sub_test_fp(char *num_str)
{
fp_t temp;
DIGIT_CHAR work_str[MAX_NUM_DIGITS];
DIGIT_CHAR r_str[MAX_NUM_DIGITS];

fp_init(&temp);
strcpy(work_str, num_str);
string_to_fp(&temp, work_str, strlen(work_str), "");

fp_to_string(r_str, temp, 20);
printf("%s  %s\n", work_str, r_str);

return;
}


int test_fp(void)
{
sub_test_fp("1.23456789");
sub_test_fp("1.2345678901234567890");
sub_test_fp("1.23456789999999999999999");
sub_test_fp("0.23456789");
sub_test_fp("1.23456789e-3");
sub_test_fp("1.23456789e+4");
sub_test_fp("1.23456789e-999");
sub_test_fp("1.23456789e+999");
sub_test_fp("0.00000000000123456789");
sub_test_fp("1.2");
sub_test_fp("0.0");
sub_test_fp("1.2345678901234567890e-99");

}
#endif

