OSDN Git Service

166decb74e473085ed4e3e2e176da7618b031d25
[pg-rex/syncrep.git] / 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  * $PostgreSQL: pgsql/src/backend/utils/adt/cash.c,v 1.63 2004/05/07 00:24:58 tgl Exp $
13  */
14
15 #include "postgres.h"
16
17 #include <limits.h>
18 #include <ctype.h>
19 #include <math.h>
20 #include <locale.h>
21
22 #include "libpq/pqformat.h"
23 #include "miscadmin.h"
24 #include "utils/builtins.h"
25 #include "utils/cash.h"
26 #include "utils/pg_locale.h"
27
28
29 static const char *num_word(Cash value);
30
31 /* when we go to 64 bit values we will have to modify this */
32 #define CASH_BUFSZ              24
33
34 #define TERMINATOR              (CASH_BUFSZ - 1)
35 #define LAST_PAREN              (TERMINATOR - 1)
36 #define LAST_DIGIT              (LAST_PAREN - 1)
37
38
39 /*
40  * Cash is a pass-by-ref SQL type, so we must pass and return pointers.
41  * These macros and support routine hide the pass-by-refness.
42  */
43 #define PG_GETARG_CASH(n)  (* ((Cash *) PG_GETARG_POINTER(n)))
44 #define PG_RETURN_CASH(x)  return CashGetDatum(x)
45
46 static Datum
47 CashGetDatum(Cash value)
48 {
49         Cash       *result = (Cash *) palloc(sizeof(Cash));
50
51         *result = value;
52         return PointerGetDatum(result);
53 }
54
55
56 /* cash_in()
57  * Convert a string to a cash data type.
58  * Format is [$]###[,]###[.##]
59  * Examples: 123.45 $123.45 $123,456.78
60  *
61  * This is currently implemented as a 32-bit integer.
62  * XXX HACK It looks as though some of the symbols for
63  *      monetary values returned by localeconv() can be multiple
64  *      bytes/characters. This code assumes one byte only. - tgl 97/04/14
65  * XXX UNHACK Allow the currency symbol to be multibyte.
66  *      - thomas 1998-03-01
67  */
68 Datum
69 cash_in(PG_FUNCTION_ARGS)
70 {
71         char       *str = PG_GETARG_CSTRING(0);
72         Cash            result;
73         Cash            value = 0;
74         Cash            dec = 0;
75         Cash            sgn = 1;
76         int                     seen_dot = 0;
77         const char *s = str;
78         int                     fpoint;
79         char       *csymbol;
80         char            dsymbol,
81                                 ssymbol,
82                                 psymbol,
83                            *nsymbol;
84
85         struct lconv *lconvert = PGLC_localeconv();
86
87         /*
88          * frac_digits will be CHAR_MAX in some locales, notably C.  However,
89          * just testing for == CHAR_MAX is risky, because of compilers like
90          * gcc that "helpfully" let you alter the platform-standard definition
91          * of whether char is signed or not.  If we are so unfortunate as to
92          * get compiled with a nonstandard -fsigned-char or -funsigned-char
93          * switch, then our idea of CHAR_MAX will not agree with libc's. The
94          * safest course is not to test for CHAR_MAX at all, but to impose a
95          * range check for plausible frac_digits values.
96          */
97         fpoint = lconvert->frac_digits;
98         if (fpoint < 0 || fpoint > 10)
99                 fpoint = 2;                             /* best guess in this case, I think */
100
101         dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
102         ssymbol = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
103         csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
104         psymbol = ((*lconvert->positive_sign != '\0') ? *lconvert->positive_sign : '+');
105         nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
106
107 #ifdef CASHDEBUG
108         printf("cashin- precision '%d'; decimal '%c'; thousands '%c'; currency '%s'; positive '%c'; negative '%s'\n",
109                    fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
110 #endif
111
112         /* we need to add all sorts of checking here.  For now just */
113         /* strip all leading whitespace and any leading currency symbol */
114         while (isspace((unsigned char) *s))
115                 s++;
116         if (strncmp(s, csymbol, strlen(csymbol)) == 0)
117                 s += strlen(csymbol);
118
119 #ifdef CASHDEBUG
120         printf("cashin- string is '%s'\n", s);
121 #endif
122
123         /* a leading minus or paren signifies a negative number */
124         /* again, better heuristics needed */
125         if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
126         {
127                 sgn = -1;
128                 s += strlen(nsymbol);
129 #ifdef CASHDEBUG
130                 printf("cashin- negative symbol; string is '%s'\n", s);
131 #endif
132         }
133         else if (*s == '(')
134         {
135                 sgn = -1;
136                 s++;
137
138         }
139         else if (*s == psymbol)
140                 s++;
141
142 #ifdef CASHDEBUG
143         printf("cashin- string is '%s'\n", s);
144 #endif
145
146         while (isspace((unsigned char) *s))
147                 s++;
148         if (strncmp(s, csymbol, strlen(csymbol)) == 0)
149                 s += strlen(csymbol);
150
151 #ifdef CASHDEBUG
152         printf("cashin- string is '%s'\n", s);
153 #endif
154
155         for (;; s++)
156         {
157                 /* we look for digits as int4 as we have less */
158                 /* than the required number of decimal places */
159                 if (isdigit((unsigned char) *s) && dec < fpoint)
160                 {
161                         value = (value * 10) + *s - '0';
162
163                         if (seen_dot)
164                                 dec++;
165
166                         /* decimal point? then start counting fractions... */
167                 }
168                 else if (*s == dsymbol && !seen_dot)
169                 {
170                         seen_dot = 1;
171
172                         /* "thousands" separator? then skip... */
173                 }
174                 else if (*s == ssymbol)
175                 {
176
177                 }
178                 else
179                 {
180                         /* round off */
181                         if (isdigit((unsigned char) *s) && *s >= '5')
182                                 value++;
183
184                         /* adjust for less than required decimal places */
185                         for (; dec < fpoint; dec++)
186                                 value *= 10;
187
188                         break;
189                 }
190         }
191
192         while (isspace((unsigned char) *s) || *s == '0' || *s == ')')
193                 s++;
194
195         if (*s != '\0')
196                 ereport(ERROR,
197                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
198                                  errmsg("invalid input syntax for type money: \"%s\"", str)));
199
200         result = (value * sgn);
201
202 #ifdef CASHDEBUG
203         printf("cashin- result is %d\n", result);
204 #endif
205
206         PG_RETURN_CASH(result);
207 }
208
209
210 /* cash_out()
211  * Function to convert cash to a dollars and cents representation.
212  * XXX HACK This code appears to assume US conventions for
213  *      positive-valued amounts. - tgl 97/04/14
214  */
215 Datum
216 cash_out(PG_FUNCTION_ARGS)
217 {
218         Cash            value = PG_GETARG_CASH(0);
219         char       *result;
220         char            buf[CASH_BUFSZ];
221         int                     minus = 0;
222         int                     count = LAST_DIGIT;
223         int                     point_pos;
224         int                     comma_position = 0;
225         int                     points,
226                                 mon_group;
227         char            comma;
228         char       *csymbol,
229                                 dsymbol,
230                            *nsymbol;
231         char            convention;
232
233         struct lconv *lconvert = PGLC_localeconv();
234
235         /* see comments about frac_digits in cash_in() */
236         points = lconvert->frac_digits;
237         if (points < 0 || points > 10)
238                 points = 2;                             /* best guess in this case, I think */
239
240         /*
241          * As with frac_digits, must apply a range check to mon_grouping to
242          * avoid being fooled by variant CHAR_MAX values.
243          */
244         mon_group = *lconvert->mon_grouping;
245         if (mon_group <= 0 || mon_group > 6)
246                 mon_group = 3;
247
248         comma = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
249         convention = lconvert->n_sign_posn;
250         dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
251         csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
252         nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
253
254         point_pos = LAST_DIGIT - points;
255
256         /* allow more than three decimal points and separate them */
257         if (comma)
258         {
259                 point_pos -= (points - 1) / mon_group;
260                 comma_position = point_pos % (mon_group + 1);
261         }
262
263         /* we work with positive amounts and add the minus sign at the end */
264         if (value < 0)
265         {
266                 minus = 1;
267                 value = -value;
268         }
269
270         /* allow for trailing negative strings */
271         MemSet(buf, ' ', CASH_BUFSZ);
272         buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
273
274         while (value || count > (point_pos - 2))
275         {
276                 if (points && count == point_pos)
277                         buf[count--] = dsymbol;
278                 else if (comma && count % (mon_group + 1) == comma_position)
279                         buf[count--] = comma;
280
281                 buf[count--] = ((unsigned int) value % 10) + '0';
282                 value = ((unsigned int) value) / 10;
283         }
284
285         strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
286         count -= strlen(csymbol) - 1;
287
288         if (buf[LAST_DIGIT] == ',')
289                 buf[LAST_DIGIT] = buf[LAST_PAREN];
290
291         /* see if we need to signify negative amount */
292         if (minus)
293         {
294                 if (!PointerIsValid(result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol))))
295                         ereport(ERROR,
296                                         (errcode(ERRCODE_OUT_OF_MEMORY),
297                                          errmsg("out of memory")));
298
299                 /* Position code of 0 means use parens */
300                 if (convention == 0)
301                         sprintf(result, "(%s)", buf + count);
302                 else if (convention == 2)
303                         sprintf(result, "%s%s", buf + count, nsymbol);
304                 else
305                         sprintf(result, "%s%s", nsymbol, buf + count);
306         }
307         else
308         {
309                 if (!PointerIsValid(result = palloc(CASH_BUFSZ + 2 - count)))
310                         ereport(ERROR,
311                                         (errcode(ERRCODE_OUT_OF_MEMORY),
312                                          errmsg("out of memory")));
313
314                 strcpy(result, buf + count);
315         }
316
317         PG_RETURN_CSTRING(result);
318 }
319
320 /*
321  *              cash_recv                       - converts external binary format to cash
322  */
323 Datum
324 cash_recv(PG_FUNCTION_ARGS)
325 {
326         StringInfo      buf = (StringInfo) PG_GETARG_POINTER(0);
327
328         PG_RETURN_CASH((Cash) pq_getmsgint(buf, sizeof(Cash)));
329 }
330
331 /*
332  *              cash_send                       - converts cash to binary format
333  */
334 Datum
335 cash_send(PG_FUNCTION_ARGS)
336 {
337         Cash            arg1 = PG_GETARG_CASH(0);
338         StringInfoData buf;
339
340         pq_begintypsend(&buf);
341         pq_sendint(&buf, arg1, sizeof(Cash));
342         PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
343 }
344
345 /*
346  * Comparison functions
347  */
348
349 Datum
350 cash_eq(PG_FUNCTION_ARGS)
351 {
352         Cash            c1 = PG_GETARG_CASH(0);
353         Cash            c2 = PG_GETARG_CASH(1);
354
355         PG_RETURN_BOOL(c1 == c2);
356 }
357
358 Datum
359 cash_ne(PG_FUNCTION_ARGS)
360 {
361         Cash            c1 = PG_GETARG_CASH(0);
362         Cash            c2 = PG_GETARG_CASH(1);
363
364         PG_RETURN_BOOL(c1 != c2);
365 }
366
367 Datum
368 cash_lt(PG_FUNCTION_ARGS)
369 {
370         Cash            c1 = PG_GETARG_CASH(0);
371         Cash            c2 = PG_GETARG_CASH(1);
372
373         PG_RETURN_BOOL(c1 < c2);
374 }
375
376 Datum
377 cash_le(PG_FUNCTION_ARGS)
378 {
379         Cash            c1 = PG_GETARG_CASH(0);
380         Cash            c2 = PG_GETARG_CASH(1);
381
382         PG_RETURN_BOOL(c1 <= c2);
383 }
384
385 Datum
386 cash_gt(PG_FUNCTION_ARGS)
387 {
388         Cash            c1 = PG_GETARG_CASH(0);
389         Cash            c2 = PG_GETARG_CASH(1);
390
391         PG_RETURN_BOOL(c1 > c2);
392 }
393
394 Datum
395 cash_ge(PG_FUNCTION_ARGS)
396 {
397         Cash            c1 = PG_GETARG_CASH(0);
398         Cash            c2 = PG_GETARG_CASH(1);
399
400         PG_RETURN_BOOL(c1 >= c2);
401 }
402
403 Datum
404 cash_cmp(PG_FUNCTION_ARGS)
405 {
406         Cash            c1 = PG_GETARG_CASH(0);
407         Cash            c2 = PG_GETARG_CASH(1);
408
409         if (c1 > c2)
410                 PG_RETURN_INT32(1);
411         else if (c1 == c2)
412                 PG_RETURN_INT32(0);
413         else
414                 PG_RETURN_INT32(-1);
415 }
416
417
418 /* cash_pl()
419  * Add two cash values.
420  */
421 Datum
422 cash_pl(PG_FUNCTION_ARGS)
423 {
424         Cash            c1 = PG_GETARG_CASH(0);
425         Cash            c2 = PG_GETARG_CASH(1);
426         Cash            result;
427
428         result = c1 + c2;
429
430         PG_RETURN_CASH(result);
431 }
432
433
434 /* cash_mi()
435  * Subtract two cash values.
436  */
437 Datum
438 cash_mi(PG_FUNCTION_ARGS)
439 {
440         Cash            c1 = PG_GETARG_CASH(0);
441         Cash            c2 = PG_GETARG_CASH(1);
442         Cash            result;
443
444         result = c1 - c2;
445
446         PG_RETURN_CASH(result);
447 }
448
449
450 /* cash_mul_flt8()
451  * Multiply cash by float8.
452  */
453 Datum
454 cash_mul_flt8(PG_FUNCTION_ARGS)
455 {
456         Cash            c = PG_GETARG_CASH(0);
457         float8          f = PG_GETARG_FLOAT8(1);
458         Cash            result;
459
460         result = c * f;
461         PG_RETURN_CASH(result);
462 }
463
464
465 /* flt8_mul_cash()
466  * Multiply float8 by cash.
467  */
468 Datum
469 flt8_mul_cash(PG_FUNCTION_ARGS)
470 {
471         float8          f = PG_GETARG_FLOAT8(0);
472         Cash            c = PG_GETARG_CASH(1);
473         Cash            result;
474
475         result = f * c;
476         PG_RETURN_CASH(result);
477 }
478
479
480 /* cash_div_flt8()
481  * Divide cash by float8.
482  *
483  * XXX Don't know if rounding or truncating is correct behavior.
484  * Round for now. - tgl 97/04/15
485  */
486 Datum
487 cash_div_flt8(PG_FUNCTION_ARGS)
488 {
489         Cash            c = PG_GETARG_CASH(0);
490         float8          f = PG_GETARG_FLOAT8(1);
491         Cash            result;
492
493         if (f == 0.0)
494                 ereport(ERROR,
495                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
496                                  errmsg("division by zero")));
497
498         result = rint(c / f);
499         PG_RETURN_CASH(result);
500 }
501
502 /* cash_mul_flt4()
503  * Multiply cash by float4.
504  */
505 Datum
506 cash_mul_flt4(PG_FUNCTION_ARGS)
507 {
508         Cash            c = PG_GETARG_CASH(0);
509         float4          f = PG_GETARG_FLOAT4(1);
510         Cash            result;
511
512         result = c * f;
513         PG_RETURN_CASH(result);
514 }
515
516
517 /* flt4_mul_cash()
518  * Multiply float4 by cash.
519  */
520 Datum
521 flt4_mul_cash(PG_FUNCTION_ARGS)
522 {
523         float4          f = PG_GETARG_FLOAT4(0);
524         Cash            c = PG_GETARG_CASH(1);
525         Cash            result;
526
527         result = f * c;
528         PG_RETURN_CASH(result);
529 }
530
531
532 /* cash_div_flt4()
533  * Divide cash by float4.
534  *
535  * XXX Don't know if rounding or truncating is correct behavior.
536  * Round for now. - tgl 97/04/15
537  */
538 Datum
539 cash_div_flt4(PG_FUNCTION_ARGS)
540 {
541         Cash            c = PG_GETARG_CASH(0);
542         float4          f = PG_GETARG_FLOAT4(1);
543         Cash            result;
544
545         if (f == 0.0)
546                 ereport(ERROR,
547                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
548                                  errmsg("division by zero")));
549
550         result = rint(c / f);
551         PG_RETURN_CASH(result);
552 }
553
554
555 /* cash_mul_int4()
556  * Multiply cash by int4.
557  */
558 Datum
559 cash_mul_int4(PG_FUNCTION_ARGS)
560 {
561         Cash            c = PG_GETARG_CASH(0);
562         int32           i = PG_GETARG_INT32(1);
563         Cash            result;
564
565         result = c * i;
566         PG_RETURN_CASH(result);
567 }
568
569
570 /* int4_mul_cash()
571  * Multiply int4 by cash.
572  */
573 Datum
574 int4_mul_cash(PG_FUNCTION_ARGS)
575 {
576         int32           i = PG_GETARG_INT32(0);
577         Cash            c = PG_GETARG_CASH(1);
578         Cash            result;
579
580         result = i * c;
581         PG_RETURN_CASH(result);
582 }
583
584
585 /* cash_div_int4()
586  * Divide cash by 4-byte integer.
587  *
588  * XXX Don't know if rounding or truncating is correct behavior.
589  * Round for now. - tgl 97/04/15
590  */
591 Datum
592 cash_div_int4(PG_FUNCTION_ARGS)
593 {
594         Cash            c = PG_GETARG_CASH(0);
595         int32           i = PG_GETARG_INT32(1);
596         Cash            result;
597
598         if (i == 0)
599                 ereport(ERROR,
600                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
601                                  errmsg("division by zero")));
602
603         result = rint(c / i);
604
605         PG_RETURN_CASH(result);
606 }
607
608
609 /* cash_mul_int2()
610  * Multiply cash by int2.
611  */
612 Datum
613 cash_mul_int2(PG_FUNCTION_ARGS)
614 {
615         Cash            c = PG_GETARG_CASH(0);
616         int16           s = PG_GETARG_INT16(1);
617         Cash            result;
618
619         result = c * s;
620         PG_RETURN_CASH(result);
621 }
622
623 /* int2_mul_cash()
624  * Multiply int2 by cash.
625  */
626 Datum
627 int2_mul_cash(PG_FUNCTION_ARGS)
628 {
629         int16           s = PG_GETARG_INT16(0);
630         Cash            c = PG_GETARG_CASH(1);
631         Cash            result;
632
633         result = s * c;
634         PG_RETURN_CASH(result);
635 }
636
637 /* cash_div_int2()
638  * Divide cash by int2.
639  *
640  * XXX Don't know if rounding or truncating is correct behavior.
641  * Round for now. - tgl 97/04/15
642  */
643 Datum
644 cash_div_int2(PG_FUNCTION_ARGS)
645 {
646         Cash            c = PG_GETARG_CASH(0);
647         int16           s = PG_GETARG_INT16(1);
648         Cash            result;
649
650         if (s == 0)
651                 ereport(ERROR,
652                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
653                                  errmsg("division by zero")));
654
655         result = rint(c / s);
656         PG_RETURN_CASH(result);
657 }
658
659 /* cashlarger()
660  * Return larger of two cash values.
661  */
662 Datum
663 cashlarger(PG_FUNCTION_ARGS)
664 {
665         Cash            c1 = PG_GETARG_CASH(0);
666         Cash            c2 = PG_GETARG_CASH(1);
667         Cash            result;
668
669         result = (c1 > c2) ? c1 : c2;
670
671         PG_RETURN_CASH(result);
672 }
673
674 /* cashsmaller()
675  * Return smaller of two cash values.
676  */
677 Datum
678 cashsmaller(PG_FUNCTION_ARGS)
679 {
680         Cash            c1 = PG_GETARG_CASH(0);
681         Cash            c2 = PG_GETARG_CASH(1);
682         Cash            result;
683
684         result = (c1 < c2) ? c1 : c2;
685
686         PG_RETURN_CASH(result);
687 }
688
689
690 /* cash_words()
691  * This converts a int4 as well but to a representation using words
692  * Obviously way North American centric - sorry
693  */
694 Datum
695 cash_words(PG_FUNCTION_ARGS)
696 {
697         Cash            value = PG_GETARG_CASH(0);
698         unsigned int val;
699         char            buf[256];
700         char       *p = buf;
701         Cash            m0;
702         Cash            m1;
703         Cash            m2;
704         Cash            m3;
705         text       *result;
706
707         /* work with positive numbers */
708         if (value < 0)
709         {
710                 value = -value;
711                 strcpy(buf, "minus ");
712                 p += 6;
713         }
714         else
715                 buf[0] = '\0';
716
717         /* Now treat as unsigned, to avoid trouble at INT_MIN */
718         val = (unsigned int) value;
719
720         m0 = val % 100;                         /* cents */
721         m1 = (val / 100) % 1000;        /* hundreds */
722         m2 = (val / 100000) % 1000; /* thousands */
723         m3 = val / 100000000 % 1000;    /* millions */
724
725         if (m3)
726         {
727                 strcat(buf, num_word(m3));
728                 strcat(buf, " million ");
729         }
730
731         if (m2)
732         {
733                 strcat(buf, num_word(m2));
734                 strcat(buf, " thousand ");
735         }
736
737         if (m1)
738                 strcat(buf, num_word(m1));
739
740         if (!*p)
741                 strcat(buf, "zero");
742
743         strcat(buf, (val / 100) == 1 ? " dollar and " : " dollars and ");
744         strcat(buf, num_word(m0));
745         strcat(buf, m0 == 1 ? " cent" : " cents");
746
747         /* capitalize output */
748         buf[0] = pg_toupper((unsigned char) buf[0]);
749
750         /* make a text type for output */
751         result = (text *) palloc(strlen(buf) + VARHDRSZ);
752         VARATT_SIZEP(result) = strlen(buf) + VARHDRSZ;
753         memcpy(VARDATA(result), buf, strlen(buf));
754
755         PG_RETURN_TEXT_P(result);
756 }
757
758
759 /*************************************************************************
760  * Private routines
761  ************************************************************************/
762
763 static const char *
764 num_word(Cash value)
765 {
766         static char buf[128];
767         static const char *small[] = {
768                 "zero", "one", "two", "three", "four", "five", "six", "seven",
769                 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
770                 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
771                 "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"
772         };
773         const char **big = small + 18;
774         int                     tu = value % 100;
775
776         /* deal with the simple cases first */
777         if (value <= 20)
778                 return small[value];
779
780         /* is it an even multiple of 100? */
781         if (!tu)
782         {
783                 sprintf(buf, "%s hundred", small[value / 100]);
784                 return buf;
785         }
786
787         /* more than 99? */
788         if (value > 99)
789         {
790                 /* is it an even multiple of 10 other than 10? */
791                 if (value % 10 == 0 && tu > 10)
792                         sprintf(buf, "%s hundred %s",
793                                         small[value / 100], big[tu / 10]);
794                 else if (tu < 20)
795                         sprintf(buf, "%s hundred and %s",
796                                         small[value / 100], small[tu]);
797                 else
798                         sprintf(buf, "%s hundred %s %s",
799                                         small[value / 100], big[tu / 10], small[tu % 10]);
800
801         }
802         else
803         {
804                 /* is it an even multiple of 10 other than 10? */
805                 if (value % 10 == 0 && tu > 10)
806                         sprintf(buf, "%s", big[tu / 10]);
807                 else if (tu < 20)
808                         sprintf(buf, "%s", small[tu]);
809                 else
810                         sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);
811         }
812
813         return buf;
814 }       /* num_word() */