cash.c 16.2 KB
Newer Older
Marc G. Fournier's avatar
Marc G. Fournier committed
1
/*
2 3 4 5
 * cash.c
 * Written by D'Arcy J.M. Cain
 *
 * Functions to allow input and output of money normally but store
6
 * and handle it as int4s
7 8 9 10
 *
 * A slightly modified version of this file and a discussion of the
 * workings can be found in the book "Software Solutions in C" by
 * Dale Schumacher, Academic Press, ISBN: 0-12-632360-7.
11
 *
12
 * $Header: /cvsroot/pgsql/src/backend/utils/adt/cash.c,v 1.33 2000/01/15 02:59:36 petere Exp $
13 14 15 16 17 18 19 20 21 22
 */

#include <limits.h>
#include <ctype.h>
#include <locale.h>

#include "postgres.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/cash.h"
Marc G. Fournier's avatar
Marc G. Fournier committed
23

Vadim B. Mikheev's avatar
Vadim B. Mikheev committed
24 25
static const char *num_word(Cash value);

26
/* when we go to 64 bit values we will have to modify this */
27
#define CASH_BUFSZ		24
28

29 30 31
#define TERMINATOR		(CASH_BUFSZ - 1)
#define LAST_PAREN		(TERMINATOR - 1)
#define LAST_DIGIT		(LAST_PAREN - 1)
32 33

#ifdef USE_LOCALE
34
static struct lconv *lconvert = NULL;
35 36 37 38 39 40
#endif

/* cash_in()
 * Convert a string to a cash data type.
 * Format is [$]###[,]###[.##]
 * Examples: 123.45 $123.45 $123,456.78
41
 *
42 43
 * This is currently implemented as a 32-bit integer.
 * XXX HACK It looks as though some of the symbols for
44 45
 *	monetary values returned by localeconv() can be multiple
 *	bytes/characters. This code assumes one byte only. - tgl 97/04/14
46
 * XXX UNHACK Allow the currency symbol to be multi-byte.
47
 *	- thomas 1998-03-01
48
 */
