libpqtypes - numerics.c

Home Page

array.c
datetime.c
error.c
events.c
exec.c
geo.c
getaddrinfo.h
handler.c
libpqtypes-int.h
libpqtypes.h
misc.c
network.c
numerics.c
param.c
port.c
record.c
regression-test.c
spec.c
utils.c
varlena.c
  1 
  2 /*
  3  * numeric.c
  4  *   Type handler for the numeric data types.
  5  *
  6  * Copyright (c) 2011 eSilo, LLC. All rights reserved.
  7  * This is free software; see the source for copying conditions.  There is
  8  * NO warranty; not even for MERCHANTABILITY or  FITNESS FOR A  PARTICULAR
  9  * PURPOSE.
 10  */
 11 
 12 #include "libpqtypes-int.h"
 13 #include <errno.h>
 14 
 15 /* Extremely annoying warning "conversion from 'int ' to 'short'" on
 16  * Visual Studio 6.  Normally this warning is useful but not in this
 17  * case.  Disable it.
 18  */
 19 #if defined(PQT_MSVC) && PQT_MSVC <= 1200
 20 # pragma warning (disable : 4244)
 21 #endif
 22 
 23 /*
 24  * Macros and structures for receiving numeric field in binary
 25  */
 26 #define NBASE   10000
 27 #define HALF_NBASE  5000
 28 #define DEC_DIGITS  4     /* decimal digits per NBASE digit */
 29 #define MUL_GUARD_DIGITS  2 /* these are measured in NBASE digits */
 30 #define DIV_GUARD_DIGITS  4
 31 
 32 /*
 33  * Hardcoded precision limit - arbitrary, but must be small enough that
 34  * dscale values will fit in 14 bits.
 35  */
 36 #define NUMERIC_MAX_PRECISION   1000
 37 
 38 /*
 39  * Sign values and macros to deal with packing/unpacking n_sign_dscale
 40  */
 41 #define NUMERIC_SIGN_MASK 0xC000
 42 #define NUMERIC_POS     0x0000
 43 #define NUMERIC_NEG     0x4000
 44 #define NUMERIC_NAN     0xC000
 45 #define NUMERIC_DSCALE_MASK 0x3FFF
 46 #define NUMERIC_SIGN(n)   ((n)->n_sign_dscale & NUMERIC_SIGN_MASK)
 47 #define NUMERIC_DSCALE(n) ((n)->n_sign_dscale & NUMERIC_DSCALE_MASK)
 48 #define NUMERIC_IS_NAN(n) (NUMERIC_SIGN(n) != NUMERIC_POS &&  \
 49                NUMERIC_SIGN(n) != NUMERIC_NEG)
 50 
 51 typedef short NumericDigit;
 52 static const int round_powers[4] = {0, 1000, 100, 10};
 53 
 54 typedef struct NumericVar
 55 {
 56   int ndigits;            /* # of digits in digits[] - can be 0! */
 57   int weight;             /* weight of first digit */
 58   int sign;               /* NUMERIC_POS, NUMERIC_NEG, or NUMERIC_NAN */
 59   int dscale;             /* display scale */
 60   NumericDigit *buf;      /* start of palloc'd space for digits[] */
 61   NumericDigit *digits;   /* base-NBASE digits */
 62 } NumericVar;
 63 
 64 static int str2num(PGtypeArgs *args, const char *str, NumericVar *dest);
 65 static int num2str(char *out, size_t outl, NumericVar *var, int dscale);
 66 
 67 int
 68 pqt_put_int2(PGtypeArgs *args)
 69 {
 70   PGint2 n = (PGint2) va_arg(args->ap, int);
 71   pqt_buf_putint2(args->put.out, n);
 72   return 2;
 73 }
 74 
 75 int
 76 pqt_get_int2(PGtypeArgs *args)
 77 {
 78   DECLVALUE(args);
 79   PGint2 *i2p = va_arg(args->ap, PGint2 *);
 80 
 81   CHKGETVALS(args, i2p);
 82 
 83   if (args->format == TEXTFMT)
 84   {
 85     int n;
 86 
 87     errno = 0;
 88     if ((n = (int) strtol(value, NULL, 10)) == 0 && errno)
 89       RERR_STR2INT(args);
 90 
 91     *i2p = (PGint2)n;
 92     return 0;
 93   }
 94 
 95   *i2p = pqt_buf_getint2(value);
 96   return 0;
 97 }
 98 
 99 /* handles oid as well */
