]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/cash.c
Used modified version of indent that understands over 100 typedefs.
[postgresql] / src / backend / utils / adt / cash.c
1 /*
2  * cash.c
3  * Written by D'Arcy J.M. Cain
4  *
5  * Functions to allow input and output of money normally but store
6  * and handle it as int4s
7  *
8  * A slightly modified version of this file and a discussion of the
9  * workings can be found in the book "Software Solutions in C" by
10  * Dale Schumacher, Academic Press, ISBN: 0-12-632360-7.
11  *
12  * $Header: /cvsroot/pgsql/src/backend/utils/adt/cash.c,v 1.12 1997/09/08 21:48:13 momjian Exp $
13  */
14
15 #include <stdio.h>
16 #include <string.h>
17 #include <limits.h>
18 #include <ctype.h>
19 #include <locale.h>
20
21 #include "postgres.h"
22 #include "miscadmin.h"
23 #include "utils/builtins.h"
24 #include "utils/cash.h"
25
26 static const char *num_word(Cash value);
27
28 /* when we go to 64 bit values we will have to modify this */
29 #define CASH_BUFSZ              24
30
31 #define TERMINATOR              (CASH_BUFSZ - 1)
32 #define LAST_PAREN              (TERMINATOR - 1)
33 #define LAST_DIGIT              (LAST_PAREN - 1)
34
35 #ifdef USE_LOCALE
36 static struct lconv *lconv = NULL;
37
38 #endif
39
40 /* cash_in()
41  * Convert a string to a cash data type.
42  * Format is [$]###[,]###[.##]
43  * Examples: 123.45 $123.45 $123,456.78
44  *
45  * This is currently implemented as a 32-bit integer.
46  * XXX HACK It looks as though some of the symbols for
47  *      monetary values returned by localeconv() can be multiple
48  *      bytes/characters. This code assumes one byte only. - tgl 97/04/14
49  */
50 Cash       *
51 cash_in(const char *str)
52 {
53         Cash       *result;
54
55         Cash            value = 0;
56         Cash            dec = 0;
57         Cash            sgn = 1;
58         int                     seen_dot = 0;
59         const char *s = str;
60         int                     fpoint;
61         char            dsymbol,
62                                 ssymbol,
63                                 psymbol,
64                                 nsymbol,
65                                 csymbol;
66
67 #ifdef USE_LOCALE
68         if (lconv == NULL)
69                 lconv = localeconv();
70
71         /* frac_digits in the C locale seems to return CHAR_MAX */
72         /* best guess is 2 in this case I think */
73         fpoint = ((lconv->frac_digits != CHAR_MAX) ? lconv->frac_digits : 2);           /* int_frac_digits? */
74
75         dsymbol = *lconv->mon_decimal_point;
76         ssymbol = *lconv->mon_thousands_sep;
77         csymbol = *lconv->currency_symbol;
78         psymbol = *lconv->positive_sign;
79         nsymbol = *lconv->negative_sign;
80 #else
81         fpoint = 2;
82         dsymbol = '.';
83         ssymbol = ',';
84         csymbol = '$';
85         psymbol = '+';
86         nsymbol = '-';
87 #endif
88
89         /* we need to add all sorts of checking here.  For now just */
90         /* strip all leading whitespace and any leading dollar sign */
91         while (isspace(*s) || *s == csymbol)
92                 s++;
93
94         /* a leading minus or paren signifies a negative number */
95         /* again, better heuristics needed */
96         if (*s == nsymbol || *s == '(')
97         {
98                 sgn = -1;
99                 s++;
100
101         }
102         else if (*s == psymbol)
103         {
104                 s++;
105         }
106
107         while (isspace(*s) || *s == csymbol)
108                 s++;
109
110         for (;; s++)
111         {
112                 /* we look for digits as int4 as we have less */
113                 /* than the required number of decimal places */
114                 if (isdigit(*s) && dec < fpoint)
115                 {
116                         value = (value * 10) + *s - '0';
117
118                         if (seen_dot)
119                                 dec++;
120
121                         /* decimal point? then start counting fractions... */
122                 }
123                 else if (*s == dsymbol && !seen_dot)
124                 {
125                         seen_dot = 1;
126
127                         /* "thousands" separator? then skip... */
128                 }
129                 else if (*s == ssymbol)
130                 {
131
132                 }
133                 else
134                 {
135                         /* round off */
136                         if (isdigit(*s) && *s >= '5')
137                                 value++;
138
139                         /* adjust for less than required decimal places */
140                         for (; dec < fpoint; dec++)
141                                 value *= 10;
142
143                         break;
144                 }
145         }
146
147         while (isspace(*s) || *s == '0' || *s == ')')
148                 s++;
149
150         if (*s != '\0')
151                 elog(WARN, "Bad money external representation %s", str);
152
153         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
154                 elog(WARN, "Memory allocation failed, can't input cash '%s'", str);
155
156         *result = (value * sgn);
157
158         return (result);
159 }                                                               /* cash_in() */
160
161
162 /* cash_out()
163  * Function to convert cash to a dollars and cents representation.
164  * XXX HACK This code appears to assume US conventions for
165  *      positive-valued amounts. - tgl 97/04/14
166  */
167 const char *
168 cash_out(Cash *value)
169 {
170         char       *result;
171         char            buf[CASH_BUFSZ];
172         int                     minus = 0;
173         int                     count = LAST_DIGIT;
174         int                     point_pos;
175         int                     comma_position = 0;
176         char            mon_group,
177                                 comma,
178                                 points;
179         char            csymbol,
180                                 dsymbol,
181                            *nsymbol;
182         char            convention;
183
184 #ifdef USE_LOCALE
185         if (lconv == NULL)
186                 lconv = localeconv();
187
188         mon_group = *lconv->mon_grouping;
189         comma = *lconv->mon_thousands_sep;
190         csymbol = *lconv->currency_symbol;
191         dsymbol = *lconv->mon_decimal_point;
192         nsymbol = lconv->negative_sign;
193         /* frac_digits in the C locale seems to return CHAR_MAX */
194         /* best guess is 2 in this case I think */
195         points = ((lconv->frac_digits != CHAR_MAX) ? lconv->frac_digits : 2);           /* int_frac_digits? */
196         convention = lconv->n_sign_posn;
197 #else
198         mon_group = 3;
199         comma = ',';
200         csymbol = '$';
201         dsymbol = '.';
202         nsymbol = "-";
203         points = 2;
204         convention = 0;
205 #endif
206
207         point_pos = LAST_DIGIT - points;
208
209         /* We're playing a little fast and loose with this.  Shoot me. */
210         if (!mon_group || mon_group == CHAR_MAX)
211                 mon_group = 3;
212
213         /* allow more than three decimal points and separate them */
214         if (comma)
215         {
216                 point_pos -= (points - 1) / mon_group;
217                 comma_position = point_pos % (mon_group + 1);
218         }
219
220         /* we work with positive amounts and add the minus sign at the end */
221         if (*value < 0)
222         {
223                 minus = 1;
224                 *value *= -1;
225         }
226
227         /* allow for trailing negative strings */
228         memset(buf, ' ', CASH_BUFSZ);
229         buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
230
231         while (*value || count > (point_pos - 2))
232         {
233                 if (points && count == point_pos)
234                         buf[count--] = dsymbol;
235                 else if (comma && count % (mon_group + 1) == comma_position)
236                         buf[count--] = comma;
237
238                 buf[count--] = (*value % 10) + '0';
239                 *value /= 10;
240         }
241
242         buf[count] = csymbol;
243
244         if (buf[LAST_DIGIT] == ',')
245                 buf[LAST_DIGIT] = buf[LAST_PAREN];
246
247         /* see if we need to signify negative amount */
248         if (minus)
249         {
250                 if (!PointerIsValid(result = PALLOC(CASH_BUFSZ + 2 - count + strlen(nsymbol))))
251                         elog(WARN, "Memory allocation failed, can't output cash", NULL);
252
253                 /* Position code of 0 means use parens */
254                 if (convention == 0)
255                         sprintf(result, "(%s)", buf + count);
256                 else if (convention == 2)
257                         sprintf(result, "%s%s", buf + count, nsymbol);
258                 else
259                         sprintf(result, "%s%s", nsymbol, buf + count);
260         }
261         else
262         {
263                 if (!PointerIsValid(result = PALLOC(CASH_BUFSZ + 2 - count)))
264                         elog(WARN, "Memory allocation failed, can't output cash", NULL);
265
266                 strcpy(result, buf + count);
267         }
268
269         return (result);
270 }                                                               /* cash_out() */
271
272
273 bool
274 cash_eq(Cash *c1, Cash *c2)
275 {
276         if (!PointerIsValid(c1) || !PointerIsValid(c2))
277                 return (FALSE);
278
279         return (*c1 == *c2);
280 }                                                               /* cash_eq() */
281
282 bool
283 cash_ne(Cash *c1, Cash *c2)
284 {
285         if (!PointerIsValid(c1) || !PointerIsValid(c2))
286                 return (FALSE);
287
288         return (*c1 != *c2);
289 }                                                               /* cash_ne() */
290
291 bool
292 cash_lt(Cash *c1, Cash *c2)
293 {
294         if (!PointerIsValid(c1) || !PointerIsValid(c2))
295                 return (FALSE);
296
297         return (*c1 < *c2);
298 }                                                               /* cash_lt() */
299
300 bool
301 cash_le(Cash *c1, Cash *c2)
302 {
303         if (!PointerIsValid(c1) || !PointerIsValid(c2))
304                 return (FALSE);
305
306         return (*c1 <= *c2);
307 }                                                               /* cash_le() */
308
309 bool
310 cash_gt(Cash *c1, Cash *c2)
311 {
312         if (!PointerIsValid(c1) || !PointerIsValid(c2))
313                 return (FALSE);
314
315         return (*c1 > *c2);
316 }                                                               /* cash_gt() */
317
318 bool
319 cash_ge(Cash *c1, Cash *c2)
320 {
321         if (!PointerIsValid(c1) || !PointerIsValid(c2))
322                 return (FALSE);
323
324         return (*c1 >= *c2);
325 }                                                               /* cash_ge() */
326
327
328 /* cash_pl()
329  * Add two cash values.
330  */
331 Cash       *
332 cash_pl(Cash *c1, Cash *c2)
333 {
334         Cash       *result;
335
336         if (!PointerIsValid(c1) || !PointerIsValid(c2))
337                 return (NULL);
338
339         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
340                 elog(WARN, "Memory allocation failed, can't add cash", NULL);
341
342         *result = (*c1 + *c2);
343
344         return (result);
345 }                                                               /* cash_pl() */
346
347
348 /* cash_mi()
349  * Subtract two cash values.
350  */
351 Cash       *
352 cash_mi(Cash *c1, Cash *c2)
353 {
354         Cash       *result;
355
356         if (!PointerIsValid(c1) || !PointerIsValid(c2))
357                 return (NULL);
358
359         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
360                 elog(WARN, "Memory allocation failed, can't subtract cash", NULL);
361
362         *result = (*c1 - *c2);
363
364         return (result);
365 }                                                               /* cash_mi() */
366
367
368 /* cash_mul()
369  * Multiply cash by floating point number.
370  */
371 Cash       *
372 cash_mul(Cash *c, float8 *f)
373 {
374         Cash       *result;
375
376         if (!PointerIsValid(f) || !PointerIsValid(c))
377                 return (NULL);
378
379         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
380                 elog(WARN, "Memory allocation failed, can't multiply cash", NULL);
381
382         *result = ((*f) * (*c));
383
384         return (result);
385 }                                                               /* cash_mul() */
386
387
388 /* cash_div()
389  * Divide cash by floating point number.
390  *
391  * XXX Don't know if rounding or truncating is correct behavior.
392  * Round for now. - tgl 97/04/15
393  */
394 Cash       *
395 cash_div(Cash *c, float8 *f)
396 {
397         Cash       *result;
398
399         if (!PointerIsValid(f) || !PointerIsValid(c))
400                 return (NULL);
401
402         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
403                 elog(WARN, "Memory allocation failed, can't divide cash", NULL);
404
405         if (*f == 0.0)
406                 elog(WARN, "cash_div:  divide by 0.0 error");
407
408         *result = rint(*c / *f);
409
410         return (result);
411 }                                                               /* cash_div() */
412
413
414 /* cashlarger()
415  * Return larger of two cash values.
416  */
417 Cash       *
418 cashlarger(Cash *c1, Cash *c2)
419 {
420         Cash       *result;
421
422         if (!PointerIsValid(c1) || !PointerIsValid(c2))
423                 return (NULL);
424
425         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
426                 elog(WARN, "Memory allocation failed, can't return larger cash", NULL);
427
428         *result = ((*c1 > *c2) ? *c1 : *c2);
429
430         return (result);
431 }                                                               /* cashlarger() */
432
433
434 /* cashsmaller()
435  * Return smaller of two cash values.
436  */
437 Cash       *
438 cashsmaller(Cash *c1, Cash *c2)
439 {
440         Cash       *result;
441
442         if (!PointerIsValid(c1) || !PointerIsValid(c2))
443                 return (NULL);
444
445         if (!PointerIsValid(result = PALLOCTYPE(Cash)))
446                 elog(WARN, "Memory allocation failed, can't return smaller cash", NULL);
447
448         *result = ((*c1 < *c2) ? *c1 : *c2);
449
450         return (result);
451 }                                                               /* cashsmaller() */
452
453
454 /* cash_words_out()
455  * This converts a int4 as well but to a representation using words
456  * Obviously way North American centric - sorry
457  */
458 const char *
459 cash_words_out(Cash *value)
460 {
461         static char buf[128];
462         char       *p = buf;
463         Cash            m0;
464         Cash            m1;
465         Cash            m2;
466         Cash            m3;
467
468         /* work with positive numbers */
469         if (*value < 0)
470         {
471                 *value *= -1;
472                 strcpy(buf, "minus ");
473                 p += 6;
474         }
475         else
476         {
477                 *buf = 0;
478         }
479
480         m0 = *value % 100;                      /* cents */
481         m1 = (*value / 100) % 1000; /* hundreds */
482         m2 = (*value / 100000) % 1000;          /* thousands */
483         m3 = *value / 100000000 % 1000;         /* millions */
484
485         if (m3)
486         {
487                 strcat(buf, num_word(m3));
488                 strcat(buf, " million ");
489         }
490
491         if (m2)
492         {
493                 strcat(buf, num_word(m2));
494                 strcat(buf, " thousand ");
495         }
496
497         if (m1)
498                 strcat(buf, num_word(m1));
499
500         if (!*p)
501                 strcat(buf, "zero");
502
503         strcat(buf, (int) (*value / 100) == 1 ? " dollar and " : " dollars and ");
504         strcat(buf, num_word(m0));
505         strcat(buf, m0 == 1 ? " cent" : " cents");
506         *buf = toupper(*buf);
507         return (buf);
508 }                                                               /* cash_words_out() */
509
510
511 /*************************************************************************
512  * Private routines
513  ************************************************************************/
514
515 static const char *
516 num_word(Cash value)
517 {
518         static char buf[128];
519         static const char *small[] = {
520                 "zero", "one", "two", "three", "four", "five", "six", "seven",
521                 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
522                 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
523                 "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety"
524         };
525         const char **big = small + 18;
526         int                     tu = value % 100;
527
528         /* deal with the simple cases first */
529         if (value <= 20)
530                 return (small[value]);
531
532         /* is it an even multiple of 100? */
533         if (!tu)
534         {
535                 sprintf(buf, "%s hundred", small[value / 100]);
536                 return (buf);
537         }
538
539         /* more than 99? */
540         if (value > 99)
541         {
542                 /* is it an even multiple of 10 other than 10? */
543                 if (value % 10 == 0 && tu > 10)
544                         sprintf(buf, "%s hundred %s",
545                                         small[value / 100], big[tu / 10]);
546                 else if (tu < 20)
547                         sprintf(buf, "%s hundred and %s",
548                                         small[value / 100], small[tu]);
549                 else
550                         sprintf(buf, "%s hundred %s %s",
551                                         small[value / 100], big[tu / 10], small[tu % 10]);
552
553         }
554         else
555         {
556                 /* is it an even multiple of 10 other than 10? */
557                 if (value % 10 == 0 && tu > 10)
558                         sprintf(buf, "%s", big[tu / 10]);
559                 else if (tu < 20)
560                         sprintf(buf, "%s", small[tu]);
561                 else
562                         sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);
563         }
564
565         return (buf);
566 }                                                               /* num_word() */