49
Cash *
50 51
cash_in(const char *str)
{
52 53 54 55 56 57 58 59
	Cash	   *result;

	Cash		value = 0;
	Cash		dec = 0;
	Cash		sgn = 1;
	int			seen_dot = 0;
	const char *s = str;
	int			fpoint;
60
	char	   *csymbol;
61 62 63
	char		dsymbol,
				ssymbol,
				psymbol,
64
			   *nsymbol;
65 66

#ifdef USE_LOCALE
67 68 69 70
#ifdef CASHDEBUG
	setlocale(LC_ALL, "");
	lconvert = localeconv();
#endif
71 72
	if (lconvert == NULL)
		lconvert = localeconv();
73 74 75

	/* frac_digits in the C locale seems to return CHAR_MAX */
	/* best guess is 2 in this case I think */
76
	fpoint = ((lconvert->frac_digits != CHAR_MAX) ? lconvert->frac_digits : 2); /* int_frac_digits? */
77

78 79 80 81 82
	dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
	ssymbol = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
	csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
	psymbol = ((*lconvert->positive_sign != '\0') ? *lconvert->positive_sign : '+');
	nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
83
#else
84 85 86
	fpoint = 2;
	dsymbol = '.';
	ssymbol = ',';
87
	csymbol = "$";
88
	psymbol = '+';
89
	nsymbol = "-";
90 91
#endif

92
#ifdef CASHDEBUG
93 94
	printf("cashin- precision '%d'; decimal '%c'; thousands '%c'; currency '%s'; positive '%c'; negative '%s'\n",
		   fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
95 96
#endif

97
	/* we need to add all sorts of checking here.  For now just */
98
	/* strip all leading whitespace and any leading currency symbol */
99 100 101 102
	while (isspace(*s))
		s++;
	if (strncmp(s, csymbol, strlen(csymbol)) == 0)
		s += strlen(csymbol);
103 104

#ifdef CASHDEBUG
105
	printf("cashin- string is '%s'\n", s);
106
#endif
107

108 109
	/* a leading minus or paren signifies a negative number */
	/* again, better heuristics needed */
110
	if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
111 112 113 114
	{
		sgn = -1;
		s += strlen(nsymbol);
#ifdef CASHDEBUG
115
		printf("cashin- negative symbol; string is '%s'\n", s);
116 117 118
#endif
	}
	else if (*s == '(')
119 120 121
	{
		sgn = -1;
		s++;
122

123 124 125
	}
	else if (*s == psymbol)
		s++;
126

127
#ifdef CASHDEBUG
128
	printf("cashin- string is '%s'\n", s);
129 130
#endif

131 132 133 134
	while (isspace(*s))
		s++;
	if (strncmp(s, csymbol, strlen(csymbol)) == 0)
		s += strlen(csymbol);
135 136

#ifdef CASHDEBUG
137
	printf("cashin- string is '%s'\n", s);
138
#endif
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174

	for (;; s++)
	{
		/* we look for digits as int4 as we have less */
		/* than the required number of decimal places */
		if (isdigit(*s) && dec < fpoint)
		{
			value = (value * 10) + *s - '0';

			if (seen_dot)
				dec++;

			/* decimal point? then start counting fractions... */
		}
		else if (*s == dsymbol && !seen_dot)
		{
			seen_dot = 1;

			/* "thousands" separator? then skip... */
		}
		else if (*s == ssymbol)
		{

		}
		else
		{
			/* round off */
			if (isdigit(*s) && *s >= '5')
				value++;

			/* adjust for less than required decimal places */
			for (; dec < fpoint; dec++)
				value *= 10;

			break;
		}
175
	}
Marc G. Fournier's avatar
Marc G. Fournier committed
176

177 178
	while (isspace(*s) || *s == '0' || *s == ')')
		s++;
Marc G. Fournier's avatar
Marc G. Fournier committed
179

180
	if (*s != '\0')
181
		elog(ERROR, "Bad money external representation %s", str);
Marc G. Fournier's avatar
Marc G. Fournier committed
182

183
	if (!PointerIsValid(result = palloc(sizeof(Cash))))
184
		elog(ERROR, "Memory allocation failed, can't input cash '%s'", str);
Marc G. Fournier's avatar
Marc G. Fournier committed
185

186
	*result = (value * sgn);
Marc G. Fournier's avatar
Marc G. Fournier committed
187

188
#ifdef CASHDEBUG
189
	printf("cashin- result is %d\n", *result);
190 191
#endif

192
	return result;
193
}	/* cash_in() */
Marc G. Fournier's avatar
Marc G. Fournier committed
194 195


196 197 198
/* cash_out()
 * Function to convert cash to a dollars and cents representation.
 * XXX HACK This code appears to assume US conventions for
199
 *	positive-valued amounts. - tgl 97/04/14
200
 */
201
const char *
202
cash_out(Cash *in_value)
Marc G. Fournier's avatar
Marc G. Fournier committed
203
{
204
	Cash		value = *in_value;
205 206 207 208 209 210 211 212 213
	char	   *result;
	char		buf[CASH_BUFSZ];
	int			minus = 0;
	int			count = LAST_DIGIT;
	int			point_pos;
	int			comma_position = 0;
	char		mon_group,
				comma,
				points;
214
	char	   *csymbol,
215 216 217
				dsymbol,
			   *nsymbol;
	char		convention;
218 219

#ifdef USE_LOCALE
220 221 222 223
	if (lconvert == NULL)
		lconvert = localeconv();

	mon_group = *lconvert->mon_grouping;
224
	comma = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
225 226
	/* frac_digits in the C locale seems to return CHAR_MAX */
	/* best guess is 2 in this case I think */
227
	points = ((lconvert->frac_digits != CHAR_MAX) ? lconvert->frac_digits : 2); /* int_frac_digits? */
228
	convention = lconvert->n_sign_posn;
229 230 231
	dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
	csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
	nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
232
#else
233 234
	mon_group = 3;
	comma = ',';
235
	csymbol = "$";
236 237 238 239
	dsymbol = '.';
	nsymbol = "-";
	points = 2;
	convention = 0;
240 241
#endif

242
	point_pos = LAST_DIGIT - points;
243

244
	/* We're playing a little fast and loose with this.  Shoot me. */
245
	/* Not me, that was the other guy. Haven't fixed it yet - thomas */
246 247 248 249 250 251 252 253 254 255 256
	if (!mon_group || mon_group == CHAR_MAX)
		mon_group = 3;

	/* allow more than three decimal points and separate them */
	if (comma)
	{
		point_pos -= (points - 1) / mon_group;
		comma_position = point_pos % (mon_group + 1);
	}

	/* we work with positive amounts and add the minus sign at the end */
Bruce Momjian's avatar
Bruce Momjian committed
257
	if (value < 0)
258 259
	{
		minus = 1;
Bruce Momjian's avatar
Bruce Momjian committed
260
		value *= -1;
261
	}
262

263
	/* allow for trailing negative strings */
Bruce Momjian's avatar
Bruce Momjian committed
264
	MemSet(buf, ' ', CASH_BUFSZ);
265 266
	buf[TERMINATOR] = buf[LAST_PAREN] = '\0';

Bruce Momjian's avatar
Bruce Momjian committed
267
	while (value || count > (point_pos - 2))
268 269 270 271 272 273
	{
		if (points && count == point_pos)
			buf[count--] = dsymbol;
		else if (comma && count % (mon_group + 1) == comma_position)
			buf[count--] = comma;

Bruce Momjian's avatar
Bruce Momjian committed
274 275
		buf[count--] = (value % 10) + '0';
		value /= 10;
276 277
	}

278 279
	strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
	count -= strlen(csymbol) - 1;
280 281 282 283 284 285 286

	if (buf[LAST_DIGIT] == ',')
		buf[LAST_DIGIT] = buf[LAST_PAREN];

	/* see if we need to signify negative amount */
	if (minus)
	{
287
		if (!PointerIsValid(result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol))))
288
			elog(ERROR, "Memory allocation failed, can't output cash");
289 290 291 292 293 294 295 296 297

		/* Position code of 0 means use parens */
		if (convention == 0)
			sprintf(result, "(%s)", buf + count);
		else if (convention == 2)
			sprintf(result, "%s%s", buf + count, nsymbol);
		else
			sprintf(result, "%s%s", nsymbol, buf + count);
	}
