]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/cash.c
From: "Pedro J. Lobo" <pjlobo@euitt.upm.es>
[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 longs
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.6 1997/04/24 20:30:41 scrappy 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 long 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 == ')') 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     *result = rint(*c / *f);
375
376     return(result);
377 } /* cash_div() */
378
379
380 /* cashlarger()
381  * Return larger of two cash values.
382  */
383 Cash *
384 cashlarger(Cash *c1, Cash *c2)
385 {
386     Cash *result;
387
388     if (!PointerIsValid(c1) || !PointerIsValid(c2))
389         return(NULL);
390
391     if (!PointerIsValid(result = PALLOCTYPE(Cash)))
392         elog(WARN,"Memory allocation failed, can't return larger cash",NULL);
393
394     *result = ((*c1 > *c2)? *c1: *c2);
395
396     return(result);
397 } /* cashlarger() */
398
399
400 /* cashsmaller()
401  * Return smaller of two cash values.
402  */
403 Cash *
404 cashsmaller(Cash *c1, Cash *c2)
405 {
406     Cash *result;
407
408     if (!PointerIsValid(c1) || !PointerIsValid(c2))
409         return(NULL);
410
411     if (!PointerIsValid(result = PALLOCTYPE(Cash)))
412         elog(WARN,"Memory allocation failed, can't return smaller cash",NULL);
413
414     *result = ((*c1 < *c2)? *c1: *c2);
415
416     return(result);
417 } /* cashsmaller() */
418
419
420 /* cash_words_out()
421  * This converts a long as well but to a representation using words
422  * Obviously way North American centric - sorry
423  */
424 const char *
425 cash_words_out(Cash *value)
426 {
427     static char buf[128];
428     char *p = buf;
429     Cash m0;
430     Cash m1;
431     Cash m2;
432     Cash m3;
433
434     /* work with positive numbers */
435     if (*value < 0) {
436         *value *= -1;
437         strcpy(buf, "minus ");
438         p += 6;
439     } else {
440         *buf = 0;
441     }
442
443     m0 = *value % 100; /* cents */
444     m1 = (*value/100) % 1000; /* hundreds */
445     m2 = (*value/100000) % 1000; /* thousands */
446     m3 = *value/100000000 % 1000; /* millions */
447
448     if (m3) {
449         strcat(buf, num_word(m3));
450         strcat(buf, " million ");
451     }
452
453     if (m2) {
454         strcat(buf, num_word(m2));
455         strcat(buf, " thousand ");
456     }
457
458     if (m1)
459         strcat(buf, num_word(m1));
460
461     if (!*p)
462         strcat(buf, "zero");
463
464     strcat(buf, (int)(*value/100) == 1 ? " dollar and " : " dollars and ");
465     strcat(buf, num_word(m0));
466     strcat(buf, m0 == 1 ? " cent" : " cents");
467     *buf = toupper(*buf);
468     return(buf);
469 } /* cash_words_out() */
470
471
472 /*************************************************************************
473  * Private routines
474  ************************************************************************/
475
476 static const char *
477 num_word(Cash value)
478 {
479     static char buf[128];
480     static const char *small[] = {
481         "zero", "one", "two", "three", "four", "five", "six", "seven",
482         "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
483         "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
484         "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety"
485     };
486     const char **big = small + 18;
487     int tu = value % 100;
488
489     /* deal with the simple cases first */
490     if (value <= 20)
491         return(small[value]);
492
493     /* is it an even multiple of 100? */
494     if (!tu) {
495         sprintf(buf, "%s hundred", small[value/100]);
496         return(buf);
497     }
498
499     /* more than 99? */
500     if (value > 99) {
501         /* is it an even multiple of 10 other than 10? */
502         if (value % 10 == 0 && tu > 10)
503             sprintf(buf, "%s hundred %s",
504               small[value/100], big[tu/10]);
505         else if (tu < 20)
506             sprintf(buf, "%s hundred and %s",
507               small[value/100], small[tu]);
508         else
509             sprintf(buf, "%s hundred %s %s",
510               small[value/100], big[tu/10], small[tu % 10]);
511
512     } else {
513         /* is it an even multiple of 10 other than 10? */
514         if (value % 10 == 0 && tu > 10)
515             sprintf(buf, "%s", big[tu/10]);
516         else if (tu < 20)
517             sprintf(buf, "%s", small[tu]);
518         else
519             sprintf(buf, "%s %s", big[tu/10], small[tu % 10]);
520     }
521
522     return(buf);
523 } /* num_word() */