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