298
	else
299
	{
300
		if (!PointerIsValid(result = palloc(CASH_BUFSZ + 2 - count)))
301
			elog(ERROR, "Memory allocation failed, can't output cash");
Marc G. Fournier's avatar
Marc G. Fournier committed
302

303 304
		strcpy(result, buf + count);
	}
Marc G. Fournier's avatar
Marc G. Fournier committed
305

306
	return result;
307
}	/* cash_out() */
Marc G. Fournier's avatar
Marc G. Fournier committed
308 309


310
bool
311
cash_eq(Cash *c1, Cash *c2)
312
{
313
	if (!PointerIsValid(c1) || !PointerIsValid(c2))
314
		return FALSE;
Marc G. Fournier's avatar
Marc G. Fournier committed
315

316
	return *c1 == *c2;
317
}	/* cash_eq() */
318 319

bool
320
cash_ne(Cash *c1, Cash *c2)
321
{
322
	if (!PointerIsValid(c1) || !PointerIsValid(c2))
323
		return FALSE;
Marc G. Fournier's avatar
Marc G. Fournier committed
324

325
	return *c1 != *c2;
326
}	/* cash_ne() */
Marc G. Fournier's avatar
Marc G. Fournier committed
327

328
bool
329
cash_lt(Cash *c1, Cash *c2)
Marc G. Fournier's avatar
Marc G. Fournier committed
330
{
331
	if (!PointerIsValid(c1) || !PointerIsValid(c2))
332
		return FALSE;
Marc G. Fournier's avatar
Marc G. Fournier committed
333

334
	return *c1 < *c2;
335
}	/* cash_lt() */
Marc G. Fournier's avatar
Marc G. Fournier committed
336

337
bool
338
cash_le(Cash *c1, Cash *c2)
Marc G. Fournier's avatar
Marc G. Fournier committed
339
{
340
	if (!PointerIsValid(c1) || !PointerIsValid(c2))
341
		return FALSE;
Marc G. Fournier's avatar
Marc G. Fournier committed
342

343
	return *c1 <= *c2;
344
}	/* cash_le() */
Marc G. Fournier's avatar
Marc G. Fournier committed
345

346
bool
347
cash_gt(Cash *c1, Cash *c2)
348
{
349
	if (!PointerIsValid(c1) || !PointerIsValid(c2))
350
		return FALSE;
Marc G. Fournier's avatar
Marc G. Fournier committed
351

352
	return *c1 > *c2;
353
}	/* cash_gt() */
354 355

