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