100 int
101 pqt_put_int4(PGtypeArgs *args)
102 {
103   PGint4 n = va_arg(args->ap, PGint4);
104   *(PGint4 *)args->put.out = (PGint4) htonl(n);
105   return 4;
106 }
107 
108 /* handles oid as well */
109 int
110 pqt_get_int4(PGtypeArgs *args)
111 {
112   DECLVALUE(args);
113   PGint4 *i4p = va_arg(args->ap, PGint4 *);
114 
115   CHKGETVALS(args, i4p);
116 
117   if (args->format == TEXTFMT)
118   {
119     PGint4 n;
120 
121     /* Use strtoul so this can support Oid */
122     if ((n = (PGint4) strtoul(value, NULL, 10)) == 0 && errno)
123       RERR_STR2INT(args);
124 
125     *i4p = n;
126     return 0;
127   }
128 
129   *i4p = (PGint4) pqt_buf_getint4(value);
130   return 0;
131 }
132 
133 int
134 pqt_put_int8(PGtypeArgs *args)
135 {
136   PGint8 i8 = va_arg(args->ap, PGint8);
137   pqt_swap8(args->put.out, &i8, 1);
138   return 8;
139 }
140 
141 int
142 pqt_get_int8(PGtypeArgs *args)
143 {
144   DECLVALUE(args);
145   PGint8 *i8p = va_arg(args->ap, PGint8 *);
146 
147   CHKGETVALS(args, i8p);
148 
149   if (args->format == TEXTFMT)
150   {
151     if (pqt_text_to_int8(value, i8p) == -1)
152       RERR_STR2INT(args);
153     return 0;
154   }
155 
156   pqt_swap8(i8p, value, 0);
157   return 0;
158 }
159 
160 int
161 pqt_put_float4(PGtypeArgs *args)
162 {
163   PGfloat4 f = (PGfloat4) va_arg(args->ap, double);
164   void *vp = (void *)&f;
165   pqt_buf_putint4(args->put.out, *(int *) vp);
166   return 4;
167 }
168 
169 int
170 pqt_get_float4(PGtypeArgs *args)
171 {
172   DECLVALUE(args);
173   int *f4p = (int *) va_arg(args->ap, PGfloat4 *);
174 
175   CHKGETVALS(args, f4p);
176 
177   if (args->format == TEXTFMT)
178   {
179     PGfloat4 f;
180 
181     errno = 0;
182     if ((f = (PGfloat4) strtod(value, NULL)) == 0 && errno)
183       RERR_STR2INT(args);
184 
185     *(PGfloat4 *) f4p = f;
186     return 0;
187   }
188 
189   *f4p = pqt_buf_getint4(value);
190   return 0;
191 }
192 
193 int
194 pqt_put_float8(PGtypeArgs *args)
195 {
196   PGfloat8 d = va_arg(args->ap, PGfloat8);
197   pqt_swap8(args->put.out, &d, 1);
198   return 8;
199 }
200 
201 int
202 pqt_get_float8(PGtypeArgs *args)
203 {
204   DECLVALUE(args);
205   PGfloat8 *f8p = va_arg(args->ap, PGfloat8 *);
206 
207   CHKGETVALS(args, f8p);
208 
209   if (args->format == TEXTFMT)
210   {
211     if (!pqt_text_to_float8(f8p, value, NULL))
212       RERR_STR2INT(args);
213 
214     return 0;
215   }
216 
217   pqt_swap8(f8p, value, 0);
218   return 0;
219 }
220 
221 /* exposing a NumericVar struct to a libpq user, or something similar,
222  * doesn't seem useful w/o a library to operate on it.  Instead, we
223  * always expose a numeric in text format and let the API user decide
224  * how to use it .. like strod or a 3rd party big number library.  We
225  * always send a numeric in binary though.
226  */
227 int
228 pqt_put_numeric(PGtypeArgs *args)
229 {
230   int numlen;
231   NumericVar num = {0};
232   short *out;
233   PGnumeric str = va_arg(args->ap, PGnumeric);
234 
235   if (str2num(args, str, &num))
236   {
237     if (num.digits)
238       free(num.digits);
239     return -1;
240   }
241 
242   /* variable length data type, grow args->put.out buffer if needed */
243   numlen = (int) sizeof(short) * (4 + num.ndigits);
244   if (args->put.expandBuffer(args, numlen) == -1)
245     return -1;
246 
247   out = (short *) args->put.out;
248   *out++ = htons((short) num.ndigits);
249   *out++ = htons((short) num.weight);
250   *out++ = htons((short) num.sign);
251   *out++ = htons((short) num.dscale);
252 
253   if (num.digits)
254   {
255     int i;
256     for (i=0; i < num.ndigits; i++)
257       *out++ = htons(num.digits[i]);
258     free(num.digits);
259   }
260 
261   return numlen;
262 }
263 
264 /* exposing a NumericVar struct to a libpq user, or something similar,
265  * doesn't seem useful w/o a library to operate on it.  Instead, we
266  * always expose a numeric in text format and let the API user decide
267  * how to use it .. like strod or a 3rd party big number library.
268  */
269 int
270 pqt_get_numeric(PGtypeArgs *args)
271 {
272   int i;
273   short *s;
274   NumericVar num;
275   DECLVALUE(args);
276   char buf[4096];
277   size_t len;
278   PGnumeric *str = va_arg(args->ap, PGnumeric *);
279 
280   CHKGETVALS(args, str);
281 
282   if (args->format == TEXTFMT)
283   {
284     *str = value;
285     return 0;
286   }
287 
288   s = (short *) value;
289   num.ndigits = ntohs(*s); s++;
290   num.weight  = ntohs(*s); s++;
291   num.sign    = ntohs(*s); s++;
292   num.dscale  = ntohs(*s); s++;
293   num.digits  = (short *) malloc(num.ndigits * sizeof(short));
294   if (!num.digits)
295     RERR_MEM(args);
296 
297   for (i=0; i < num.ndigits; i++)
298   {
299     num.digits[i] = ntohs(*s);
300     s++;
301   }
302 
303   i = num2str(buf, sizeof(buf), &num, num.dscale);
304   free(num.digits);
305 
306   /* num2str failed, only fails when 'str' is too small */
307   if (i == -1)
308     RERR(args, "out buffer is too small");
309 
310   len = strlen(buf)+1;
311   *str = PQresultAlloc(args->get.result, len);
312   if (!*str)
313     RERR_MEM(args);
314 
315   memcpy(*str, buf, len);
316   return 0;
317 
318 }
319 
320 
321 /*
322  * round_var
323  *
324  * Round the value of a variable to no more than rscale decimal digits
325  * after the decimal point.  NOTE: we allow rscale < 0 here, implying
326  * rounding before the decimal point.
327  */
328 static void
329 round_var(NumericVar *var, int rscale)
330 {
331   NumericDigit *digits = var->digits;
332   int     di;
333   int     ndigits;
334   int     carry;
335 
336   var->dscale = rscale;
337 
338   /* decimal digits wanted */
339   di = (var->weight + 1) * DEC_DIGITS + rscale;
340 
341   /*
342    * If di = 0, the value loses all digits, but could round up to 1 if its
343    * first extra digit is >= 5.  If di < 0 the result must be 0.
344    */
345   if (di < 0)
346   {
347     var->ndigits = 0;
348     var->weight = 0;
349     var->sign = NUMERIC_POS;
350   }
351   else
352   {
353     /* NBASE digits wanted */
354     ndigits = (di + DEC_DIGITS - 1) / DEC_DIGITS;
355 
356     /* 0, or number of decimal digits to keep in last NBASE digit */
357     di %= DEC_DIGITS;
358 
359     if (ndigits < var->ndigits ||
360       (ndigits == var->ndigits && di > 0))
361     {
362       var->ndigits = ndigits;
363 
364       if (di == 0)
365         carry = (digits[ndigits] >= HALF_NBASE) ? 1 : 0;
366       else
367       {
368         /* Must round within last NBASE digit */
369         int     extra,
370               pow10;
371 
372         pow10 = round_powers[di];
373         extra = digits[--ndigits] % pow10;
374         digits[ndigits] = digits[ndigits] - (NumericDigit) extra;
375         carry = 0;
376         if (extra >= pow10 / 2)
377         {
378           pow10 += digits[ndigits];
379           if (pow10 >= NBASE)
380           {
381             pow10 -= NBASE;
382             carry = 1;
383           }
384           digits[ndigits] = (NumericDigit) pow10;
385         }
386       }
387 
388       /* Propagate carry if needed */
389       while (carry)
390       {
391         carry += digits[--ndigits];
392         if (carry >= NBASE)
393         {
394           digits[ndigits] = (NumericDigit) (carry - NBASE);
395           carry = 1;
396         }
397         else
398         {
399           digits[ndigits] = (NumericDigit) carry;
400           carry = 0;
401         }
402       }
403 
404       if (ndigits < 0)
405       {
406         var->digits--;
407         var->ndigits++;
408         var->weight++;
409       }
410     }
411   }
412 }
413 
414 /*
415  * strip_var
416  *
417  * Strip any leading and trailing zeroes from a numeric variable
418  */
419 static void
420 strip_var(NumericVar *var)
421 {
422   NumericDigit *digits = var->digits;
423   int     ndigits = var->ndigits;
424 
425   /* Strip leading zeroes */
426   while (ndigits > 0 && *digits == 0)
427   {
428     digits++;
429     var->weight--;
430     ndigits--;
431   }
432 
433   /* Strip trailing zeroes */
434   while (ndigits > 0 && digits[ndigits - 1] == 0)
435     ndigits--;
436 
437   /* If it's zero, normalize the sign and weight */
438   if (ndigits == 0)
439   {
440     var->sign = NUMERIC_POS;
441     var->weight = 0;
442   }
443 
444   var->digits = digits;
445   var->ndigits = ndigits;
446 }
447 
448 /*
449  * str2num()
450  *
451  *  Parse a string and put the number into a variable
452  *  returns -1 on error and 0 for success.
453  */
454 static int
455 str2num(PGtypeArgs *args, const char *str, NumericVar *dest)
456 {
457   const char *cp = str;
458   int   have_dp = FALSE;
459   int     i;
460   unsigned char *decdigits;
461   int     sign = NUMERIC_POS;
462   int     dweight = -1;
463   int     ddigits;
464   int     dscale = 0;
465   int     weight;
466   int     ndigits;
467   int     offset;
468   NumericDigit *digits;
469 
470   /*
471    * We first parse the string to extract decimal digits and determine the
472    * correct decimal weight.  Then convert to NBASE representation.
473    */
474 
475   /* skip leading spaces */
476   while (*cp)
477   {
478     if (!isspace((unsigned char) *cp))
479       break;
480     cp++;
481   }
482 
483   switch (*cp)
484   {
485     case '+':
486       sign = NUMERIC_POS;
487       cp++;
488       break;
489 
490     case '-':
491       sign = NUMERIC_NEG;
492       cp++;
493       break;
494   }
495 
496   if (*cp == '.')
497   {
498     have_dp = TRUE;
499     cp++;
500   }
501 
502   if (!isdigit((unsigned char) *cp))
503     return args->errorf(args,
504       "invalid input syntax for type numeric: '%s'", str);
505 
506   decdigits = (unsigned char *) malloc(strlen(cp) + DEC_DIGITS * 2);
507 
508   /* leading padding for digit alignment later */
509   memset(decdigits, 0, DEC_DIGITS);
510   i = DEC_DIGITS;
511 
512   while (*cp)
513   {
514     if (isdigit((unsigned char) *cp))
515     {
516       decdigits[i++] = *cp++ - '0';
517       if (!have_dp)
518         dweight++;
519       else
520         dscale++;
521     }
522     else if (*cp == '.')
523     {
524       if (have_dp)
525       {
526         free(decdigits);
527         return args->errorf(args,
528           "invalid input syntax for type numeric: '%s'", str);
529       }
530 
531       have_dp = TRUE;
532       cp++;
533     }
534     else
535       break;
536   }
537 
538   ddigits = i - DEC_DIGITS;
539   /* trailing padding for digit alignment later */
540   memset(decdigits + i, 0, DEC_DIGITS - 1);
541 
542   /* Handle exponent, if any */
543   if (*cp == 'e' || *cp == 'E')
544   {
545     long    exponent;
546     char     *endptr;
547 
548     cp++;
549     exponent = strtol(cp, &endptr, 10);
550     if (endptr == cp)
551     {
552       free(decdigits);
553       return args->errorf(args,
554         "invalid input syntax for type numeric: '%s'", str);
555     }
556 
557     cp = endptr;
558     if (exponent > NUMERIC_MAX_PRECISION ||
559       exponent < -NUMERIC_MAX_PRECISION)
560     {
561       free(decdigits);
562       return args->errorf(args,
563         "invalid input syntax for type numeric: '%s'", str);
564     }
565 
566     dweight += (int) exponent;
567     dscale -= (int) exponent;
568     if (dscale < 0)
569       dscale = 0;
570   }
571 
572   /* Should be nothing left but spaces */
573   while (*cp)
574   {
575     if (!isspace((unsigned char) *cp))
576     {
577       free(decdigits);
578       return args->errorf(args,
579         "invalid input syntax for type numeric: '%s'", str);
580     }
581     cp++;
582   }
583 
584   /*
585    * Okay, convert pure-decimal representation to base NBASE.  First we need
586    * to determine the converted weight and ndigits.  offset is the number of
587    * decimal zeroes to insert before the first given digit to have a
588    * correctly aligned first NBASE digit.
589    */
590   if (dweight >= 0)
591     weight = (dweight + 1 + DEC_DIGITS - 1) / DEC_DIGITS - 1;
592   else
593     weight = -((-dweight - 1) / DEC_DIGITS + 1);
594   offset = (weight + 1) * DEC_DIGITS - (dweight + 1);
595   ndigits = (ddigits + offset + DEC_DIGITS - 1) / DEC_DIGITS;
596 
597   dest->digits = (NumericDigit *) malloc((ndigits) * sizeof(NumericDigit));
598   dest->ndigits = ndigits;
599   dest->sign = sign;
600   dest->weight = weight;
601   dest->dscale = dscale;
602 
603   i = DEC_DIGITS - offset;
604   digits = dest->digits;
605 
606   while (ndigits-- > 0)
607   {
608     *digits++ = ((decdigits[i] * 10 + decdigits[i + 1]) * 10 +
609            decdigits[i + 2]) * 10 + decdigits[i + 3];
610     i += DEC_DIGITS;
611   }
612 
613   free(decdigits);
614 
615   /* Strip any leading/trailing zeroes, and normalize weight if zero */
616   strip_var(dest);
617   return 0;
618 }
619 
620 /*
621  * num2str() -
622  *
623  *  Convert a var to text representation (guts of numeric_out).
624  *  CAUTION: var's contents may be modified by rounding!
625  *  returns -1 on error and 0 for success.
626  */
627 static int
628 num2str(char *out, size_t outl, NumericVar *var, int dscale)
629 {
630   //char     *str;
631   char     *cp;
632   char     *endcp;
633   int     i;
634   int     d;
635   NumericDigit dig;
636   NumericDigit d1;
637 
638   if (dscale < 0)
639     dscale = 0;
640 
641   /*
642    * Check if we must round up before printing the value and do so.
643    */
644   round_var(var, dscale);
645 
646   /*
647    * Allocate space for the result.
648    *
649    * i is set to to # of decimal digits before decimal point. dscale is the
650    * # of decimal digits we will print after decimal point. We may generate
651    * as many as DEC_DIGITS-1 excess digits at the end, and in addition we
652    * need room for sign, decimal point, null terminator.
653    */
654   i = (var->weight + 1) * DEC_DIGITS;
655   if (i <= 0)
656     i = 1;
657 
658   if (outl <= (size_t) (i + dscale + DEC_DIGITS + 2))
659     return -1;
660 
661   //str = palloc(i + dscale + DEC_DIGITS + 2);
662   //cp = str
663   cp = out;
664 
665   /*
666    * Output a dash for negative values
667    */
668   if (var->sign == NUMERIC_NEG)
669     *cp++ = '-';
670 
671   /*
672    * Output all digits before the decimal point
673    */
674   if (var->weight < 0)
675   {
676     d = var->weight + 1;
677     *cp++ = '0';
678   }
679   else
680   {
681     for (d = 0; d <= var->weight; d++)
682     {
683       dig = (d < var->ndigits) ? var->digits[d] : 0;
684       /* In the first digit, suppress extra leading decimal zeroes */
685       {
686         int   putit = (d > 0);
687 
688         d1 = dig / 1000;
689         dig -= d1 * 1000;
690         putit |= (d1 > 0);
691         if (putit)
692           *cp++ = (char) (d1 + '0');
693         d1 = dig / 100;
694         dig -= d1 * 100;
695         putit |= (d1 > 0);
696         if (putit)
697           *cp++ = (char) (d1 + '0');
698         d1 = dig / 10;
699         dig -= d1 * 10;
700         putit |= (d1 > 0);
701         if (putit)
702           *cp++ = (char) (d1 + '0');
703         *cp++ = (char) (dig + '0');
704       }
705     }
706   }
707 
708   /*
709    * If requested, output a decimal point and all the digits that follow it.
710    * We initially put out a multiple of DEC_DIGITS digits, then truncate if
711    * needed.
712    */
713   if (dscale > 0)
714   {
715     *cp++ = '.';
716     endcp = cp + dscale;
717     for (i = 0; i < dscale; d++, i += DEC_DIGITS)
718     {
719       dig = (d >= 0 && d < var->ndigits) ? var->digits[d] : 0;
720       d1 = dig / 1000;
721       dig -= d1 * 1000;
722       *cp++ = (char) (d1 + '0');
723       d1 = dig / 100;
724       dig -= d1 * 100;
725       *cp++ = (char) (d1 + '0');
726       d1 = dig / 10;
727       dig -= d1 * 10;
728       *cp++ = (char) (d1 + '0');
729       *cp++ = (char) (dig + '0');
730     }
731     cp = endcp;
732   }
733 
734   /*
735    * terminate the string and return it
736    */
737   *cp = '\0';
738   return 0;
739 }