bool
356
cash_ge(Cash *c1, Cash *c2)
Marc G. Fournier's avatar
Marc G. Fournier committed
357
{
358
	if (!PointerIsValid(c1) || !PointerIsValid(c2))
359
		return FALSE;
Marc G. Fournier's avatar
Marc G. Fournier committed
360

361
	return *c1 >= *c2;
362
}	/* cash_ge() */
Marc G. Fournier's avatar
Marc G. Fournier committed
363 364


365 366 367
/* cash_pl()
 * Add two cash values.
 */
368
Cash *
369
cash_pl(Cash *c1, Cash *c2)
370
{
371
	Cash	   *result;
372

373
	if (!PointerIsValid(c1) || !PointerIsValid(c2))
374
		return NULL;
375

376
	if (!PointerIsValid(result = palloc(sizeof(Cash))))
377
		elog(ERROR, "Memory allocation failed, can't add cash");
378

379
	*result = (*c1 + *c2);
380

381
	return result;
382
}	/* cash_pl() */
383 384 385 386 387


/* cash_mi()
 * Subtract two cash values.
 */
388
Cash *
389
cash_mi(Cash *c1, Cash *c2)
390
{
391
	Cash	   *result;
392

393
	if (!PointerIsValid(c1) || !PointerIsValid(c2))
394
		return NULL;
Marc G. Fournier's avatar
Marc G. Fournier committed
395

396
	if (!PointerIsValid(result = palloc(sizeof(Cash))))
397
		elog(ERROR, "Memory allocation failed, can't subtract cash");
Marc G. Fournier's avatar
Marc G. Fournier committed
398

399
	*result = (*c1 - *c2);
Marc G. Fournier's avatar
Marc G. Fournier committed
400

401
	return result;
402
}	/* cash_mi() */
403 404


405 406
/* cash_mul_flt8()
 * Multiply cash by float8.
407
 */
408
Cash *
409
cash_mul_flt8(Cash *c, float8 *f)
410
{
411
	Cash	   *result;
412

413
	if (!PointerIsValid(f) || !PointerIsValid(c))
414
		return NULL;
415

416
	if (!PointerIsValid(result = palloc(sizeof(Cash))))
417
		elog(ERROR, "Memory allocation failed, can't multiply cash");
418

419
	*result = ((*f) * (*c));
420

421
	return result;
422
}	/* cash_mul_flt8() */
423 424


425 426 427
/* flt8_mul_cash()
 * Multiply float8 by cash.
 */
428
Cash *
429 430
flt8_mul_cash(float8 *f, Cash *c)
{
431
	return cash_mul_flt8(c, f);
432
}	/* flt8_mul_cash() */
433 434 435 436


/* cash_div_flt8()
 * Divide cash by float8.
437 438 439 440
 *
 * XXX Don't know if rounding or truncating is correct behavior.
 * Round for now. - tgl 97/04/15
 */
441
Cash *
442
cash_div_flt8(Cash *c, float8 *f)
443
{
444
	Cash	   *result;
445

446
	if (!PointerIsValid(f) || !PointerIsValid(c))
447
		return NULL;
448

449
	if (!PointerIsValid(result = palloc(sizeof(Cash))))
450
		elog(ERROR, "Memory allocation failed, can't divide cash");
451

452
	if (*f == 0.0)
453
		elog(ERROR, "cash_div:  divide by 0.0 error");
454

455
	*result = rint(*c / *f);
456

457
	return result;
458
}	/* cash_div_flt8() */
459 460 461 462

/* cash_mul_flt4()
 * Multiply cash by float4.
 */
463
Cash *
464 465 466 467 468
cash_mul_flt4(Cash *c, float4 *f)
{
	Cash	   *result;

	if (!PointerIsValid(f) || !PointerIsValid(c))
469
		return NULL;
470

471
	if (!PointerIsValid(result = palloc(sizeof(Cash))))
472
		elog(ERROR, "Memory allocation failed, can't multiply cash");
473 474 475

	*result = ((*f) * (*c));

476
	return result;
477
}	/* cash_mul_flt4() */
478 479 480 481 482


/* flt4_mul_cash()
 * Multiply float4 by float4.
 */
483
Cash *
484 485
flt4_mul_cash(float4 *f, Cash *c)
{
486
	return cash_mul_flt4(c, f);
487
}	/* flt4_mul_cash() */
488 489 490 491 492 493 494 495


