/***************************************************************************
                          numeric_ops.c  -  description
                             -------------------
    begin                : Wednesday June 21 2000
    email                : tboldt@attglobal.net
    Author               : Terry D. Boldt
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

/*
 *  Functions to execute arthmetic operators on integer and double operands
 *  6-23-2000
 *
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <float.h>
#include <math.h>

#define NUMERIC_OPS_STATICS
#include "finvar.h"

static double neg_table[] = {
    1e-256,
    1e-128,
    1e-64,
    1e-32,
    1e-16,
    1e-8,
    1e-4,
    1e-2,
    1e-1,
    1.0
};

static double pos_table[] = {
    1e+256,
    1e+128,
    1e+64,
    1e+32,
    1e+16,
    1e+8,
    1e+4,
    1e+2,
    1e+1
};

#define MAX_SCALE ((LONG_MAX - 10) / 10)

/* function to translate ASCII string to numeric format.
 *
 * Recognizes either integer numerics or floating point numerics.
 * Recognizes integers in format:
 *  (sign)digit_sequence
 *  digit_sequence may contain a grouping character, the grouping character is ignored
 *  optional sign == '+' or '-'
 *
 *  Recognizes floating point in formats:
 *  (sign)digit_sequence.digits(exp)
 *  (sign)digit_sequence.(exp)
 *  (sign)digit_sequence(exp)
 *  (sign).digits(exp)
 *  '.' represents the radix point passed, digit_sequence may contain a grouping character
 *      the grouping character is ignored
 *  optional sign == '+' or '-'
 *  optional exp == ('e' or 'E')(sign)digits
 *
 * Terminates on first unrecognized character.
 *
 */
void           *trans_numeric(
const char *str,              /* pointer to string to translate */
char   radix_point,      /* radix character                */
char   group_char,       /* grouping character to left of radix  */
char **endstr)           /* where to return pointer to first unrecognized character */
{
	double         dblval = 0.0;
	int            exp = 0,
	               dchr,
	               err = 0,
	               base = 10;
	long int       inum = 0;
	unsigned long  msdec = 0,
	               lsdec = 0,
                   msscale = 1;
	unsigned       radix = 0,
	               sign = 0,
	               digit_cnt = 0;
	const char    *strinit = str;
	numeric_ptr    rslt = NULL;

	while ( isspace(*str) ) str++;

	switch (*str) {
		case '-':
			sign++;
		case '+':
			str++;
		default:
		    break;
	} /* endswitch */
	
	while ( *str ) {
	
		while ( (*str >= '0') && (*str <= '9') ) {
		    digit_cnt++;
		
		    if ( msdec < MAX_SCALE ) msdec = msdec * 10 + (*str - '0');
		      else if ( msscale < MAX_SCALE ) {
		        lsdec = lsdec * 10 + (*str - '0');
			    msscale *= 10;
		    } else exp++;
		
		    if ( radix ) exp--;
		      else {
		        dchr = *str - '0';
		        if ( ((LONG_MIN + dchr) / base) > inum ) err = 1;
		        inum = inum * base + dchr;
		    } /* endif */
		    str++;		
		} /* endwhile */
		
		if ( !radix ) {
		    if ( *str == radix_point ) radix++;
		      else if ( *str != group_char ) break;
		  } else {
		    break;
		} /* endif */
		
		str++;
	} /* endwhile */
	
	if ( digit_cnt ) {
	    unsigned      exp_dcnt = 0;
	
    	if ( (*str == 'e') || (*str == 'E') ) {
	        char exp_sign = EOS;
	        int  ex_exp = 0;

	        switch (*++str) {
	        	case '-':
	    	    	exp_sign++;
        		case '+':
	        		str++;
	        	default:
	        		break;
    	    } /* endswitch */
    	
    	    while ( (*str >= '0') && (*str <= '9') ) {
        		if (ex_exp < (DBL_MAX_EXP * 2) ) ex_exp = ex_exp * 10 + (*str - '0');
    	    	str++;
	    	    exp_dcnt++;
	        } /* endwhile */

	        exp += exp_sign ? -ex_exp : ex_exp;
	    } /* endif */

	    if ( radix || exp ) {
	        int pow = 256;
	
    	    dblval = msdec;
    	    if ( msscale != 1 )	dblval = dblval * msscale + lsdec;

        	if ( dblval && exp ) {
        	    unsigned u = 0;

	            pow = 256;
    	        while ( exp > 0 ) {
    	        	while ( exp >= pow ) {
    	        		dblval *= pos_table[u];
		        	    exp -= pow;
            		} /* endwhile */
    	        	pow >>= 1;
        	    	u++;
	            } /* endwhile */
	
    	        while ( exp < 0 ) {
    	        	while ( exp <= -pow ) {
    	        		dblval *= neg_table[u];
		        	    if ( dblval == 0.0 ) {
		        	        errno = ERANGE;
		        	        err = 1;
		        	    } /* endif */
        	    		exp += pow;
	        	    } /* endwhile */
        	    	pow >>= 1;
	        	    u++;
    	        } /* endwhile */
    	
        	    /* if overflow occurred		*/
	            if ( dblval == HUGE_VAL ) {
	                errno = ERANGE;
	                err = 1;
	            } /* endif */
    	    } /* endif */

    	    if ( !err ) {
    	        rslt = (numeric_ptr)calloc(1,sizeof(numeric)); 	
        	    rslt->type = DBL_TYPE;
            	rslt->value.dbl_value = dblval;
        	} /* endif */
    	  } else {
    	    if ( (!sign && (inum == LONG_MIN)) || err ) {
        		inum = LONG_MIN + sign;
		        errno = ERANGE;
		      } else {
		        rslt = (numeric_ptr)calloc(1,sizeof(numeric));
        	    rslt->type = INT_TYPE;
    	  	    rslt->value.int_value = inum;		
        	} /* endif */
	    } /* endif */
    } /* endif */
	
	if ( endstr ) {
	   if ( !digit_cnt ) *endstr = (char *) strinit;
	     else *endstr = (char *) str;
	} /* endif */
	
	return (void *)rslt;
} /* strtod_flt */

