1671 lines
31 KiB
C
1671 lines
31 KiB
C
/* src/interfaces/ecpg/pgtypeslib/numeric.c */
|
|
|
|
#include "postgres_fe.h"
|
|
#include <ctype.h>
|
|
#include <limits.h>
|
|
|
|
#include "extern.h"
|
|
#include "pgtypes_error.h"
|
|
|
|
#define Max(x, y) ((x) > (y) ? (x) : (y))
|
|
#define Min(x, y) ((x) < (y) ? (x) : (y))
|
|
|
|
#define init_var(v) memset(v,0,sizeof(numeric))
|
|
|
|
#define digitbuf_alloc(size) ((NumericDigit *) pgtypes_alloc(size))
|
|
#define digitbuf_free(buf) \
|
|
do { \
|
|
if ((buf) != NULL) \
|
|
free(buf); \
|
|
} while (0)
|
|
|
|
#include "pgtypes_numeric.h"
|
|
|
|
#if 0
|
|
/* ----------
|
|
* apply_typmod() -
|
|
*
|
|
* Do bounds checking and rounding according to the attributes
|
|
* typmod field.
|
|
* ----------
|
|
*/
|
|
static int
|
|
apply_typmod(numeric *var, long typmod)
|
|
{
|
|
int precision;
|
|
int scale;
|
|
int maxweight;
|
|
int i;
|
|
|
|
/* Do nothing if we have a default typmod (-1) */
|
|
if (typmod < (long) (VARHDRSZ))
|
|
return (0);
|
|
|
|
typmod -= VARHDRSZ;
|
|
precision = (typmod >> 16) & 0xffff;
|
|
scale = typmod & 0xffff;
|
|
maxweight = precision - scale;
|
|
|
|
/* Round to target scale */
|
|
i = scale + var->weight + 1;
|
|
if (i >= 0 && var->ndigits > i)
|
|
{
|
|
int carry = (var->digits[i] > 4) ? 1 : 0;
|
|
|
|
var->ndigits = i;
|
|
|
|
while (carry)
|
|
{
|
|
carry += var->digits[--i];
|
|
var->digits[i] = carry % 10;
|
|
carry /= 10;
|
|
}
|
|
|
|
if (i < 0)
|
|
{
|
|
var->digits--;
|
|
var->ndigits++;
|
|
var->weight++;
|
|
}
|
|
}
|
|
else
|
|
var->ndigits = Max(0, Min(i, var->ndigits));
|
|
|
|
/*
|
|
* Check for overflow - note we can't do this before rounding, because
|
|
* rounding could raise the weight. Also note that the var's weight could
|
|
* be inflated by leading zeroes, which will be stripped before storage
|
|
* but perhaps might not have been yet. In any case, we must recognize a
|
|
* true zero, whose weight doesn't mean anything.
|
|
*/
|
|
if (var->weight >= maxweight)
|
|
{
|
|
/* Determine true weight; and check for all-zero result */
|
|
int tweight = var->weight;
|
|
|
|
for (i = 0; i < var->ndigits; i++)
|
|
{
|
|
if (var->digits[i])
|
|
break;
|
|
tweight--;
|
|
}
|
|
|
|
if (tweight >= maxweight && i < var->ndigits)
|
|
{
|
|
errno = PGTYPES_NUM_OVERFLOW;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
var->rscale = scale;
|
|
var->dscale = scale;
|
|
return (0);
|
|
}
|
|
#endif
|
|
|
|
/* ----------
|
|
* alloc_var() -
|
|
*
|
|
* Allocate a digit buffer of ndigits digits (plus a spare digit for rounding)
|
|
* ----------
|
|
*/
|
|
static int
|
|
alloc_var(numeric *var, int ndigits)
|
|
{
|
|
digitbuf_free(var->buf);
|
|
var->buf = digitbuf_alloc(ndigits + 1);
|
|
if (var->buf == NULL)
|
|
return -1;
|
|
var->buf[0] = 0;
|
|
var->digits = var->buf + 1;
|
|
var->ndigits = ndigits;
|
|
return 0;
|
|
}
|
|
|
|
numeric *
|
|
PGTYPESnumeric_new(void)
|
|
{
|
|
numeric *var;
|
|
|
|
if ((var = (numeric *) pgtypes_alloc(sizeof(numeric))) == NULL)
|
|
return NULL;
|
|
|
|
if (alloc_var(var, 0) < 0)
|
|
{
|
|
free(var);
|
|
return NULL;
|
|
}
|
|
|
|
return var;
|
|
}
|
|
|
|
decimal *
|
|
PGTYPESdecimal_new(void)
|
|
{
|
|
decimal *var;
|
|
|
|
if ((var = (decimal *) pgtypes_alloc(sizeof(decimal))) == NULL)
|
|
return NULL;
|
|
|
|
memset(var, 0, sizeof(decimal));
|
|
|
|
return var;
|
|
}
|
|
|
|
/* ----------
|
|
* set_var_from_str()
|
|
*
|
|
* Parse a string and put the number into a variable
|
|
* ----------
|
|
*/
|
|
static int
|
|
set_var_from_str(char *str, char **ptr, numeric *dest)
|
|
{
|
|
bool have_dp = FALSE;
|
|
int i = 0;
|
|
|
|
errno = 0;
|
|
*ptr = str;
|
|
while (*(*ptr))
|
|
{
|
|
if (!isspace((unsigned char) *(*ptr)))
|
|
break;
|
|
(*ptr)++;
|
|
}
|
|
|
|
if (pg_strncasecmp(*ptr, "NaN", 3) == 0)
|
|
{
|
|
*ptr += 3;
|
|
dest->sign = NUMERIC_NAN;
|
|
|
|
/* Should be nothing left but spaces */
|
|
while (*(*ptr))
|
|
{
|
|
if (!isspace((unsigned char) *(*ptr)))
|
|
{
|
|
errno = PGTYPES_NUM_BAD_NUMERIC;
|
|
return -1;
|
|
}
|
|
(*ptr)++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (alloc_var(dest, strlen((*ptr))) < 0)
|
|
return -1;
|
|
dest->weight = -1;
|
|
dest->dscale = 0;
|
|
dest->sign = NUMERIC_POS;
|
|
|
|
switch (*(*ptr))
|
|
{
|
|
case '+':
|
|
dest->sign = NUMERIC_POS;
|
|
(*ptr)++;
|
|
break;
|
|
|
|
case '-':
|
|
dest->sign = NUMERIC_NEG;
|
|
(*ptr)++;
|
|
break;
|
|
}
|
|
|
|
if (*(*ptr) == '.')
|
|
{
|
|
have_dp = TRUE;
|
|
(*ptr)++;
|
|
}
|
|
|
|
if (!isdigit((unsigned char) *(*ptr)))
|
|
{
|
|
errno = PGTYPES_NUM_BAD_NUMERIC;
|
|
return -1;
|
|
}
|
|
|
|
while (*(*ptr))
|
|
{
|
|
if (isdigit((unsigned char) *(*ptr)))
|
|
{
|
|
dest->digits[i++] = *(*ptr)++ - '0';
|
|
if (!have_dp)
|
|
dest->weight++;
|
|
else
|
|
dest->dscale++;
|
|
}
|
|
else if (*(*ptr) == '.')
|
|
{
|
|
if (have_dp)
|
|
{
|
|
errno = PGTYPES_NUM_BAD_NUMERIC;
|
|
return -1;
|
|
}
|
|
have_dp = TRUE;
|
|
(*ptr)++;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
dest->ndigits = i;
|
|
|
|
/* Handle exponent, if any */
|
|
if (*(*ptr) == 'e' || *(*ptr) == 'E')
|
|
{
|
|
long exponent;
|
|
char *endptr;
|
|
|
|
(*ptr)++;
|
|
exponent = strtol(*ptr, &endptr, 10);
|
|
if (endptr == (*ptr))
|
|
{
|
|
errno = PGTYPES_NUM_BAD_NUMERIC;
|
|
return -1;
|
|
}
|
|
(*ptr) = endptr;
|
|
if (exponent > NUMERIC_MAX_PRECISION ||
|
|
exponent < -NUMERIC_MAX_PRECISION)
|
|
{
|
|
errno = PGTYPES_NUM_BAD_NUMERIC;
|
|
return -1;
|
|
}
|
|
dest->weight += (int) exponent;
|
|
dest->dscale -= (int) exponent;
|
|
if (dest->dscale < 0)
|
|
dest->dscale = 0;
|
|
}
|
|
|
|
/* Should be nothing left but spaces */
|
|
while (*(*ptr))
|
|
{
|
|
if (!isspace((unsigned char) *(*ptr)))
|
|
{
|
|
errno = PGTYPES_NUM_BAD_NUMERIC;
|
|
return -1;
|
|
}
|
|
(*ptr)++;
|
|
}
|
|
|
|
/* Strip any leading zeroes */
|
|
while (dest->ndigits > 0 && *(dest->digits) == 0)
|
|
{
|
|
(dest->digits)++;
|
|
(dest->weight)--;
|
|
(dest->ndigits)--;
|
|
}
|
|
if (dest->ndigits == 0)
|
|
dest->weight = 0;
|
|
|
|
dest->rscale = dest->dscale;
|
|
return (0);
|
|
}
|
|
|
|
|
|
/* ----------
|
|
* get_str_from_var() -
|
|
*
|
|
* Convert a var to text representation (guts of numeric_out).
|
|
* CAUTION: var's contents may be modified by rounding!
|
|
* ----------
|
|
*/
|
|
static char *
|
|
get_str_from_var(numeric *var, int dscale)
|
|
{
|
|
char *str;
|
|
char *cp;
|
|
int i;
|
|
int d;
|
|
|
|
if (var->sign == NUMERIC_NAN)
|
|
{
|
|
str = (char *) pgtypes_alloc(4);
|
|
if (str == NULL)
|
|
return NULL;
|
|
sprintf(str, "NaN");
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* Check if we must round up before printing the value and do so.
|
|
*/
|
|
i = dscale + var->weight + 1;
|
|
if (i >= 0 && var->ndigits > i)
|
|
{
|
|
int carry = (var->digits[i] > 4) ? 1 : 0;
|
|
|
|
var->ndigits = i;
|
|
|
|
while (carry)
|
|
{
|
|
carry += var->digits[--i];
|
|
var->digits[i] = carry % 10;
|
|
carry /= 10;
|
|
}
|
|
|
|
if (i < 0)
|
|
{
|
|
var->digits--;
|
|
var->ndigits++;
|
|
var->weight++;
|
|
}
|
|
}
|
|
else
|
|
var->ndigits = Max(0, Min(i, var->ndigits));
|
|
|
|
/*
|
|
* Allocate space for the result
|
|
*/
|
|
if ((str = (char *) pgtypes_alloc(Max(0, dscale) + Max(0, var->weight) + 4)) == NULL)
|
|
return NULL;
|
|
cp = str;
|
|
|
|
/*
|
|
* Output a dash for negative values
|
|
*/
|
|
if (var->sign == NUMERIC_NEG)
|
|
*cp++ = '-';
|
|
|
|
/*
|
|
* Output all digits before the decimal point
|
|
*/
|
|
i = Max(var->weight, 0);
|
|
d = 0;
|
|
|
|
while (i >= 0)
|
|
{
|
|
if (i <= var->weight && d < var->ndigits)
|
|
*cp++ = var->digits[d++] + '0';
|
|
else
|
|
*cp++ = '0';
|
|
i--;
|
|
}
|
|
|
|
/*
|
|
* If requested, output a decimal point and all the digits that follow it.
|
|
*/
|
|
if (dscale > 0)
|
|
{
|
|
*cp++ = '.';
|
|
while (i >= -dscale)
|
|
{
|
|
if (i <= var->weight && d < var->ndigits)
|
|
*cp++ = var->digits[d++] + '0';
|
|
else
|
|
*cp++ = '0';
|
|
i--;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* terminate the string and return it
|
|
*/
|
|
*cp = '\0';
|
|
return str;
|
|
}
|
|
|
|
numeric *
|
|
PGTYPESnumeric_from_asc(char *str, char **endptr)
|
|
{
|
|
numeric *value = (numeric *) pgtypes_alloc(sizeof(numeric));
|
|
int ret;
|
|
|
|
char *realptr;
|
|
char **ptr = (endptr != NULL) ? endptr : &realptr;
|
|
|
|
if (!value)
|
|
return (NULL);
|
|
|
|
ret = set_var_from_str(str, ptr, value);
|
|
if (ret)
|
|
{
|
|
PGTYPESnumeric_free(value);
|
|
return (NULL);
|
|
}
|
|
|
|
return (value);
|
|
}
|
|
|
|
char *
|
|
PGTYPESnumeric_to_asc(numeric *num, int dscale)
|
|
{
|
|
numeric *numcopy = PGTYPESnumeric_new();
|
|
char *s;
|
|
|
|
if (numcopy == NULL)
|
|
return NULL;
|
|
|
|
if (PGTYPESnumeric_copy(num, numcopy) < 0)
|
|
{
|
|
PGTYPESnumeric_free(numcopy);
|
|
return NULL;
|
|
}
|
|
|
|
if (dscale < 0)
|
|
dscale = num->dscale;
|
|
|
|
/* get_str_from_var may change its argument */
|
|
s = get_str_from_var(numcopy, dscale);
|
|
PGTYPESnumeric_free(numcopy);
|
|
return (s);
|
|
}
|
|
|
|
/* ----------
|
|
* zero_var() -
|
|
*
|
|
* Set a variable to ZERO.
|
|
* Note: rscale and dscale are not touched.
|
|
* ----------
|
|
*/
|
|
static void
|
|
zero_var(numeric *var)
|
|
{
|
|
digitbuf_free(var->buf);
|
|
var->buf = NULL;
|
|
var->digits = NULL;
|
|
var->ndigits = 0;
|
|
var->weight = 0; /* by convention; doesn't really matter */
|
|
var->sign = NUMERIC_POS; /* anything but NAN... */
|
|
}
|
|
|
|
void
|
|
PGTYPESnumeric_free(numeric *var)
|
|
{
|
|
digitbuf_free(var->buf);
|
|
free(var);
|
|
}
|
|
|
|
void
|
|
PGTYPESdecimal_free(decimal *var)
|
|
{
|
|
free(var);
|
|
}
|
|
|
|
/* ----------
|
|
* cmp_abs() -
|
|
*
|
|
* Compare the absolute values of var1 and var2
|
|
* Returns: -1 for ABS(var1) < ABS(var2)
|
|
* 0 for ABS(var1) == ABS(var2)
|
|
* 1 for ABS(var1) > ABS(var2)
|
|
* ----------
|
|
*/
|
|
static int
|
|
cmp_abs(numeric *var1, numeric *var2)
|
|
{
|
|
int i1 = 0;
|
|
int i2 = 0;
|
|
int w1 = var1->weight;
|
|
int w2 = var2->weight;
|
|
int stat;
|
|
|
|
while (w1 > w2 && i1 < var1->ndigits)
|
|
{
|
|
if (var1->digits[i1++] != 0)
|
|
return 1;
|
|
w1--;
|
|
}
|
|
while (w2 > w1 && i2 < var2->ndigits)
|
|
{
|
|
if (var2->digits[i2++] != 0)
|
|
return -1;
|
|
w2--;
|
|
}
|
|
|
|
if (w1 == w2)
|
|
{
|
|
while (i1 < var1->ndigits && i2 < var2->ndigits)
|
|
{
|
|
stat = var1->digits[i1++] - var2->digits[i2++];
|
|
if (stat)
|
|
{
|
|
if (stat > 0)
|
|
return 1;
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
while (i1 < var1->ndigits)
|
|
{
|
|
if (var1->digits[i1++] != 0)
|
|
return 1;
|
|
}
|
|
while (i2 < var2->ndigits)
|
|
{
|
|
if (var2->digits[i2++] != 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* ----------
|
|
* add_abs() -
|
|
*
|
|
* Add the absolute values of two variables into result.
|
|
* result might point to one of the operands without danger.
|
|
* ----------
|
|
*/
|
|
static int
|
|
add_abs(numeric *var1, numeric *var2, numeric *result)
|
|
{
|
|
NumericDigit *res_buf;
|
|
NumericDigit *res_digits;
|
|
int res_ndigits;
|
|
int res_weight;
|
|
int res_rscale;
|
|
int res_dscale;
|
|
int i,
|
|
i1,
|
|
i2;
|
|
int carry = 0;
|
|
|
|
/* copy these values into local vars for speed in inner loop */
|
|
int var1ndigits = var1->ndigits;
|
|
int var2ndigits = var2->ndigits;
|
|
NumericDigit *var1digits = var1->digits;
|
|
NumericDigit *var2digits = var2->digits;
|
|
|
|
res_weight = Max(var1->weight, var2->weight) + 1;
|
|
res_rscale = Max(var1->rscale, var2->rscale);
|
|
res_dscale = Max(var1->dscale, var2->dscale);
|
|
res_ndigits = res_rscale + res_weight + 1;
|
|
if (res_ndigits <= 0)
|
|
res_ndigits = 1;
|
|
|
|
if ((res_buf = digitbuf_alloc(res_ndigits)) == NULL)
|
|
return -1;
|
|
res_digits = res_buf;
|
|
|
|
i1 = res_rscale + var1->weight + 1;
|
|
i2 = res_rscale + var2->weight + 1;
|
|
for (i = res_ndigits - 1; i >= 0; i--)
|
|
{
|
|
i1--;
|
|
i2--;
|
|
if (i1 >= 0 && i1 < var1ndigits)
|
|
carry += var1digits[i1];
|
|
if (i2 >= 0 && i2 < var2ndigits)
|
|
carry += var2digits[i2];
|
|
|
|
if (carry >= 10)
|
|
{
|
|
res_digits[i] = carry - 10;
|
|
carry = 1;
|
|
}
|
|
else
|
|
{
|
|
res_digits[i] = carry;
|
|
carry = 0;
|
|
}
|
|
}
|
|
|
|
while (res_ndigits > 0 && *res_digits == 0)
|
|
{
|
|
res_digits++;
|
|
res_weight--;
|
|
res_ndigits--;
|
|
}
|
|
while (res_ndigits > 0 && res_digits[res_ndigits - 1] == 0)
|
|
res_ndigits--;
|
|
|
|
if (res_ndigits == 0)
|
|
res_weight = 0;
|
|
|
|
digitbuf_free(result->buf);
|
|
result->ndigits = res_ndigits;
|
|
result->buf = res_buf;
|
|
result->digits = res_digits;
|
|
result->weight = res_weight;
|
|
result->rscale = res_rscale;
|
|
result->dscale = res_dscale;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* ----------
|
|
* sub_abs() -
|
|
*
|
|
* Subtract the absolute value of var2 from the absolute value of var1
|
|
* and store in result. result might point to one of the operands
|
|
* without danger.
|
|
*
|
|
* ABS(var1) MUST BE GREATER OR EQUAL ABS(var2) !!!
|
|
* ----------
|
|
*/
|
|
static int
|
|
sub_abs(numeric *var1, numeric *var2, numeric *result)
|
|
{
|
|
NumericDigit *res_buf;
|
|
NumericDigit *res_digits;
|
|
int res_ndigits;
|
|
int res_weight;
|
|
int res_rscale;
|
|
int res_dscale;
|
|
int i,
|
|
i1,
|
|
i2;
|
|
int borrow = 0;
|
|
|
|
/* copy these values into local vars for speed in inner loop */
|
|
int var1ndigits = var1->ndigits;
|
|
int var2ndigits = var2->ndigits;
|
|
NumericDigit *var1digits = var1->digits;
|
|
NumericDigit *var2digits = var2->digits;
|
|
|
|
res_weight = var1->weight;
|
|
res_rscale = Max(var1->rscale, var2->rscale);
|
|
res_dscale = Max(var1->dscale, var2->dscale);
|
|
res_ndigits = res_rscale + res_weight + 1;
|
|
if (res_ndigits <= 0)
|
|
res_ndigits = 1;
|
|
|
|
if ((res_buf = digitbuf_alloc(res_ndigits)) == NULL)
|
|
return -1;
|
|
res_digits = res_buf;
|
|
|
|
i1 = res_rscale + var1->weight + 1;
|
|
i2 = res_rscale + var2->weight + 1;
|
|
for (i = res_ndigits - 1; i >= 0; i--)
|
|
{
|
|
i1--;
|
|
i2--;
|
|
if (i1 >= 0 && i1 < var1ndigits)
|
|
borrow += var1digits[i1];
|
|
if (i2 >= 0 && i2 < var2ndigits)
|
|
borrow -= var2digits[i2];
|
|
|
|
if (borrow < 0)
|
|
{
|
|
res_digits[i] = borrow + 10;
|
|
borrow = -1;
|
|
}
|
|
else
|
|
{
|
|
res_digits[i] = borrow;
|
|
borrow = 0;
|
|
}
|
|
}
|
|
|
|
while (res_ndigits > 0 && *res_digits == 0)
|
|
{
|
|
res_digits++;
|
|
res_weight--;
|
|
res_ndigits--;
|
|
}
|
|
while (res_ndigits > 0 && res_digits[res_ndigits - 1] == 0)
|
|
res_ndigits--;
|
|
|
|
if (res_ndigits == 0)
|
|
res_weight = 0;
|
|
|
|
digitbuf_free(result->buf);
|
|
result->ndigits = res_ndigits;
|
|
result->buf = res_buf;
|
|
result->digits = res_digits;
|
|
result->weight = res_weight;
|
|
result->rscale = res_rscale;
|
|
result->dscale = res_dscale;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ----------
|
|
* add_var() -
|
|
*
|
|
* Full version of add functionality on variable level (handling signs).
|
|
* result might point to one of the operands too without danger.
|
|
* ----------
|
|
*/
|
|
int
|
|
PGTYPESnumeric_add(numeric *var1, numeric *var2, numeric *result)
|
|
{
|
|
/*
|
|
* Decide on the signs of the two variables what to do
|
|
*/
|
|
if (var1->sign == NUMERIC_POS)
|
|
{
|
|
if (var2->sign == NUMERIC_POS)
|
|
{
|
|
/*
|
|
* Both are positive result = +(ABS(var1) + ABS(var2))
|
|
*/
|
|
if (add_abs(var1, var2, result) != 0)
|
|
return -1;
|
|
result->sign = NUMERIC_POS;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* var1 is positive, var2 is negative Must compare absolute values
|
|
*/
|
|
switch (cmp_abs(var1, var2))
|
|
{
|
|
case 0:
|
|
/* ----------
|
|
* ABS(var1) == ABS(var2)
|
|
* result = ZERO
|
|
* ----------
|
|
*/
|
|
zero_var(result);
|
|
result->rscale = Max(var1->rscale, var2->rscale);
|
|
result->dscale = Max(var1->dscale, var2->dscale);
|
|
break;
|
|
|
|
case 1:
|
|
/* ----------
|
|
* ABS(var1) > ABS(var2)
|
|
* result = +(ABS(var1) - ABS(var2))
|
|
* ----------
|
|
*/
|
|
if (sub_abs(var1, var2, result) != 0)
|
|
return -1;
|
|
result->sign = NUMERIC_POS;
|
|
break;
|
|
|
|
case -1:
|
|
/* ----------
|
|
* ABS(var1) < ABS(var2)
|
|
* result = -(ABS(var2) - ABS(var1))
|
|
* ----------
|
|
*/
|
|
if (sub_abs(var2, var1, result) != 0)
|
|
return -1;
|
|
result->sign = NUMERIC_NEG;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (var2->sign == NUMERIC_POS)
|
|
{
|
|
/* ----------
|
|
* var1 is negative, var2 is positive
|
|
* Must compare absolute values
|
|
* ----------
|
|
*/
|
|
switch (cmp_abs(var1, var2))
|
|
{
|
|
case 0:
|
|
/* ----------
|
|
* ABS(var1) == ABS(var2)
|
|
* result = ZERO
|
|
* ----------
|
|
*/
|
|
zero_var(result);
|
|
result->rscale = Max(var1->rscale, var2->rscale);
|
|
result->dscale = Max(var1->dscale, var2->dscale);
|
|
break;
|
|
|
|
case 1:
|
|
/* ----------
|
|
* ABS(var1) > ABS(var2)
|
|
* result = -(ABS(var1) - ABS(var2))
|
|
* ----------
|
|
*/
|
|
if (sub_abs(var1, var2, result) != 0)
|
|
return -1;
|
|
result->sign = NUMERIC_NEG;
|
|
break;
|
|
|
|
case -1:
|
|
/* ----------
|
|
* ABS(var1) < ABS(var2)
|
|
* result = +(ABS(var2) - ABS(var1))
|
|
* ----------
|
|
*/
|
|
if (sub_abs(var2, var1, result) != 0)
|
|
return -1;
|
|
result->sign = NUMERIC_POS;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* ----------
|
|
* Both are negative
|
|
* result = -(ABS(var1) + ABS(var2))
|
|
* ----------
|
|
*/
|
|
if (add_abs(var1, var2, result) != 0)
|
|
return -1;
|
|
result->sign = NUMERIC_NEG;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* ----------
|
|
* sub_var() -
|
|
*
|
|
* Full version of sub functionality on variable level (handling signs).
|
|
* result might point to one of the operands too without danger.
|
|
* ----------
|
|
*/
|
|
int
|
|
PGTYPESnumeric_sub(numeric *var1, numeric *var2, numeric *result)
|
|
{
|
|
/*
|
|
* Decide on the signs of the two variables what to do
|
|
*/
|
|
if (var1->sign == NUMERIC_POS)
|
|
{
|
|
if (var2->sign == NUMERIC_NEG)
|
|
{
|
|
/* ----------
|
|
* var1 is positive, var2 is negative
|
|
* result = +(ABS(var1) + ABS(var2))
|
|
* ----------
|
|
*/
|
|
if (add_abs(var1, var2, result) != 0)
|
|
return -1;
|
|
result->sign = NUMERIC_POS;
|
|
}
|
|
else
|
|
{
|
|
/* ----------
|
|
* Both are positive
|
|
* Must compare absolute values
|
|
* ----------
|
|
*/
|
|
switch (cmp_abs(var1, var2))
|
|
{
|
|
case 0:
|
|
/* ----------
|
|
* ABS(var1) == ABS(var2)
|
|
* result = ZERO
|
|
* ----------
|
|
*/
|
|
zero_var(result);
|
|
result->rscale = Max(var1->rscale, var2->rscale);
|
|
result->dscale = Max(var1->dscale, var2->dscale);
|
|
break;
|
|
|
|
case 1:
|
|
/* ----------
|
|
* ABS(var1) > ABS(var2)
|
|
* result = +(ABS(var1) - ABS(var2))
|
|
* ----------
|
|
*/
|
|
if (sub_abs(var1, var2, result) != 0)
|
|
return -1;
|
|
result->sign = NUMERIC_POS;
|
|
break;
|
|
|
|
case -1:
|
|
/* ----------
|
|
* ABS(var1) < ABS(var2)
|
|
* result = -(ABS(var2) - ABS(var1))
|
|
* ----------
|
|
*/
|
|
if (sub_abs(var2, var1, result) != 0)
|
|
return -1;
|
|
result->sign = NUMERIC_NEG;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (var2->sign == NUMERIC_NEG)
|
|
{
|
|
/* ----------
|
|
* Both are negative
|
|
* Must compare absolute values
|
|
* ----------
|
|
*/
|
|
switch (cmp_abs(var1, var2))
|
|
{
|
|
case 0:
|
|
/* ----------
|
|
* ABS(var1) == ABS(var2)
|
|
* result = ZERO
|
|
* ----------
|
|
*/
|
|
zero_var(result);
|
|
result->rscale = Max(var1->rscale, var2->rscale);
|
|
result->dscale = Max(var1->dscale, var2->dscale);
|
|
break;
|
|
|
|
case 1:
|
|
/* ----------
|
|
* ABS(var1) > ABS(var2)
|
|
* result = -(ABS(var1) - ABS(var2))
|
|
* ----------
|
|
*/
|
|
if (sub_abs(var1, var2, result) != 0)
|
|
return -1;
|
|
result->sign = NUMERIC_NEG;
|
|
break;
|
|
|
|
case -1:
|
|
/* ----------
|
|
* ABS(var1) < ABS(var2)
|
|
* result = +(ABS(var2) - ABS(var1))
|
|
* ----------
|
|
*/
|
|
if (sub_abs(var2, var1, result) != 0)
|
|
return -1;
|
|
result->sign = NUMERIC_POS;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* ----------
|
|
* var1 is negative, var2 is positive
|
|
* result = -(ABS(var1) + ABS(var2))
|
|
* ----------
|
|
*/
|
|
if (add_abs(var1, var2, result) != 0)
|
|
return -1;
|
|
result->sign = NUMERIC_NEG;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ----------
|
|
* mul_var() -
|
|
*
|
|
* Multiplication on variable level. Product of var1 * var2 is stored
|
|
* in result. Accuracy of result is determined by global_rscale.
|
|
* ----------
|
|
*/
|
|
int
|
|
PGTYPESnumeric_mul(numeric *var1, numeric *var2, numeric *result)
|
|
{
|
|
NumericDigit *res_buf;
|
|
NumericDigit *res_digits;
|
|
int res_ndigits;
|
|
int res_weight;
|
|
int res_sign;
|
|
int i,
|
|
ri,
|
|
i1,
|
|
i2;
|
|
long sum = 0;
|
|
int global_rscale = var1->rscale + var2->rscale;
|
|
|
|
res_weight = var1->weight + var2->weight + 2;
|
|
res_ndigits = var1->ndigits + var2->ndigits + 1;
|
|
if (var1->sign == var2->sign)
|
|
res_sign = NUMERIC_POS;
|
|
else
|
|
res_sign = NUMERIC_NEG;
|
|
|
|
if ((res_buf = digitbuf_alloc(res_ndigits)) == NULL)
|
|
return -1;
|
|
res_digits = res_buf;
|
|
memset(res_digits, 0, res_ndigits);
|
|
|
|
ri = res_ndigits;
|
|
for (i1 = var1->ndigits - 1; i1 >= 0; i1--)
|
|
{
|
|
sum = 0;
|
|
i = --ri;
|
|
|
|
for (i2 = var2->ndigits - 1; i2 >= 0; i2--)
|
|
{
|
|
sum += res_digits[i] + var1->digits[i1] * var2->digits[i2];
|
|
res_digits[i--] = sum % 10;
|
|
sum /= 10;
|
|
}
|
|
res_digits[i] = sum;
|
|
}
|
|
|
|
i = res_weight + global_rscale + 2;
|
|
if (i >= 0 && i < res_ndigits)
|
|
{
|
|
sum = (res_digits[i] > 4) ? 1 : 0;
|
|
res_ndigits = i;
|
|
i--;
|
|
while (sum)
|
|
{
|
|
sum += res_digits[i];
|
|
res_digits[i--] = sum % 10;
|
|
sum /= 10;
|
|
}
|
|
}
|
|
|
|
while (res_ndigits > 0 && *res_digits == 0)
|
|
{
|
|
res_digits++;
|
|
res_weight--;
|
|
res_ndigits--;
|
|
}
|
|
while (res_ndigits > 0 && res_digits[res_ndigits - 1] == 0)
|
|
res_ndigits--;
|
|
|
|
if (res_ndigits == 0)
|
|
{
|
|
res_sign = NUMERIC_POS;
|
|
res_weight = 0;
|
|
}
|
|
|
|
digitbuf_free(result->buf);
|
|
result->buf = res_buf;
|
|
result->digits = res_digits;
|
|
result->ndigits = res_ndigits;
|
|
result->weight = res_weight;
|
|
result->rscale = global_rscale;
|
|
result->sign = res_sign;
|
|
result->dscale = var1->dscale + var2->dscale;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Default scale selection for division
|
|
*
|
|
* Returns the appropriate display scale for the division result,
|
|
* and sets global_rscale to the result scale to use during div_var.
|
|
*
|
|
* Note that this must be called before div_var.
|
|
*/
|
|
static int
|
|
select_div_scale(numeric *var1, numeric *var2, int *rscale)
|
|
{
|
|
int weight1,
|
|
weight2,
|
|
qweight,
|
|
i;
|
|
NumericDigit firstdigit1,
|
|
firstdigit2;
|
|
int res_dscale;
|
|
|
|
/*
|
|
* The result scale of a division isn't specified in any SQL standard. For
|
|
* PostgreSQL we select a display scale that will give at least
|
|
* NUMERIC_MIN_SIG_DIGITS significant digits, so that numeric gives a
|
|
* result no less accurate than float8; but use a scale not less than
|
|
* either input's display scale.
|
|
*/
|
|
|
|
/* Get the actual (normalized) weight and first digit of each input */
|
|
|
|
weight1 = 0; /* values to use if var1 is zero */
|
|
firstdigit1 = 0;
|
|
for (i = 0; i < var1->ndigits; i++)
|
|
{
|
|
firstdigit1 = var1->digits[i];
|
|
if (firstdigit1 != 0)
|
|
{
|
|
weight1 = var1->weight - i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
weight2 = 0; /* values to use if var2 is zero */
|
|
firstdigit2 = 0;
|
|
for (i = 0; i < var2->ndigits; i++)
|
|
{
|
|
firstdigit2 = var2->digits[i];
|
|
if (firstdigit2 != 0)
|
|
{
|
|
weight2 = var2->weight - i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Estimate weight of quotient. If the two first digits are equal, we
|
|
* can't be sure, but assume that var1 is less than var2.
|
|
*/
|
|
qweight = weight1 - weight2;
|
|
if (firstdigit1 <= firstdigit2)
|
|
qweight--;
|
|
|
|
/* Select display scale */
|
|
res_dscale = NUMERIC_MIN_SIG_DIGITS - qweight;
|
|
res_dscale = Max(res_dscale, var1->dscale);
|
|
res_dscale = Max(res_dscale, var2->dscale);
|
|
res_dscale = Max(res_dscale, NUMERIC_MIN_DISPLAY_SCALE);
|
|
res_dscale = Min(res_dscale, NUMERIC_MAX_DISPLAY_SCALE);
|
|
|
|
/* Select result scale */
|
|
*rscale = res_dscale + 4;
|
|
|
|
return res_dscale;
|
|
}
|
|
|
|
int
|
|
PGTYPESnumeric_div(numeric *var1, numeric *var2, numeric *result)
|
|
{
|
|
NumericDigit *res_digits;
|
|
int res_ndigits;
|
|
int res_sign;
|
|
int res_weight;
|
|
numeric dividend;
|
|
numeric divisor[10];
|
|
int ndigits_tmp;
|
|
int weight_tmp;
|
|
int rscale_tmp;
|
|
int ri;
|
|
int i;
|
|
long guess;
|
|
long first_have;
|
|
long first_div;
|
|
int first_nextdigit;
|
|
int stat = 0;
|
|
int rscale;
|
|
int res_dscale = select_div_scale(var1, var2, &rscale);
|
|
int err = -1;
|
|
NumericDigit *tmp_buf;
|
|
|
|
/*
|
|
* First of all division by zero check
|
|
*/
|
|
ndigits_tmp = var2->ndigits + 1;
|
|
if (ndigits_tmp == 1)
|
|
{
|
|
errno = PGTYPES_NUM_DIVIDE_ZERO;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Determine the result sign, weight and number of digits to calculate
|
|
*/
|
|
if (var1->sign == var2->sign)
|
|
res_sign = NUMERIC_POS;
|
|
else
|
|
res_sign = NUMERIC_NEG;
|
|
res_weight = var1->weight - var2->weight + 1;
|
|
res_ndigits = rscale + res_weight;
|
|
if (res_ndigits <= 0)
|
|
res_ndigits = 1;
|
|
|
|
/*
|
|
* Now result zero check
|
|
*/
|
|
if (var1->ndigits == 0)
|
|
{
|
|
zero_var(result);
|
|
result->rscale = rscale;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Initialize local variables
|
|
*/
|
|
init_var(÷nd);
|
|
for (i = 1; i < 10; i++)
|
|
init_var(&divisor[i]);
|
|
|
|
/*
|
|
* Make a copy of the divisor which has one leading zero digit
|
|
*/
|
|
divisor[1].ndigits = ndigits_tmp;
|
|
divisor[1].rscale = var2->ndigits;
|
|
divisor[1].sign = NUMERIC_POS;
|
|
divisor[1].buf = digitbuf_alloc(ndigits_tmp);
|
|
if (divisor[1].buf == NULL)
|
|
goto done;
|
|
divisor[1].digits = divisor[1].buf;
|
|
divisor[1].digits[0] = 0;
|
|
memcpy(&(divisor[1].digits[1]), var2->digits, ndigits_tmp - 1);
|
|
|
|
/*
|
|
* Make a copy of the dividend
|
|
*/
|
|
dividend.ndigits = var1->ndigits;
|
|
dividend.weight = 0;
|
|
dividend.rscale = var1->ndigits;
|
|
dividend.sign = NUMERIC_POS;
|
|
dividend.buf = digitbuf_alloc(var1->ndigits);
|
|
if (dividend.buf == NULL)
|
|
goto done;
|
|
dividend.digits = dividend.buf;
|
|
memcpy(dividend.digits, var1->digits, var1->ndigits);
|
|
|
|
/*
|
|
* Setup the result. Do the allocation in a temporary buffer first, so we
|
|
* don't free result->buf unless we have successfully allocated a buffer
|
|
* to replace it with.
|
|
*/
|
|
tmp_buf = digitbuf_alloc(res_ndigits + 2);
|
|
if (tmp_buf == NULL)
|
|
goto done;
|
|
digitbuf_free(result->buf);
|
|
result->buf = tmp_buf;
|
|
res_digits = result->buf;
|
|
result->digits = res_digits;
|
|
result->ndigits = res_ndigits;
|
|
result->weight = res_weight;
|
|
result->rscale = rscale;
|
|
result->sign = res_sign;
|
|
res_digits[0] = 0;
|
|
|
|
first_div = divisor[1].digits[1] * 10;
|
|
if (ndigits_tmp > 2)
|
|
first_div += divisor[1].digits[2];
|
|
|
|
first_have = 0;
|
|
first_nextdigit = 0;
|
|
|
|
weight_tmp = 1;
|
|
rscale_tmp = divisor[1].rscale;
|
|
|
|
for (ri = 0; ri <= res_ndigits; ri++)
|
|
{
|
|
first_have = first_have * 10;
|
|
if (first_nextdigit >= 0 && first_nextdigit < dividend.ndigits)
|
|
first_have += dividend.digits[first_nextdigit];
|
|
first_nextdigit++;
|
|
|
|
guess = (first_have * 10) / first_div + 1;
|
|
if (guess > 9)
|
|
guess = 9;
|
|
|
|
while (guess > 0)
|
|
{
|
|
if (divisor[guess].buf == NULL)
|
|
{
|
|
int i;
|
|
long sum = 0;
|
|
|
|
memcpy(&divisor[guess], &divisor[1], sizeof(numeric));
|
|
divisor[guess].buf = digitbuf_alloc(divisor[guess].ndigits);
|
|
if (divisor[guess].buf == NULL)
|
|
goto done;
|
|
divisor[guess].digits = divisor[guess].buf;
|
|
for (i = divisor[1].ndigits - 1; i >= 0; i--)
|
|
{
|
|
sum += divisor[1].digits[i] * guess;
|
|
divisor[guess].digits[i] = sum % 10;
|
|
sum /= 10;
|
|
}
|
|
}
|
|
|
|
divisor[guess].weight = weight_tmp;
|
|
divisor[guess].rscale = rscale_tmp;
|
|
|
|
stat = cmp_abs(÷nd, &divisor[guess]);
|
|
if (stat >= 0)
|
|
break;
|
|
|
|
guess--;
|
|
}
|
|
|
|
res_digits[ri + 1] = guess;
|
|
if (stat == 0)
|
|
{
|
|
ri++;
|
|
break;
|
|
}
|
|
|
|
weight_tmp--;
|
|
rscale_tmp++;
|
|
|
|
if (guess == 0)
|
|
continue;
|
|
|
|
if (sub_abs(÷nd, &divisor[guess], ÷nd) != 0)
|
|
goto done;
|
|
|
|
first_nextdigit = dividend.weight - weight_tmp;
|
|
first_have = 0;
|
|
if (first_nextdigit >= 0 && first_nextdigit < dividend.ndigits)
|
|
first_have = dividend.digits[first_nextdigit];
|
|
first_nextdigit++;
|
|
}
|
|
|
|
result->ndigits = ri + 1;
|
|
if (ri == res_ndigits + 1)
|
|
{
|
|
int carry = (res_digits[ri] > 4) ? 1 : 0;
|
|
|
|
result->ndigits = ri;
|
|
res_digits[ri] = 0;
|
|
|
|
while (carry && ri > 0)
|
|
{
|
|
carry += res_digits[--ri];
|
|
res_digits[ri] = carry % 10;
|
|
carry /= 10;
|
|
}
|
|
}
|
|
|
|
while (result->ndigits > 0 && *(result->digits) == 0)
|
|
{
|
|
(result->digits)++;
|
|
(result->weight)--;
|
|
(result->ndigits)--;
|
|
}
|
|
while (result->ndigits > 0 && result->digits[result->ndigits - 1] == 0)
|
|
(result->ndigits)--;
|
|
if (result->ndigits == 0)
|
|
result->sign = NUMERIC_POS;
|
|
|
|
result->dscale = res_dscale;
|
|
err = 0; /* if we've made it this far, return success */
|
|
|
|
done:
|
|
|
|
/*
|
|
* Tidy up
|
|
*/
|
|
if (dividend.buf != NULL)
|
|
digitbuf_free(dividend.buf);
|
|
|
|
for (i = 1; i < 10; i++)
|
|
{
|
|
if (divisor[i].buf != NULL)
|
|
digitbuf_free(divisor[i].buf);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
int
|
|
PGTYPESnumeric_cmp(numeric *var1, numeric *var2)
|
|
{
|
|
/* use cmp_abs function to calculate the result */
|
|
|
|
/* both are positive: normal comparation with cmp_abs */
|
|
if (var1->sign == NUMERIC_POS && var2->sign == NUMERIC_POS)
|
|
return cmp_abs(var1, var2);
|
|
|
|
/* both are negative: return the inverse of the normal comparation */
|
|
if (var1->sign == NUMERIC_NEG && var2->sign == NUMERIC_NEG)
|
|
{
|
|
/*
|
|
* instead of inverting the result, we invert the parameter ordering
|
|
*/
|
|
return cmp_abs(var2, var1);
|
|
}
|
|
|
|
/* one is positive, one is negative: trivial */
|
|
if (var1->sign == NUMERIC_POS && var2->sign == NUMERIC_NEG)
|
|
return 1;
|
|
if (var1->sign == NUMERIC_NEG && var2->sign == NUMERIC_POS)
|
|
return -1;
|
|
|
|
errno = PGTYPES_NUM_BAD_NUMERIC;
|
|
return INT_MAX;
|
|
|
|
}
|
|
|
|
int
|
|
PGTYPESnumeric_from_int(signed int int_val, numeric *var)
|
|
{
|
|
/* implicit conversion */
|
|
signed long int long_int = int_val;
|
|
|
|
return PGTYPESnumeric_from_long(long_int, var);
|
|
}
|
|
|
|
int
|
|
PGTYPESnumeric_from_long(signed long int long_val, numeric *var)
|
|
{
|
|
/* calculate the size of the long int number */
|
|
/* a number n needs log_10 n digits */
|
|
|
|
/*
|
|
* however we multiply by 10 each time and compare instead of calculating
|
|
* the logarithm
|
|
*/
|
|
|
|
int size = 0;
|
|
int i;
|
|
signed long int abs_long_val = long_val;
|
|
signed long int extract;
|
|
signed long int reach_limit;
|
|
|
|
if (abs_long_val < 0)
|
|
{
|
|
abs_long_val *= -1;
|
|
var->sign = NUMERIC_NEG;
|
|
}
|
|
else
|
|
var->sign = NUMERIC_POS;
|
|
|
|
reach_limit = 1;
|
|
do
|
|
{
|
|
size++;
|
|
reach_limit *= 10;
|
|
} while (reach_limit - 1 < abs_long_val && reach_limit <= LONG_MAX / 10);
|
|
|
|
if (reach_limit > LONG_MAX / 10)
|
|
{
|
|
/* add the first digit and a .0 */
|
|
size += 2;
|
|
}
|
|
else
|
|
{
|
|
/* always add a .0 */
|
|
size++;
|
|
reach_limit /= 10;
|
|
}
|
|
|
|
if (alloc_var(var, size) < 0)
|
|
return -1;
|
|
|
|
var->rscale = 1;
|
|
var->dscale = 1;
|
|
var->weight = size - 2;
|
|
|
|
i = 0;
|
|
do
|
|
{
|
|
extract = abs_long_val - (abs_long_val % reach_limit);
|
|
var->digits[i] = extract / reach_limit;
|
|
abs_long_val -= extract;
|
|
i++;
|
|
reach_limit /= 10;
|
|
|
|
/*
|
|
* we can abandon if abs_long_val reaches 0, because the memory is
|
|
* initialized properly and filled with '0', so converting 10000 in
|
|
* only one step is no problem
|
|
*/
|
|
} while (abs_long_val > 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
PGTYPESnumeric_copy(numeric *src, numeric *dst)
|
|
{
|
|
int i;
|
|
|
|
if (dst == NULL)
|
|
return -1;
|
|
zero_var(dst);
|
|
|
|
dst->weight = src->weight;
|
|
dst->rscale = src->rscale;
|
|
dst->dscale = src->dscale;
|
|
dst->sign = src->sign;
|
|
|
|
if (alloc_var(dst, src->ndigits) != 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < src->ndigits; i++)
|
|
dst->digits[i] = src->digits[i];
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
PGTYPESnumeric_from_double(double d, numeric *dst)
|
|
{
|
|
char buffer[100];
|
|
numeric *tmp;
|
|
int i;
|
|
|
|
if (sprintf(buffer, "%f", d) == 0)
|
|
return -1;
|
|
|
|
if ((tmp = PGTYPESnumeric_from_asc(buffer, NULL)) == NULL)
|
|
return -1;
|
|
i = PGTYPESnumeric_copy(tmp, dst);
|
|
PGTYPESnumeric_free(tmp);
|
|
if (i != 0)
|
|
return -1;
|
|
|
|
errno = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
numericvar_to_double(numeric *var, double *dp)
|
|
{
|
|
char *tmp;
|
|
double val;
|
|
char *endptr;
|
|
numeric *varcopy = PGTYPESnumeric_new();
|
|
|
|
if (varcopy == NULL)
|
|
return -1;
|
|
|
|
if (PGTYPESnumeric_copy(var, varcopy) < 0)
|
|
{
|
|
PGTYPESnumeric_free(varcopy);
|
|
return -1;
|
|
}
|
|
|
|
tmp = get_str_from_var(varcopy, varcopy->dscale);
|
|
PGTYPESnumeric_free(varcopy);
|
|
|
|
if (tmp == NULL)
|
|
return -1;
|
|
|
|
/*
|
|
* strtod does not reset errno to 0 in case of success.
|
|
*/
|
|
errno = 0;
|
|
val = strtod(tmp, &endptr);
|
|
if (errno == ERANGE)
|
|
{
|
|
free(tmp);
|
|
if (val == 0)
|
|
errno = PGTYPES_NUM_UNDERFLOW;
|
|
else
|
|
errno = PGTYPES_NUM_OVERFLOW;
|
|
return -1;
|
|
}
|
|
|
|
/* can't free tmp yet, endptr points still into it */
|
|
if (*endptr != '\0')
|
|
{
|
|
/* shouldn't happen ... */
|
|
free(tmp);
|
|
errno = PGTYPES_NUM_BAD_NUMERIC;
|
|
return -1;
|
|
}
|
|
free(tmp);
|
|
*dp = val;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
PGTYPESnumeric_to_double(numeric *nv, double *dp)
|
|
{
|
|
double tmp;
|
|
|
|
if (numericvar_to_double(nv, &tmp) != 0)
|
|
return -1;
|
|
*dp = tmp;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
PGTYPESnumeric_to_int(numeric *nv, int *ip)
|
|
{
|
|
long l;
|
|
int i;
|
|
|
|
if ((i = PGTYPESnumeric_to_long(nv, &l)) != 0)
|
|
return i;
|
|
|
|
if (l < -INT_MAX || l > INT_MAX)
|
|
{
|
|
errno = PGTYPES_NUM_OVERFLOW;
|
|
return -1;
|
|
}
|
|
|
|
*ip = (int) l;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
PGTYPESnumeric_to_long(numeric *nv, long *lp)
|
|
{
|
|
char *s = PGTYPESnumeric_to_asc(nv, 0);
|
|
char *endptr;
|
|
|
|
if (s == NULL)
|
|
return -1;
|
|
|
|
errno = 0;
|
|
*lp = strtol(s, &endptr, 10);
|
|
if (endptr == s)
|
|
{
|
|
/* this should not happen actually */
|
|
free(s);
|
|
return -1;
|
|
}
|
|
free(s);
|
|
if (errno == ERANGE)
|
|
{
|
|
if (*lp == LONG_MIN)
|
|
errno = PGTYPES_NUM_UNDERFLOW;
|
|
else
|
|
errno = PGTYPES_NUM_OVERFLOW;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
PGTYPESnumeric_to_decimal(numeric *src, decimal *dst)
|
|
{
|
|
int i;
|
|
|
|
if (src->ndigits > DECSIZE)
|
|
{
|
|
errno = PGTYPES_NUM_OVERFLOW;
|
|
return -1;
|
|
}
|
|
|
|
dst->weight = src->weight;
|
|
dst->rscale = src->rscale;
|
|
dst->dscale = src->dscale;
|
|
dst->sign = src->sign;
|
|
dst->ndigits = src->ndigits;
|
|
|
|
for (i = 0; i < src->ndigits; i++)
|
|
dst->digits[i] = src->digits[i];
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
PGTYPESnumeric_from_decimal(decimal *src, numeric *dst)
|
|
{
|
|
int i;
|
|
|
|
zero_var(dst);
|
|
|
|
dst->weight = src->weight;
|
|
dst->rscale = src->rscale;
|
|
dst->dscale = src->dscale;
|
|
dst->sign = src->sign;
|
|
|
|
if (alloc_var(dst, src->ndigits) != 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < src->ndigits; i++)
|
|
dst->digits[i] = src->digits[i];
|
|
|
|
return 0;
|
|
}
|