/* cash_div_flt4()
 * Divide cash by float4.
 *
 * XXX Don't know if rounding or truncating is correct behavior.
 * Round for now. - tgl 97/04/15
 */
496
Cash *
497 498 499 500 501
cash_div_flt4(Cash *c, float4 *f)
{
	Cash	   *result;

	if (!PointerIsValid(f) || !PointerIsValid(c))
502
		return NULL;
503

504
	if (!PointerIsValid(result = palloc(sizeof(Cash))))
505
		elog(ERROR, "Memory allocation failed, can't divide cash");
506 507

	if (*f == 0.0)
508
		elog(ERROR, "cash_div:  divide by 0.0 error");
509 510 511

	*result = rint(*c / *f);

512
	return result;
513
}	/* cash_div_flt4() */
514 515 516 517 518


/* cash_mul_int4()
 * Multiply cash by int4.
 */
519
Cash *
520 521 522 523 524
cash_mul_int4(Cash *c, int4 i)
{
	Cash	   *result;

	if (!PointerIsValid(c))
525
		return NULL;
526

527
	if (!PointerIsValid(result = palloc(sizeof(Cash))))
528
		elog(ERROR, "Memory allocation failed, can't multiply cash");
529 530 531

	*result = ((i) * (*c));

532
	return result;
533
}	/* cash_mul_int4() */
534 535 536 537 538


/* int4_mul_cash()
 * Multiply int4 by cash.
 */
539
Cash *
540 541
int4_mul_cash(int4 i, Cash *c)
{
542
	return cash_mul_int4(c, i);
543
}	/* int4_mul_cash() */
544 545 546 547 548 549 550 551


/* cash_div_int4()
 * Divide cash by 4-byte integer.
 *
 * XXX Don't know if rounding or truncating is correct behavior.
 * Round for now. - tgl 97/04/15
 */
552
Cash *
553 554 555 556 557
cash_div_int4(Cash *c, int4 i)
{
	Cash	   *result;

	if (!PointerIsValid(c))
558
		return NULL;
559

560
	if (!PointerIsValid(result = palloc(sizeof(Cash))))
561
		elog(ERROR, "Memory allocation failed, can't divide cash");
562 563

	if (i == 0)
564
		elog(ERROR, "cash_idiv:  divide by 0 error");
565 566 567

	*result = rint(*c / i);

568
	return result;
569
}	/* cash_div_int4() */
570 571 572 573 574


/* cash_mul_int2()
 * Multiply cash by int2.
 */
575
Cash *
576 577 578 579 580
cash_mul_int2(Cash *c, int2 s)
{
	Cash	   *result;

	if (!PointerIsValid(c))
581
		return NULL;
582

583
	if (!PointerIsValid(result = palloc(sizeof(Cash))))
584
		elog(ERROR, "Memory allocation failed, can't multiply cash");
585 586 587

	*result = ((s) * (*c));

588
	return result;
589
}	/* cash_mul_int2() */
590 591 592 593 594


/* int2_mul_cash()
 * Multiply int2 by cash.
 */
595
Cash *
596 597
int2_mul_cash(int2 s, Cash *c)
{
598
	return cash_mul_int2(c, s);
599
}	/* int2_mul_cash() */
600 601 602 603 604 605 606 607


/* cash_div_int2()
 * Divide cash by int2.
 *
 * XXX Don't know if rounding or truncating is correct behavior.
 * Round for now. - tgl 97/04/15
 */
608
Cash *
609 610 611 612 613
cash_div_int2(Cash *c, int2 s)
{
	Cash	   *result;

	if (!PointerIsValid(c))
614
		return NULL;
615

616
	if (!PointerIsValid(result = palloc(sizeof(Cash))))
617
		elog(ERROR, "Memory allocation failed, can't divide cash");
618 619

	if (s == 0)
620
		elog(ERROR, "cash_div:  divide by 0 error");
621 622 623

	*result = rint(*c / s);

624
	return result;
625
}	/* cash_div_int2() */
626 627 628 629 630


/* cashlarger()
 * Return larger of two cash values.
 */
