You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gnucash/src/calculation/numeric_ops.c

388 lines
11 KiB

/***************************************************************************
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 */