hpqtypes-1.2.2: libpqtypes/src/numerics.c
/*
* numeric.c
* Type handler for the numeric data types.
*
* Copyright (c) 2011 eSilo, LLC. All rights reserved.
* This is free software; see the source for copying conditions. There is
* NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE.
*/
#include "libpqtypes-int.h"
#include <errno.h>
/* Extremely annoying warning "conversion from 'int ' to 'short'" on
* Visual Studio 6. Normally this warning is useful but not in this
* case. Disable it.
*/
#if defined(PQT_MSVC) && PQT_MSVC <= 1200
# pragma warning (disable : 4244)
#endif
/*
* Macros and structures for receiving numeric field in binary
*/
#define NBASE 10000
#define HALF_NBASE 5000
#define DEC_DIGITS 4 /* decimal digits per NBASE digit */
#define MUL_GUARD_DIGITS 2 /* these are measured in NBASE digits */
#define DIV_GUARD_DIGITS 4
/*
* Hardcoded precision limit - arbitrary, but must be small enough that
* dscale values will fit in 14 bits.
*/
#define NUMERIC_MAX_PRECISION 1000
/*
* Sign values and macros to deal with packing/unpacking n_sign_dscale
*/
#define NUMERIC_SIGN_MASK 0xC000
#define NUMERIC_POS 0x0000
#define NUMERIC_NEG 0x4000
#define NUMERIC_NAN 0xC000
#define NUMERIC_DSCALE_MASK 0x3FFF
#define NUMERIC_SIGN(n) ((n)->n_sign_dscale & NUMERIC_SIGN_MASK)
#define NUMERIC_DSCALE(n) ((n)->n_sign_dscale & NUMERIC_DSCALE_MASK)
#define NUMERIC_IS_NAN(n) (NUMERIC_SIGN(n) != NUMERIC_POS && \
NUMERIC_SIGN(n) != NUMERIC_NEG)
typedef short NumericDigit;
static const int round_powers[4] = {0, 1000, 100, 10};
typedef struct NumericVar
{
int ndigits; /* # of digits in digits[] - can be 0! */
int weight; /* weight of first digit */
int sign; /* NUMERIC_POS, NUMERIC_NEG, or NUMERIC_NAN */
int dscale; /* display scale */
NumericDigit *buf; /* start of palloc'd space for digits[] */
NumericDigit *digits; /* base-NBASE digits */
} NumericVar;
static int str2num(PGtypeArgs *args, const char *str, NumericVar *dest);
static int num2str(char *out, size_t outl, NumericVar *var, int dscale);
int
pqt_put_int2(PGtypeArgs *args)
{
PGint2 *i2p = va_arg(args->ap, PGint2 *);
PUTNULLCHK(args, i2p);
pqt_buf_putint2(args->put.out, *i2p);
return 2;
}
int
pqt_get_int2(PGtypeArgs *args)
{
DECLVALUE(args);
PGint2 *i2p = va_arg(args->ap, PGint2 *);
CHKGETVALS(args, i2p);
if (args->format == TEXTFMT)
{
int n;
errno = 0;
if ((n = (int) strtol(value, NULL, 10)) == 0 && errno)
RERR_STR2INT(args);
*i2p = (PGint2)n;
return 0;
}
*i2p = pqt_buf_getint2(value);
return 0;
}
/* handles oid as well */
int
pqt_put_int4(PGtypeArgs *args)
{
PGint4 *i4p = va_arg(args->ap, PGint4 *);
PUTNULLCHK(args, i4p);
*(PGint4 *)args->put.out = (PGint4) htonl(*i4p);
return 4;
}
/* handles oid as well */
int
pqt_get_int4(PGtypeArgs *args)
{
DECLVALUE(args);
PGint4 *i4p = va_arg(args->ap, PGint4 *);
CHKGETVALS(args, i4p);
if (args->format == TEXTFMT)
{
PGint4 n;
/* Use strtoul so this can support Oid */
if ((n = (PGint4) strtoul(value, NULL, 10)) == 0 && errno)
RERR_STR2INT(args);
*i4p = n;
return 0;
}
*i4p = (PGint4) pqt_buf_getint4(value);
return 0;
}
int
pqt_put_int8(PGtypeArgs *args)
{
PGint8 *i8p = va_arg(args->ap, PGint8 *);
PUTNULLCHK(args, i8p);
pqt_swap8(args->put.out, i8p, 1);
return 8;
}
int
pqt_get_int8(PGtypeArgs *args)
{
DECLVALUE(args);
PGint8 *i8p = va_arg(args->ap, PGint8 *);
CHKGETVALS(args, i8p);
if (args->format == TEXTFMT)
{
if (pqt_text_to_int8(value, i8p) == -1)
RERR_STR2INT(args);
return 0;
}
pqt_swap8(i8p, value, 0);
return 0;
}
int
pqt_put_float4(PGtypeArgs *args)
{
PGfloat4 *f4p = va_arg(args->ap, PGfloat4 *);
PUTNULLCHK(args, f4p);
void *vp = (void *)f4p;
pqt_buf_putint4(args->put.out, *(int *) vp);
return 4;
}
int
pqt_get_float4(PGtypeArgs *args)
{
DECLVALUE(args);
int *f4p = (int *) va_arg(args->ap, PGfloat4 *);
CHKGETVALS(args, f4p);
if (args->format == TEXTFMT)
{
PGfloat4 f;
errno = 0;
if ((f = (PGfloat4) strtod(value, NULL)) == 0 && errno)
RERR_STR2INT(args);
*(PGfloat4 *) f4p = f;
return 0;
}
*f4p = pqt_buf_getint4(value);
return 0;
}
int
pqt_put_float8(PGtypeArgs *args)
{
PGfloat8 *f8p = va_arg(args->ap, PGfloat8 *);
PUTNULLCHK(args, f8p);
pqt_swap8(args->put.out, f8p, 1);
return 8;
}
int
pqt_get_float8(PGtypeArgs *args)
{
DECLVALUE(args);
PGfloat8 *f8p = va_arg(args->ap, PGfloat8 *);
CHKGETVALS(args, f8p);
if (args->format == TEXTFMT)
{
if (!pqt_text_to_float8(f8p, value, NULL))
RERR_STR2INT(args);
return 0;
}
pqt_swap8(f8p, value, 0);
return 0;
}
/* exposing a NumericVar struct to a libpq user, or something similar,
* doesn't seem useful w/o a library to operate on it. Instead, we
* always expose a numeric in text format and let the API user decide
* how to use it .. like strod or a 3rd party big number library. We
* always send a numeric in binary though.
*/
int
pqt_put_numeric(PGtypeArgs *args)
{
int numlen;
NumericVar num = {0};
short *out;
PGnumeric str = va_arg(args->ap, PGnumeric);
PUTNULLCHK(args, str);
if (str2num(args, str, &num))
{
if (num.digits)
free(num.digits);
return -1;
}
/* variable length data type, grow args->put.out buffer if needed */
numlen = (int) sizeof(short) * (4 + num.ndigits);
if (args->put.expandBuffer(args, numlen) == -1)
return -1;
out = (short *) args->put.out;
*out++ = htons((short) num.ndigits);
*out++ = htons((short) num.weight);
*out++ = htons((short) num.sign);
*out++ = htons((short) num.dscale);
if (num.digits)
{
int i;
for (i=0; i < num.ndigits; i++)
*out++ = htons(num.digits[i]);
free(num.digits);
}
return numlen;
}
/* exposing a NumericVar struct to a libpq user, or something similar,
* doesn't seem useful w/o a library to operate on it. Instead, we
* always expose a numeric in text format and let the API user decide
* how to use it .. like strod or a 3rd party big number library.
*/
int
pqt_get_numeric(PGtypeArgs *args)
{
int i;
short *s;
NumericVar num;
DECLVALUE(args);
char buf[4096];
size_t len;
PGnumeric *str = va_arg(args->ap, PGnumeric *);
CHKGETVALS(args, str);
if (args->format == TEXTFMT)
{
*str = value;
return 0;
}
s = (short *) value;
num.ndigits = ntohs(*s); s++;
num.weight = ntohs(*s); s++;
num.sign = ntohs(*s); s++;
num.dscale = ntohs(*s); s++;
num.digits = (short *) malloc(num.ndigits * sizeof(short));
if (!num.digits)
RERR_MEM(args);
for (i=0; i < num.ndigits; i++)
{
num.digits[i] = ntohs(*s);
s++;
}
i = num2str(buf, sizeof(buf), &num, num.dscale);
free(num.digits);
/* num2str failed, only fails when 'str' is too small */
if (i == -1)
RERR(args, "out buffer is too small");
len = strlen(buf)+1;
*str = PQresultAlloc(args->get.result, len);
if (!*str)
RERR_MEM(args);
memcpy(*str, buf, len);
return 0;
}
/*
* round_var
*
* Round the value of a variable to no more than rscale decimal digits
* after the decimal point. NOTE: we allow rscale < 0 here, implying
* rounding before the decimal point.
*/
static void
round_var(NumericVar *var, int rscale)
{
NumericDigit *digits = var->digits;
int di;
int ndigits;
int carry;
var->dscale = rscale;
/* decimal digits wanted */
di = (var->weight + 1) * DEC_DIGITS + rscale;
/*
* If di = 0, the value loses all digits, but could round up to 1 if its
* first extra digit is >= 5. If di < 0 the result must be 0.
*/
if (di < 0)
{
var->ndigits = 0;
var->weight = 0;
var->sign = NUMERIC_POS;
}
else
{
/* NBASE digits wanted */
ndigits = (di + DEC_DIGITS - 1) / DEC_DIGITS;
/* 0, or number of decimal digits to keep in last NBASE digit */
di %= DEC_DIGITS;
if (ndigits < var->ndigits ||
(ndigits == var->ndigits && di > 0))
{
var->ndigits = ndigits;
if (di == 0)
carry = (digits[ndigits] >= HALF_NBASE) ? 1 : 0;
else
{
/* Must round within last NBASE digit */
int extra,
pow10;
pow10 = round_powers[di];
extra = digits[--ndigits] % pow10;
digits[ndigits] = digits[ndigits] - (NumericDigit) extra;
carry = 0;
if (extra >= pow10 / 2)
{
pow10 += digits[ndigits];
if (pow10 >= NBASE)
{
pow10 -= NBASE;
carry = 1;
}
digits[ndigits] = (NumericDigit) pow10;
}
}
/* Propagate carry if needed */
while (carry)
{
carry += digits[--ndigits];
if (carry >= NBASE)
{
digits[ndigits] = (NumericDigit) (carry - NBASE);
carry = 1;
}
else
{
digits[ndigits] = (NumericDigit) carry;
carry = 0;
}
}
if (ndigits < 0)
{
var->digits--;
var->ndigits++;
var->weight++;
}
}
}
}
/*
* strip_var
*
* Strip any leading and trailing zeroes from a numeric variable
*/
static void
strip_var(NumericVar *var)
{
NumericDigit *digits = var->digits;
int ndigits = var->ndigits;
/* Strip leading zeroes */
while (ndigits > 0 && *digits == 0)
{
digits++;
var->weight--;
ndigits--;
}
/* Strip trailing zeroes */
while (ndigits > 0 && digits[ndigits - 1] == 0)
ndigits--;
/* If it's zero, normalize the sign and weight */
if (ndigits == 0)
{
var->sign = NUMERIC_POS;
var->weight = 0;
}
var->digits = digits;
var->ndigits = ndigits;
}
/*
* str2num()
*
* Parse a string and put the number into a variable
* returns -1 on error and 0 for success.
*/
static int
str2num(PGtypeArgs *args, const char *str, NumericVar *dest)
{
const char *cp = str;
int have_dp = FALSE;
int i;
unsigned char *decdigits;
int sign = NUMERIC_POS;
int dweight = -1;
int ddigits;
int dscale = 0;
int weight;
int ndigits;
int offset;
NumericDigit *digits;
/*
* We first parse the string to extract decimal digits and determine the
* correct decimal weight. Then convert to NBASE representation.
*/
/* skip leading spaces */
while (*cp)
{
if (!isspace((unsigned char) *cp))
break;
cp++;
}
switch (*cp)
{
case '+':
sign = NUMERIC_POS;
cp++;
break;
case '-':
sign = NUMERIC_NEG;
cp++;
break;
}
if (*cp == '.')
{
have_dp = TRUE;
cp++;
}
if (!isdigit((unsigned char) *cp))
return args->errorf(args,
"invalid input syntax for type numeric: '%s'", str);
decdigits = (unsigned char *) malloc(strlen(cp) + DEC_DIGITS * 2);
/* leading padding for digit alignment later */
memset(decdigits, 0, DEC_DIGITS);
i = DEC_DIGITS;
while (*cp)
{
if (isdigit((unsigned char) *cp))
{
decdigits[i++] = *cp++ - '0';
if (!have_dp)
dweight++;
else
dscale++;
}
else if (*cp == '.')
{
if (have_dp)
{
free(decdigits);
return args->errorf(args,
"invalid input syntax for type numeric: '%s'", str);
}
have_dp = TRUE;
cp++;
}
else
break;
}
ddigits = i - DEC_DIGITS;
/* trailing padding for digit alignment later */
memset(decdigits + i, 0, DEC_DIGITS - 1);
/* Handle exponent, if any */
if (*cp == 'e' || *cp == 'E')
{
long exponent;
char *endptr;
cp++;
exponent = strtol(cp, &endptr, 10);
if (endptr == cp)
{
free(decdigits);
return args->errorf(args,
"invalid input syntax for type numeric: '%s'", str);
}
cp = endptr;
if (exponent > NUMERIC_MAX_PRECISION ||
exponent < -NUMERIC_MAX_PRECISION)
{
free(decdigits);
return args->errorf(args,
"invalid input syntax for type numeric: '%s'", str);
}
dweight += (int) exponent;
dscale -= (int) exponent;
if (dscale < 0)
dscale = 0;
}
/* Should be nothing left but spaces */
while (*cp)
{
if (!isspace((unsigned char) *cp))
{
free(decdigits);
return args->errorf(args,
"invalid input syntax for type numeric: '%s'", str);
}
cp++;
}
/*
* Okay, convert pure-decimal representation to base NBASE. First we need
* to determine the converted weight and ndigits. offset is the number of
* decimal zeroes to insert before the first given digit to have a
* correctly aligned first NBASE digit.
*/
if (dweight >= 0)
weight = (dweight + 1 + DEC_DIGITS - 1) / DEC_DIGITS - 1;
else
weight = -((-dweight - 1) / DEC_DIGITS + 1);
offset = (weight + 1) * DEC_DIGITS - (dweight + 1);
ndigits = (ddigits + offset + DEC_DIGITS - 1) / DEC_DIGITS;
dest->digits = (NumericDigit *) malloc((ndigits) * sizeof(NumericDigit));
dest->ndigits = ndigits;
dest->sign = sign;
dest->weight = weight;
dest->dscale = dscale;
i = DEC_DIGITS - offset;
digits = dest->digits;
while (ndigits-- > 0)
{
*digits++ = ((decdigits[i] * 10 + decdigits[i + 1]) * 10 +
decdigits[i + 2]) * 10 + decdigits[i + 3];
i += DEC_DIGITS;
}
free(decdigits);
/* Strip any leading/trailing zeroes, and normalize weight if zero */
strip_var(dest);
return 0;
}
/*
* num2str() -
*
* Convert a var to text representation (guts of numeric_out).
* CAUTION: var's contents may be modified by rounding!
* returns -1 on error and 0 for success.
*/
static int
num2str(char *out, size_t outl, NumericVar *var, int dscale)
{
//char *str;
char *cp;
char *endcp;
int i;
int d;
NumericDigit dig;
NumericDigit d1;
if (dscale < 0)
dscale = 0;
/*
* Check if we must round up before printing the value and do so.
*/
round_var(var, dscale);
/*
* Allocate space for the result.
*
* i is set to to # of decimal digits before decimal point. dscale is the
* # of decimal digits we will print after decimal point. We may generate
* as many as DEC_DIGITS-1 excess digits at the end, and in addition we
* need room for sign, decimal point, null terminator.
*/
i = (var->weight + 1) * DEC_DIGITS;
if (i <= 0)
i = 1;
if (outl <= (size_t) (i + dscale + DEC_DIGITS + 2))
return -1;
//str = palloc(i + dscale + DEC_DIGITS + 2);
//cp = str
cp = out;
/*
* Output a dash for negative values
*/
if (var->sign == NUMERIC_NEG)
*cp++ = '-';
/*
* Output all digits before the decimal point
*/
if (var->weight < 0)
{
d = var->weight + 1;
*cp++ = '0';
}
else
{
for (d = 0; d <= var->weight; d++)
{
dig = (d < var->ndigits) ? var->digits[d] : 0;
/* In the first digit, suppress extra leading decimal zeroes */
{
int putit = (d > 0);
d1 = dig / 1000;
dig -= d1 * 1000;
putit |= (d1 > 0);
if (putit)
*cp++ = (char) (d1 + '0');
d1 = dig / 100;
dig -= d1 * 100;
putit |= (d1 > 0);
if (putit)
*cp++ = (char) (d1 + '0');
d1 = dig / 10;
dig -= d1 * 10;
putit |= (d1 > 0);
if (putit)
*cp++ = (char) (d1 + '0');
*cp++ = (char) (dig + '0');
}
}
}
/*
* If requested, output a decimal point and all the digits that follow it.
* We initially put out a multiple of DEC_DIGITS digits, then truncate if
* needed.
*/
if (dscale > 0)
{
*cp++ = '.';
endcp = cp + dscale;
for (i = 0; i < dscale; d++, i += DEC_DIGITS)
{
dig = (d >= 0 && d < var->ndigits) ? var->digits[d] : 0;
d1 = dig / 1000;
dig -= d1 * 1000;
*cp++ = (char) (d1 + '0');
d1 = dig / 100;
dig -= d1 * 100;
*cp++ = (char) (d1 + '0');
d1 = dig / 10;
dig -= d1 * 10;
*cp++ = (char) (d1 + '0');
*cp++ = (char) (dig + '0');
}
cp = endcp;
}
/*
* terminate the string and return it
*/
*cp = '\0';
return 0;
}