631
Cash *
632
cashlarger(Cash *c1, Cash *c2)
633
{
634
	Cash	   *result;
635

636
	if (!PointerIsValid(c1) || !PointerIsValid(c2))
637
		return NULL;
638

639
	if (!PointerIsValid(result = palloc(sizeof(Cash))))
640
		elog(ERROR, "Memory allocation failed, can't return larger cash");
641

642
	*result = ((*c1 > *c2) ? *c1 : *c2);
643

644
	return result;
645
}	/* cashlarger() */
646 647 648 649 650


/* cashsmaller()
 * Return smaller of two cash values.
 */
651
Cash *
652
cashsmaller(Cash *c1, Cash *c2)
653
{
654
	Cash	   *result;
655

656
	if (!PointerIsValid(c1) || !PointerIsValid(c2))
657
		return NULL;
658

659
	if (!PointerIsValid(result = palloc(sizeof(Cash))))
660
		elog(ERROR, "Memory allocation failed, can't return smaller cash");
661

662
	*result = ((*c1 < *c2) ? *c1 : *c2);
663

664
	return result;
665
}	/* cashsmaller() */
666 667 668


/* cash_words_out()
669
 * This converts a int4 as well but to a representation using words
670 671
 * Obviously way North American centric - sorry
 */
672
text *
673
cash_words_out(Cash *value)
674
{
675 676 677 678 679 680
	static char buf[128];
	char	   *p = buf;
	Cash		m0;
	Cash		m1;
	Cash		m2;
	Cash		m3;
Bruce Momjian's avatar
Bruce Momjian committed
681 682
	text	   *result;

683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718
	/* work with positive numbers */
	if (*value < 0)
	{
		*value *= -1;
		strcpy(buf, "minus ");
		p += 6;
	}
	else
		*buf = 0;

	m0 = *value % 100;			/* cents */
	m1 = (*value / 100) % 1000; /* hundreds */
	m2 = (*value / 100000) % 1000;		/* thousands */
	m3 = *value / 100000000 % 1000;		/* millions */

	if (m3)
	{
		strcat(buf, num_word(m3));
		strcat(buf, " million ");
	}

	if (m2)
	{
		strcat(buf, num_word(m2));
		strcat(buf, " thousand ");
	}

	if (m1)
		strcat(buf, num_word(m1));

	if (!*p)
		strcat(buf, "zero");

	strcat(buf, (int) (*value / 100) == 1 ? " dollar and " : " dollars and ");
	strcat(buf, num_word(m0));
	strcat(buf, m0 == 1 ? " cent" : " cents");
719 720

	/* capitalize output */
721
	*buf = toupper(*buf);
722 723 724 725 726 727 728

	/* make a text type for output */
	result = (text *) palloc(strlen(buf) + VARHDRSZ);
	VARSIZE(result) = strlen(buf) + VARHDRSZ;
	StrNCpy(VARDATA(result), buf, strlen(buf));

	return result;
729
}	/* cash_words_out() */
730 731 732 733 734 735 736 737 738


/*************************************************************************
 * Private routines
 ************************************************************************/

static const char *
num_word(Cash value)
{
739
	static char buf[128];
740 741 742 743 744 745
	static const char *small[] = {
		"zero", "one", "two", "three", "four", "five", "six", "seven",
		"eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
		"fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
		"thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety"
	};
746 747
	const char **big = small + 18;
	int			tu = value % 100;
748 749 750

	/* deal with the simple cases first */
	if (value <= 20)
751
		return small[value];
752 753 754 755 756

	/* is it an even multiple of 100? */
	if (!tu)
	{
		sprintf(buf, "%s hundred", small[value / 100]);
757
		return buf;
758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774
	}

	/* more than 99? */
	if (value > 99)
	{
		/* is it an even multiple of 10 other than 10? */
		if (value % 10 == 0 && tu > 10)
			sprintf(buf, "%s hundred %s",
					small[value / 100], big[tu / 10]);
		else if (tu < 20)
			sprintf(buf, "%s hundred and %s",
					small[value / 100], small[tu]);
		else
			sprintf(buf, "%s hundred %s %s",
					small[value / 100], big[tu / 10], small[tu % 10]);

	}
775
	else
776 777 778 779 780 781 782 783 784
	{
		/* is it an even multiple of 10 other than 10? */
		if (value % 10 == 0 && tu > 10)
			sprintf(buf, "%s", big[tu / 10]);
		else if (tu < 20)
			sprintf(buf, "%s", small[tu]);
		else
			sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);
	}
785

786
	return buf;
787
}	/* num_word() */