3 * Written by D'Arcy J.M. Cain
5 * http://www.druid.net/darcy/
7 * Functions to allow input and output of money normally but store
8 * and handle it as 64 bit ints
10 * A slightly modified version of this file and a discussion of the
11 * workings can be found in the book "Software Solutions in C" by
12 * Dale Schumacher, Academic Press, ISBN: 0-12-632360-7 except that
13 * this version handles 64 bit numbers and so can hold values up to
14 * $92,233,720,368,547,758.07.
26 #include "libpq/pqformat.h"
27 #include "utils/builtins.h"
28 #include "utils/cash.h"
29 #include "utils/pg_locale.h"
33 #define TERMINATOR (CASH_BUFSZ - 1)
34 #define LAST_PAREN (TERMINATOR - 1)
35 #define LAST_DIGIT (LAST_PAREN - 1)
38 /*************************************************************************
40 ************************************************************************/
46 static const char *small
[] = {
47 "zero", "one", "two", "three", "four", "five", "six", "seven",
48 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
49 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
50 "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"
52 const char **big
= small
+ 18;
55 /* deal with the simple cases first */
59 /* is it an even multiple of 100? */
62 sprintf(buf
, "%s hundred", small
[value
/ 100]);
69 /* is it an even multiple of 10 other than 10? */
70 if (value
% 10 == 0 && tu
> 10)
71 sprintf(buf
, "%s hundred %s",
72 small
[value
/ 100], big
[tu
/ 10]);
74 sprintf(buf
, "%s hundred and %s",
75 small
[value
/ 100], small
[tu
]);
77 sprintf(buf
, "%s hundred %s %s",
78 small
[value
/ 100], big
[tu
/ 10], small
[tu
% 10]);
82 /* is it an even multiple of 10 other than 10? */
83 if (value
% 10 == 0 && tu
> 10)
84 sprintf(buf
, "%s", big
[tu
/ 10]);
86 sprintf(buf
, "%s", small
[tu
]);
88 sprintf(buf
, "%s %s", big
[tu
/ 10], small
[tu
% 10]);
96 * Convert a string to a cash data type.
97 * Format is [$]###[,]###[.##]
98 * Examples: 123.45 $123.45 $123,456.78
102 cash_in(PG_FUNCTION_ARGS
)
104 char *str
= PG_GETARG_CSTRING(0);
118 struct lconv
*lconvert
= PGLC_localeconv();
121 * frac_digits will be CHAR_MAX in some locales, notably C. However, just
122 * testing for == CHAR_MAX is risky, because of compilers like gcc that
123 * "helpfully" let you alter the platform-standard definition of whether
124 * char is signed or not. If we are so unfortunate as to get compiled
125 * with a nonstandard -fsigned-char or -funsigned-char switch, then our
126 * idea of CHAR_MAX will not agree with libc's. The safest course is not
127 * to test for CHAR_MAX at all, but to impose a range check for plausible
128 * frac_digits values.
130 fpoint
= lconvert
->frac_digits
;
131 if (fpoint
< 0 || fpoint
> 10)
132 fpoint
= 2; /* best guess in this case, I think */
134 dsymbol
= ((*lconvert
->mon_decimal_point
!= '\0') ? *lconvert
->mon_decimal_point
: '.');
135 if (*lconvert
->mon_thousands_sep
!= '\0')
136 ssymbol
= *lconvert
->mon_thousands_sep
;
138 /* ssymbol should not equal dsymbol */
139 ssymbol
= (dsymbol
!= ',') ? ',' : '.';
140 csymbol
= ((*lconvert
->currency_symbol
!= '\0') ? lconvert
->currency_symbol
: "$");
141 psymbol
= ((*lconvert
->positive_sign
!= '\0') ? *lconvert
->positive_sign
: '+');
142 nsymbol
= ((*lconvert
->negative_sign
!= '\0') ? lconvert
->negative_sign
: "-");
145 printf("cashin- precision '%d'; decimal '%c'; thousands '%c'; currency '%s'; positive '%c'; negative '%s'\n",
146 fpoint
, dsymbol
, ssymbol
, csymbol
, psymbol
, nsymbol
);
149 /* we need to add all sorts of checking here. For now just */
150 /* strip all leading whitespace and any leading currency symbol */
151 while (isspace((unsigned char) *s
))
153 if (strncmp(s
, csymbol
, strlen(csymbol
)) == 0)
154 s
+= strlen(csymbol
);
157 printf("cashin- string is '%s'\n", s
);
160 /* a leading minus or paren signifies a negative number */
161 /* again, better heuristics needed */
162 /* XXX - doesn't properly check for balanced parens - djmc */
163 if (strncmp(s
, nsymbol
, strlen(nsymbol
)) == 0)
166 s
+= strlen(nsymbol
);
168 printf("cashin- negative symbol; string is '%s'\n", s
);
176 else if (*s
== psymbol
)
180 printf("cashin- string is '%s'\n", s
);
183 while (isspace((unsigned char) *s
))
185 if (strncmp(s
, csymbol
, strlen(csymbol
)) == 0)
186 s
+= strlen(csymbol
);
189 printf("cashin- string is '%s'\n", s
);
194 /* we look for digits as int8 as we have less */
195 /* than the required number of decimal places */
196 if (isdigit((unsigned char) *s
) && dec
< fpoint
)
198 value
= (value
* 10) + *s
- '0';
204 /* decimal point? then start counting fractions... */
205 else if (*s
== dsymbol
&& !seen_dot
)
210 /* not "thousands" separator? */
211 else if (*s
!= ssymbol
)
214 if (isdigit((unsigned char) *s
) && *s
>= '5')
217 /* adjust for less than required decimal places */
218 for (; dec
< fpoint
; dec
++)
225 /* should only be trailing digits followed by whitespace or right paren */
226 while (isdigit((unsigned char) *s
))
228 while (isspace((unsigned char) *s
) || *s
== ')')
233 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION
),
234 errmsg("invalid input syntax for type money: \"%s\"", str
)));
236 result
= value
* sgn
;
239 printf("cashin- result is %d\n", result
);
242 PG_RETURN_CASH(result
);
247 * Function to convert cash to a dollars and cents representation.
248 * XXX HACK This code appears to assume US conventions for
249 * positive-valued amounts. - tgl 97/04/14
252 cash_out(PG_FUNCTION_ARGS
)
254 Cash value
= PG_GETARG_CASH(0);
256 char buf
[CASH_BUFSZ
];
258 int count
= LAST_DIGIT
;
260 int ssymbol_position
= 0;
269 struct lconv
*lconvert
= PGLC_localeconv();
271 /* see comments about frac_digits in cash_in() */
272 points
= lconvert
->frac_digits
;
273 if (points
< 0 || points
> 10)
274 points
= 2; /* best guess in this case, I think */
277 * As with frac_digits, must apply a range check to mon_grouping to avoid
278 * being fooled by variant CHAR_MAX values.
280 mon_group
= *lconvert
->mon_grouping
;
281 if (mon_group
<= 0 || mon_group
> 6)
284 convention
= lconvert
->n_sign_posn
;
285 dsymbol
= ((*lconvert
->mon_decimal_point
!= '\0') ? *lconvert
->mon_decimal_point
: '.');
286 if (*lconvert
->mon_thousands_sep
!= '\0')
287 ssymbol
= *lconvert
->mon_thousands_sep
;
289 /* ssymbol should not equal dsymbol */
290 ssymbol
= (dsymbol
!= ',') ? ',' : '.';
291 csymbol
= ((*lconvert
->currency_symbol
!= '\0') ? lconvert
->currency_symbol
: "$");
292 nsymbol
= ((*lconvert
->negative_sign
!= '\0') ? lconvert
->negative_sign
: "-");
294 point_pos
= LAST_DIGIT
- points
;
296 point_pos
-= (points
- 1) / mon_group
;
297 ssymbol_position
= point_pos
% (mon_group
+ 1);
299 /* we work with positive amounts and add the minus sign at the end */
306 /* allow for trailing negative strings */
307 MemSet(buf
, ' ', CASH_BUFSZ
);
308 buf
[TERMINATOR
] = buf
[LAST_PAREN
] = '\0';
310 while (value
|| count
> (point_pos
- 2))
312 if (points
&& count
== point_pos
)
313 buf
[count
--] = dsymbol
;
314 else if (ssymbol
&& count
% (mon_group
+ 1) == ssymbol_position
)
315 buf
[count
--] = ssymbol
;
317 buf
[count
--] = ((uint64
) value
% 10) + '0';
318 value
= ((uint64
) value
) / 10;
321 strncpy((buf
+ count
- strlen(csymbol
) + 1), csymbol
, strlen(csymbol
));
322 count
-= strlen(csymbol
) - 1;
325 * If points == 0 and the number of digits % mon_group == 0,
326 * the code above adds a trailing ssymbol on the far right,
329 if (buf
[LAST_DIGIT
] == ssymbol
)
330 buf
[LAST_DIGIT
] = '\0';
332 /* see if we need to signify negative amount */
335 result
= palloc(CASH_BUFSZ
+ 2 - count
+ strlen(nsymbol
));
337 /* Position code of 0 means use parens */
339 sprintf(result
, "(%s)", buf
+ count
);
340 else if (convention
== 2)
341 sprintf(result
, "%s%s", buf
+ count
, nsymbol
);
343 sprintf(result
, "%s%s", nsymbol
, buf
+ count
);
347 result
= palloc(CASH_BUFSZ
+ 2 - count
);
348 strcpy(result
, buf
+ count
);
351 PG_RETURN_CSTRING(result
);
355 * cash_recv - converts external binary format to cash
358 cash_recv(PG_FUNCTION_ARGS
)
360 StringInfo buf
= (StringInfo
) PG_GETARG_POINTER(0);
362 PG_RETURN_CASH((Cash
) pq_getmsgint64(buf
));
366 * cash_send - converts cash to binary format
369 cash_send(PG_FUNCTION_ARGS
)
371 Cash arg1
= PG_GETARG_CASH(0);
374 pq_begintypsend(&buf
);
375 pq_sendint64(&buf
, arg1
);
376 PG_RETURN_BYTEA_P(pq_endtypsend(&buf
));
380 * Comparison functions
384 cash_eq(PG_FUNCTION_ARGS
)
386 Cash c1
= PG_GETARG_CASH(0);
387 Cash c2
= PG_GETARG_CASH(1);
389 PG_RETURN_BOOL(c1
== c2
);
393 cash_ne(PG_FUNCTION_ARGS
)
395 Cash c1
= PG_GETARG_CASH(0);
396 Cash c2
= PG_GETARG_CASH(1);
398 PG_RETURN_BOOL(c1
!= c2
);
402 cash_lt(PG_FUNCTION_ARGS
)
404 Cash c1
= PG_GETARG_CASH(0);
405 Cash c2
= PG_GETARG_CASH(1);
407 PG_RETURN_BOOL(c1
< c2
);
411 cash_le(PG_FUNCTION_ARGS
)
413 Cash c1
= PG_GETARG_CASH(0);
414 Cash c2
= PG_GETARG_CASH(1);
416 PG_RETURN_BOOL(c1
<= c2
);
420 cash_gt(PG_FUNCTION_ARGS
)
422 Cash c1
= PG_GETARG_CASH(0);
423 Cash c2
= PG_GETARG_CASH(1);
425 PG_RETURN_BOOL(c1
> c2
);
429 cash_ge(PG_FUNCTION_ARGS
)
431 Cash c1
= PG_GETARG_CASH(0);
432 Cash c2
= PG_GETARG_CASH(1);
434 PG_RETURN_BOOL(c1
>= c2
);
438 cash_cmp(PG_FUNCTION_ARGS
)
440 Cash c1
= PG_GETARG_CASH(0);
441 Cash c2
= PG_GETARG_CASH(1);
453 * Add two cash values.
456 cash_pl(PG_FUNCTION_ARGS
)
458 Cash c1
= PG_GETARG_CASH(0);
459 Cash c2
= PG_GETARG_CASH(1);
464 PG_RETURN_CASH(result
);
469 * Subtract two cash values.
472 cash_mi(PG_FUNCTION_ARGS
)
474 Cash c1
= PG_GETARG_CASH(0);
475 Cash c2
= PG_GETARG_CASH(1);
480 PG_RETURN_CASH(result
);
485 * Multiply cash by float8.
488 cash_mul_flt8(PG_FUNCTION_ARGS
)
490 Cash c
= PG_GETARG_CASH(0);
491 float8 f
= PG_GETARG_FLOAT8(1);
495 PG_RETURN_CASH(result
);
500 * Multiply float8 by cash.
503 flt8_mul_cash(PG_FUNCTION_ARGS
)
505 float8 f
= PG_GETARG_FLOAT8(0);
506 Cash c
= PG_GETARG_CASH(1);
510 PG_RETURN_CASH(result
);
515 * Divide cash by float8.
518 cash_div_flt8(PG_FUNCTION_ARGS
)
520 Cash c
= PG_GETARG_CASH(0);
521 float8 f
= PG_GETARG_FLOAT8(1);
526 (errcode(ERRCODE_DIVISION_BY_ZERO
),
527 errmsg("division by zero")));
529 result
= rint(c
/ f
);
530 PG_RETURN_CASH(result
);
535 * Multiply cash by float4.
538 cash_mul_flt4(PG_FUNCTION_ARGS
)
540 Cash c
= PG_GETARG_CASH(0);
541 float4 f
= PG_GETARG_FLOAT4(1);
545 PG_RETURN_CASH(result
);
550 * Multiply float4 by cash.
553 flt4_mul_cash(PG_FUNCTION_ARGS
)
555 float4 f
= PG_GETARG_FLOAT4(0);
556 Cash c
= PG_GETARG_CASH(1);
560 PG_RETURN_CASH(result
);
565 * Divide cash by float4.
569 cash_div_flt4(PG_FUNCTION_ARGS
)
571 Cash c
= PG_GETARG_CASH(0);
572 float4 f
= PG_GETARG_FLOAT4(1);
577 (errcode(ERRCODE_DIVISION_BY_ZERO
),
578 errmsg("division by zero")));
580 result
= rint(c
/ f
);
581 PG_RETURN_CASH(result
);
586 * Multiply cash by int8.
589 cash_mul_int8(PG_FUNCTION_ARGS
)
591 Cash c
= PG_GETARG_CASH(0);
592 int64 i
= PG_GETARG_INT64(1);
596 PG_RETURN_CASH(result
);
601 * Multiply int8 by cash.
604 int8_mul_cash(PG_FUNCTION_ARGS
)
606 int64 i
= PG_GETARG_INT64(0);
607 Cash c
= PG_GETARG_CASH(1);
611 PG_RETURN_CASH(result
);
615 * Divide cash by 8-byte integer.
618 cash_div_int8(PG_FUNCTION_ARGS
)
620 Cash c
= PG_GETARG_CASH(0);
621 int64 i
= PG_GETARG_INT64(1);
626 (errcode(ERRCODE_DIVISION_BY_ZERO
),
627 errmsg("division by zero")));
629 result
= rint(c
/ i
);
631 PG_RETURN_CASH(result
);
636 * Multiply cash by int4.
639 cash_mul_int4(PG_FUNCTION_ARGS
)
641 Cash c
= PG_GETARG_CASH(0);
642 int32 i
= PG_GETARG_INT32(1);
646 PG_RETURN_CASH(result
);
651 * Multiply int4 by cash.
654 int4_mul_cash(PG_FUNCTION_ARGS
)
656 int32 i
= PG_GETARG_INT32(0);
657 Cash c
= PG_GETARG_CASH(1);
661 PG_RETURN_CASH(result
);
666 * Divide cash by 4-byte integer.
670 cash_div_int4(PG_FUNCTION_ARGS
)
672 Cash c
= PG_GETARG_CASH(0);
673 int32 i
= PG_GETARG_INT32(1);
678 (errcode(ERRCODE_DIVISION_BY_ZERO
),
679 errmsg("division by zero")));
681 result
= rint(c
/ i
);
683 PG_RETURN_CASH(result
);
688 * Multiply cash by int2.
691 cash_mul_int2(PG_FUNCTION_ARGS
)
693 Cash c
= PG_GETARG_CASH(0);
694 int16 s
= PG_GETARG_INT16(1);
698 PG_RETURN_CASH(result
);
702 * Multiply int2 by cash.
705 int2_mul_cash(PG_FUNCTION_ARGS
)
707 int16 s
= PG_GETARG_INT16(0);
708 Cash c
= PG_GETARG_CASH(1);
712 PG_RETURN_CASH(result
);
716 * Divide cash by int2.
720 cash_div_int2(PG_FUNCTION_ARGS
)
722 Cash c
= PG_GETARG_CASH(0);
723 int16 s
= PG_GETARG_INT16(1);
728 (errcode(ERRCODE_DIVISION_BY_ZERO
),
729 errmsg("division by zero")));
731 result
= rint(c
/ s
);
732 PG_RETURN_CASH(result
);
736 * Return larger of two cash values.
739 cashlarger(PG_FUNCTION_ARGS
)
741 Cash c1
= PG_GETARG_CASH(0);
742 Cash c2
= PG_GETARG_CASH(1);
745 result
= (c1
> c2
) ? c1
: c2
;
747 PG_RETURN_CASH(result
);
751 * Return smaller of two cash values.
754 cashsmaller(PG_FUNCTION_ARGS
)
756 Cash c1
= PG_GETARG_CASH(0);
757 Cash c2
= PG_GETARG_CASH(1);
760 result
= (c1
< c2
) ? c1
: c2
;
762 PG_RETURN_CASH(result
);
766 * This converts a int4 as well but to a representation using words
767 * Obviously way North American centric - sorry
770 cash_words(PG_FUNCTION_ARGS
)
772 Cash value
= PG_GETARG_CASH(0);
784 /* work with positive numbers */
788 strcpy(buf
, "minus ");
794 /* Now treat as unsigned, to avoid trouble at INT_MIN */
795 val
= (uint64
) value
;
797 m0
= val
% INT64CONST(100); /* cents */
798 m1
= (val
/ INT64CONST(100)) % 1000; /* hundreds */
799 m2
= (val
/ INT64CONST(100000)) % 1000; /* thousands */
800 m3
= (val
/ INT64CONST(100000000)) % 1000; /* millions */
801 m4
= (val
/ INT64CONST(100000000000)) % 1000; /* billions */
802 m5
= (val
/ INT64CONST(100000000000000)) % 1000; /* trillions */
803 m6
= (val
/ INT64CONST(100000000000000000)) % 1000; /* quadrillions */
807 strcat(buf
, num_word(m6
));
808 strcat(buf
, " quadrillion ");
813 strcat(buf
, num_word(m5
));
814 strcat(buf
, " trillion ");
819 strcat(buf
, num_word(m4
));
820 strcat(buf
, " billion ");
825 strcat(buf
, num_word(m3
));
826 strcat(buf
, " million ");
831 strcat(buf
, num_word(m2
));
832 strcat(buf
, " thousand ");
836 strcat(buf
, num_word(m1
));
841 strcat(buf
, (val
/ 100) == 1 ? " dollar and " : " dollars and ");
842 strcat(buf
, num_word(m0
));
843 strcat(buf
, m0
== 1 ? " cent" : " cents");
845 /* capitalize output */
846 buf
[0] = pg_toupper((unsigned char) buf
[0]);
848 /* return as text datum */
849 PG_RETURN_TEXT_P(cstring_to_text(buf
));