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