/* function to free memory used by numeric structure
 */
void  free_numeric(
void *numeric_value)
{
	if ( numeric_value ) free(numeric_value);
} /* free_numeric */

/* function to perform unary '-' operation
 */
void *negate_numeric(
void *value)
{
	numeric_ptr rslt = (numeric_ptr)value;
	
	switch ( rslt->type ) {
        case INT_TYPE:
            rslt->value.int_value = -rslt->value.int_value;
            break;
        case DBL_TYPE:
            rslt->value.dbl_value = -rslt->value.dbl_value;
            break;
    } /* endswitch */

    return (void *)rslt;
} /* negate_numeric */

/* function to perform binary operators
 *	op_symbol - operation to perform
 *		ADD_OP	== perform '+'
 *		SUB_OP  == perform '-'
 *		DIV_OP	== perform '/'
 *		MUL_OP	== perform '*'
 *		ASN_OP	== perform '='
 *  l_value - pointer to left hand value
 *  r_value - pointer to right hand value
 */
void          *numeric_ops(
                           char  op_symbol,
                           void *l_value,
                           void *r_value)
{
	numeric_ptr lval = (numeric_ptr)l_value,
				rval = (numeric_ptr)r_value,
				rslt = (op_symbol == ASN_OP) ? lval : (numeric_ptr)calloc(1,sizeof(numeric));
				
	switch ( op_symbol ) {
		case ADD_OP:
			if ( lval->type == rval->type ) {
				rslt->type = lval->type;
				switch ( lval->type ) {
             		case INT_TYPE:
		  				rslt->value.int_value = lval->value.int_value + rval->value.int_value;
                 		break;
             		case DBL_TYPE:
                 		rslt->value.dbl_value = lval->value.dbl_value + rval->value.dbl_value;
                 		break;
               	} /* endswitch */
        	  } else {
        	  	rslt->type = DBL_TYPE;
        	  	switch ( lval->type ) {
            		case INT_TYPE:
                		rslt->value.dbl_value = (double)(lval->value.int_value) + rval->value.dbl_value;
                		break;
            		case DBL_TYPE:
                		rslt->value.dbl_value = lval->value.dbl_value + (double)(rval->value.int_value);
                		break;
                		
		   		} /* endswitch */
        	} /* endif */
			break;
		case SUB_OP:
			if ( lval->type == rval->type ) {
				rslt->type = lval->type;
				switch ( lval->type ) {
             		case INT_TYPE:
		  				rslt->value.int_value = lval->value.int_value - rval->value.int_value;
                 		break;
             		case DBL_TYPE:
                 		rslt->value.dbl_value = lval->value.dbl_value - rval->value.dbl_value;
                 		break;
               	} /* endswitch */
        	  } else {
        	  	rslt->type = DBL_TYPE;
        	  	switch ( lval->type ) {
            		case INT_TYPE:
                		rslt->value.dbl_value = (double)(lval->value.int_value) - rval->value.dbl_value;
                		break;
            		case DBL_TYPE:
                		rslt->value.dbl_value = lval->value.dbl_value - (double)(rval->value.int_value);
                		break;
                		
		   		} /* endswitch */
        	} /* endif */		
			break;
		case DIV_OP:
			rslt->type = DBL_TYPE;
			if ( lval->type == rval->type ) {
				switch ( lval->type ) {
             		case INT_TYPE:
		  				rslt->value.dbl_value = (double)(lval->value.int_value) / (double)(rval->value.int_value);
                 		break;
             		case DBL_TYPE:
                 		rslt->value.dbl_value = lval->value.dbl_value / rval->value.dbl_value;
                 		break;
               	} /* endswitch */
        	  } else {
        	  	switch ( lval->type ) {
            		case INT_TYPE:
                		rslt->value.dbl_value = (double)(lval->value.int_value) / rval->value.dbl_value;
                		break;
            		case DBL_TYPE:
                		rslt->value.dbl_value = lval->value.dbl_value / (double)(rval->value.int_value);
                		break;
                		
		   		} /* endswitch */
        	} /* endif */				
			break;
		case MUL_OP:
			if ( lval->type == rval->type ) {
				rslt->type = lval->type;
				switch ( lval->type ) {
             		case INT_TYPE:
		  				rslt->value.int_value = lval->value.int_value * rval->value.int_value;
                 		break;
             		case DBL_TYPE:
                 		rslt->value.dbl_value = lval->value.dbl_value * rval->value.dbl_value;
                 		break;
               	} /* endswitch */
        	  } else {
        	  	rslt->type = DBL_TYPE;
        	  	switch ( lval->type ) {
            		case INT_TYPE:
                		rslt->value.dbl_value = (double)(lval->value.int_value) * rval->value.dbl_value;
                		break;
            		case DBL_TYPE:
                		rslt->value.dbl_value = lval->value.dbl_value * (double)(rval->value.int_value);
                		break;
                		
		   		} /* endswitch */
        	} /* endif */			
			break;
		case ASN_OP:
			if ( !lval ) lval = (numeric_ptr)calloc(1,sizeof(numeric));
			lval->type = rval->type;
			lval->value = rval->value;
			rslt = lval;
			break;
	} /* endswitch */
	
	
	return (void *)rslt;
} /* numeric_ops */
