cash.c 16.8 KB
Newer Older
M
Marc G. Fournier 已提交
1
/*
2 3
 * cash.c
 * Written by D'Arcy J.M. Cain
4 5
 * darcy@druid.net
 * http://www.druid.net/darcy/
6 7
 *
 * Functions to allow input and output of money normally but store
8
 * and handle it as 64 bit ints
9 10 11
 *
 * A slightly modified version of this file and a discussion of the
 * workings can be found in the book "Software Solutions in C" by
12 13 14
 * Dale Schumacher, Academic Press, ISBN: 0-12-632360-7 except that
 * this version handles 64 bit numbers and so can hold values up to
 * $92,233,720,368,547,758.07.
15
 *
16
 * $PostgreSQL: pgsql/src/backend/utils/adt/cash.c,v 1.72 2007/08/21 03:14:36 tgl Exp $
17 18
 */

19 20
#include "postgres.h"

21 22
#include <limits.h>
#include <ctype.h>
B
Bruce Momjian 已提交
23
#include <math.h>
24 25
#include <locale.h>

T
Tom Lane 已提交
26
#include "libpq/pqformat.h"
27
#include "utils/cash.h"
28 29
#include "utils/pg_locale.h"

30
#define CASH_BUFSZ		36
31

32 33 34
#define TERMINATOR		(CASH_BUFSZ - 1)
#define LAST_PAREN		(TERMINATOR - 1)
#define LAST_DIGIT		(LAST_PAREN - 1)
35

36 37 38 39
/*
 * Cash is a pass-by-ref SQL type, so we must pass and return pointers.
 * These macros and support routine hide the pass-by-refness.
 */
40
#define PG_GETARG_CASH(n)  (* ((Cash *) PG_GETARG_POINTER(n)))
41 42
#define PG_RETURN_CASH(x)  return CashGetDatum(x)

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101


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

static const char *
num_word(Cash value)
{
	static char buf[128];
	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", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"
	};
	const char **big = small + 18;
	int			tu = value % 100;

	/* deal with the simple cases first */
	if (value <= 20)
		return small[value];

	/* is it an even multiple of 100? */
	if (!tu)
	{
		sprintf(buf, "%s hundred", small[value / 100]);
		return buf;
	}

	/* 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]);

	}
	else
	{
		/* 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]);
	}

	return buf;
}	/* num_word() */

102 103 104 105 106 107 108 109 110 111
static Datum
CashGetDatum(Cash value)
{
	Cash	   *result = (Cash *) palloc(sizeof(Cash));

	*result = value;
	return PointerGetDatum(result);
}


112 113 114 115
/* cash_in()
 * Convert a string to a cash data type.
 * Format is [$]###[,]###[.##]
 * Examples: 123.45 $123.45 $123,456.78
116
 *
117
 */
118 119
Datum
cash_in(PG_FUNCTION_ARGS)
120
{
121 122
	char	   *str = PG_GETARG_CSTRING(0);
	Cash		result;
123 124 125 126 127 128 129 130
	Cash		value = 0;
	Cash		dec = 0;
	Cash		sgn = 1;
	int			seen_dot = 0;
	const char *s = str;
	int			fpoint;
	char		dsymbol,
				ssymbol,
131 132 133
				psymbol;
	const char *nsymbol,
			   *csymbol;
B
Bruce Momjian 已提交
134

135
	struct lconv *lconvert = PGLC_localeconv();
B
Bruce Momjian 已提交
136

137
	/*
B
Bruce Momjian 已提交
138 139 140 141 142 143 144 145
	 * frac_digits will be CHAR_MAX in some locales, notably C.  However, just
	 * testing for == CHAR_MAX is risky, because of compilers like gcc that
	 * "helpfully" let you alter the platform-standard definition of whether
	 * char is signed or not.  If we are so unfortunate as to get compiled
	 * with a nonstandard -fsigned-char or -funsigned-char switch, then our
	 * idea of CHAR_MAX will not agree with libc's. The safest course is not
	 * to test for CHAR_MAX at all, but to impose a range check for plausible
	 * frac_digits values.
146 147 148 149
	 */
	fpoint = lconvert->frac_digits;
	if (fpoint < 0 || fpoint > 10)
		fpoint = 2;				/* best guess in this case, I think */
150

151 152 153 154 155
	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 : "-");
156

157
#ifdef CASHDEBUG
158 159
	printf("cashin- precision '%d'; decimal '%c'; thousands '%c'; currency '%s'; positive '%c'; negative '%s'\n",
		   fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
160 161
#endif

162
	/* we need to add all sorts of checking here.  For now just */
163
	/* strip all leading whitespace and any leading currency symbol */
164
	while (isspace((unsigned char) *s))
165 166 167
		s++;
	if (strncmp(s, csymbol, strlen(csymbol)) == 0)
		s += strlen(csymbol);
168 169

#ifdef CASHDEBUG
170
	printf("cashin- string is '%s'\n", s);
171
#endif
172

173 174
	/* a leading minus or paren signifies a negative number */
	/* again, better heuristics needed */
175
	/* XXX - doesn't properly check for balanced parens - djmc */
176
	if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
177 178 179 180
	{
		sgn = -1;
		s += strlen(nsymbol);
#ifdef CASHDEBUG
181
		printf("cashin- negative symbol; string is '%s'\n", s);
182 183 184
#endif
	}
	else if (*s == '(')
185 186 187
	{
		sgn = -1;
		s++;
188

189 190 191
	}
	else if (*s == psymbol)
		s++;
192

193
#ifdef CASHDEBUG
194
	printf("cashin- string is '%s'\n", s);
195 196
#endif

197
	while (isspace((unsigned char) *s))
198 199 200
		s++;
	if (strncmp(s, csymbol, strlen(csymbol)) == 0)
		s += strlen(csymbol);
201 202

#ifdef CASHDEBUG
203
	printf("cashin- string is '%s'\n", s);
204
#endif
205 206 207

	for (;; s++)
	{
208
		/* we look for digits as int8 as we have less */
209
		/* than the required number of decimal places */
210
		if (isdigit((unsigned char) *s) && dec < fpoint)
211 212 213 214 215 216 217
		{
			value = (value * 10) + *s - '0';

			if (seen_dot)
				dec++;

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

		}
224
		/* "thousands" separator? then skip... */
225 226 227 228 229 230 231
		else if (*s == ssymbol)
		{

		}
		else
		{
			/* round off */
232
			if (isdigit((unsigned char) *s) && *s >= '5')
233 234 235 236 237 238 239 240
				value++;

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

			break;
		}
241
	}
M
Marc G. Fournier 已提交
242

243 244 245
	/* should only be trailing digits followed by whitespace or right paren */
	while (isdigit((unsigned char) *s))
		s++;
246
	while (isspace((unsigned char) *s) || *s == ')')
247
		s++;
M
Marc G. Fournier 已提交
248

249
	if (*s != '\0')
250 251
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
B
Bruce Momjian 已提交
252
				 errmsg("invalid input syntax for type money: \"%s\"", str)));
M
Marc G. Fournier 已提交
253

254
	result = value * sgn;
M
Marc G. Fournier 已提交
255

256
#ifdef CASHDEBUG
257
	printf("cashin- result is %d\n", result);
258 259
#endif

260 261
	PG_RETURN_CASH(result);
}
M
Marc G. Fournier 已提交
262 263


264 265 266
/* cash_out()
 * Function to convert cash to a dollars and cents representation.
 * XXX HACK This code appears to assume US conventions for
267
 *	positive-valued amounts. - tgl 97/04/14
268
 */
269 270
Datum
cash_out(PG_FUNCTION_ARGS)
M
Marc G. Fournier 已提交
271
{
272
	Cash		value = PG_GETARG_CASH(0);
273 274 275 276 277 278
	char	   *result;
	char		buf[CASH_BUFSZ];
	int			minus = 0;
	int			count = LAST_DIGIT;
	int			point_pos;
	int			comma_position = 0;
279 280 281
	int			points,
				mon_group;
	char		comma;
282
	const char *csymbol,
283
			   *nsymbol;
284
	char		dsymbol;
285
	char		convention;
B
Bruce Momjian 已提交
286

287
	struct lconv *lconvert = PGLC_localeconv();
288

289 290 291 292 293 294
	/* see comments about frac_digits in cash_in() */
	points = lconvert->frac_digits;
	if (points < 0 || points > 10)
		points = 2;				/* best guess in this case, I think */

	/*
B
Bruce Momjian 已提交
295 296
	 * As with frac_digits, must apply a range check to mon_grouping to avoid
	 * being fooled by variant CHAR_MAX values.
297
	 */
298
	mon_group = *lconvert->mon_grouping;
299 300 301
	if (mon_group <= 0 || mon_group > 6)
		mon_group = 3;

302
	comma = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
303
	convention = lconvert->n_sign_posn;
304 305 306
	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 : "-");
307

308
	point_pos = LAST_DIGIT - points;
309

310 311 312 313 314 315 316 317
	/* 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 */
B
Bruce Momjian 已提交
318
	if (value < 0)
319 320
	{
		minus = 1;
321
		value = -value;
322
	}
323

324
	/* allow for trailing negative strings */
B
Bruce Momjian 已提交
325
	MemSet(buf, ' ', CASH_BUFSZ);
326 327
	buf[TERMINATOR] = buf[LAST_PAREN] = '\0';

B
Bruce Momjian 已提交
328
	while (value || count > (point_pos - 2))
329 330 331 332 333 334
	{
		if (points && count == point_pos)
			buf[count--] = dsymbol;
		else if (comma && count % (mon_group + 1) == comma_position)
			buf[count--] = comma;

335 336
		buf[count--] = ((uint64) value % 10) + '0';
		value = ((uint64) value) / 10;
337 338
	}

339 340
	strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
	count -= strlen(csymbol) - 1;
341 342 343 344 345 346 347

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

	/* see if we need to signify negative amount */
	if (minus)
	{
348
		result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol));
349 350 351 352 353 354 355 356 357

		/* 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);
	}
358
	else
359
	{
360
		result = palloc(CASH_BUFSZ + 2 - count);
361 362
		strcpy(result, buf + count);
	}
M
Marc G. Fournier 已提交
363

364 365
	PG_RETURN_CSTRING(result);
}
M
Marc G. Fournier 已提交
366

T
Tom Lane 已提交
367 368 369 370 371 372 373 374
/*
 *		cash_recv			- converts external binary format to cash
 */
Datum
cash_recv(PG_FUNCTION_ARGS)
{
	StringInfo	buf = (StringInfo) PG_GETARG_POINTER(0);

375
	PG_RETURN_CASH((Cash) pq_getmsgint64(buf));
T
Tom Lane 已提交
376 377 378 379 380 381 382 383 384 385 386 387
}

/*
 *		cash_send			- converts cash to binary format
 */
Datum
cash_send(PG_FUNCTION_ARGS)
{
	Cash		arg1 = PG_GETARG_CASH(0);
	StringInfoData buf;

	pq_begintypsend(&buf);
388
	pq_sendint64(&buf, arg1);
T
Tom Lane 已提交
389 390 391
	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}

392 393 394
/*
 * Comparison functions
 */
M
Marc G. Fournier 已提交
395

396 397
Datum
cash_eq(PG_FUNCTION_ARGS)
398
{
399 400
	Cash		c1 = PG_GETARG_CASH(0);
	Cash		c2 = PG_GETARG_CASH(1);
M
Marc G. Fournier 已提交
401

402 403
	PG_RETURN_BOOL(c1 == c2);
}
404

405 406
Datum
cash_ne(PG_FUNCTION_ARGS)
407
{
408 409
	Cash		c1 = PG_GETARG_CASH(0);
	Cash		c2 = PG_GETARG_CASH(1);
M
Marc G. Fournier 已提交
410

411 412
	PG_RETURN_BOOL(c1 != c2);
}
M
Marc G. Fournier 已提交
413

414 415
Datum
cash_lt(PG_FUNCTION_ARGS)
M
Marc G. Fournier 已提交
416
{
417 418
	Cash		c1 = PG_GETARG_CASH(0);
	Cash		c2 = PG_GETARG_CASH(1);
M
Marc G. Fournier 已提交
419

420 421
	PG_RETURN_BOOL(c1 < c2);
}
M
Marc G. Fournier 已提交
422

423 424
Datum
cash_le(PG_FUNCTION_ARGS)
M
Marc G. Fournier 已提交
425
{
426 427
	Cash		c1 = PG_GETARG_CASH(0);
	Cash		c2 = PG_GETARG_CASH(1);
M
Marc G. Fournier 已提交
428

429 430
	PG_RETURN_BOOL(c1 <= c2);
}
M
Marc G. Fournier 已提交
431

432 433
Datum
cash_gt(PG_FUNCTION_ARGS)
434
{
435 436
	Cash		c1 = PG_GETARG_CASH(0);
	Cash		c2 = PG_GETARG_CASH(1);
M
Marc G. Fournier 已提交
437

438 439
	PG_RETURN_BOOL(c1 > c2);
}
440

441 442
Datum
cash_ge(PG_FUNCTION_ARGS)
M
Marc G. Fournier 已提交
443
{
444 445
	Cash		c1 = PG_GETARG_CASH(0);
	Cash		c2 = PG_GETARG_CASH(1);
M
Marc G. Fournier 已提交
446

447 448
	PG_RETURN_BOOL(c1 >= c2);
}
M
Marc G. Fournier 已提交
449

450 451 452 453 454 455 456 457 458 459 460 461 462 463
Datum
cash_cmp(PG_FUNCTION_ARGS)
{
	Cash		c1 = PG_GETARG_CASH(0);
	Cash		c2 = PG_GETARG_CASH(1);

	if (c1 > c2)
		PG_RETURN_INT32(1);
	else if (c1 == c2)
		PG_RETURN_INT32(0);
	else
		PG_RETURN_INT32(-1);
}

M
Marc G. Fournier 已提交
464

465 466 467
/* cash_pl()
 * Add two cash values.
 */
468 469
Datum
cash_pl(PG_FUNCTION_ARGS)
470
{
471 472 473
	Cash		c1 = PG_GETARG_CASH(0);
	Cash		c2 = PG_GETARG_CASH(1);
	Cash		result;
474

475
	result = c1 + c2;
476

477 478
	PG_RETURN_CASH(result);
}
479 480 481 482 483


/* cash_mi()
 * Subtract two cash values.
 */
484 485
Datum
cash_mi(PG_FUNCTION_ARGS)
486
{
487 488 489
	Cash		c1 = PG_GETARG_CASH(0);
	Cash		c2 = PG_GETARG_CASH(1);
	Cash		result;
M
Marc G. Fournier 已提交
490

491
	result = c1 - c2;
M
Marc G. Fournier 已提交
492

493 494
	PG_RETURN_CASH(result);
}
495 496


497 498
/* cash_mul_flt8()
 * Multiply cash by float8.
499
 */
500 501
Datum
cash_mul_flt8(PG_FUNCTION_ARGS)
502
{
503 504 505
	Cash		c = PG_GETARG_CASH(0);
	float8		f = PG_GETARG_FLOAT8(1);
	Cash		result;
506

507 508 509
	result = c * f;
	PG_RETURN_CASH(result);
}
510 511


512 513 514
/* flt8_mul_cash()
 * Multiply float8 by cash.
 */
515 516
Datum
flt8_mul_cash(PG_FUNCTION_ARGS)
517
{
518 519 520 521 522 523 524
	float8		f = PG_GETARG_FLOAT8(0);
	Cash		c = PG_GETARG_CASH(1);
	Cash		result;

	result = f * c;
	PG_RETURN_CASH(result);
}
525 526 527 528


/* cash_div_flt8()
 * Divide cash by float8.
529
 */
530 531
Datum
cash_div_flt8(PG_FUNCTION_ARGS)
532
{
533 534 535
	Cash		c = PG_GETARG_CASH(0);
	float8		f = PG_GETARG_FLOAT8(1);
	Cash		result;
536

537
	if (f == 0.0)
538 539 540
		ereport(ERROR,
				(errcode(ERRCODE_DIVISION_BY_ZERO),
				 errmsg("division by zero")));
541

542 543 544
	result = rint(c / f);
	PG_RETURN_CASH(result);
}
545

546

547 548 549
/* cash_mul_flt4()
 * Multiply cash by float4.
 */
550 551
Datum
cash_mul_flt4(PG_FUNCTION_ARGS)
552
{
553 554 555
	Cash		c = PG_GETARG_CASH(0);
	float4		f = PG_GETARG_FLOAT4(1);
	Cash		result;
556

557 558 559
	result = c * f;
	PG_RETURN_CASH(result);
}
560 561 562


/* flt4_mul_cash()
563
 * Multiply float4 by cash.
564
 */
565 566
Datum
flt4_mul_cash(PG_FUNCTION_ARGS)
567
{
568 569 570 571 572 573 574
	float4		f = PG_GETARG_FLOAT4(0);
	Cash		c = PG_GETARG_CASH(1);
	Cash		result;

	result = f * c;
	PG_RETURN_CASH(result);
}
575 576 577 578 579 580


/* cash_div_flt4()
 * Divide cash by float4.
 *
 */
581 582
Datum
cash_div_flt4(PG_FUNCTION_ARGS)
583
{
584 585 586
	Cash		c = PG_GETARG_CASH(0);
	float4		f = PG_GETARG_FLOAT4(1);
	Cash		result;
587

588
	if (f == 0.0)
589 590 591
		ereport(ERROR,
				(errcode(ERRCODE_DIVISION_BY_ZERO),
				 errmsg("division by zero")));
592

593 594 595
	result = rint(c / f);
	PG_RETURN_CASH(result);
}
596 597


598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
/* cash_mul_int8()
 * Multiply cash by int8.
 */
Datum
cash_mul_int8(PG_FUNCTION_ARGS)
{
	Cash		c = PG_GETARG_CASH(0);
	int64		i = PG_GETARG_INT64(1);
	Cash		result;

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


/* int8_mul_cash()
 * Multiply int8 by cash.
 */
Datum
int8_mul_cash(PG_FUNCTION_ARGS)
{
	int64		i = PG_GETARG_INT64(0);
	Cash		c = PG_GETARG_CASH(1);
	Cash		result;

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

/* cash_div_int8()
 * Divide cash by 8-byte integer.
 */
Datum
cash_div_int8(PG_FUNCTION_ARGS)
{
	Cash		c = PG_GETARG_CASH(0);
	int64		i = PG_GETARG_INT64(1);
	Cash		result;

	if (i == 0)
		ereport(ERROR,
				(errcode(ERRCODE_DIVISION_BY_ZERO),
				 errmsg("division by zero")));

	result = rint(c / i);

	PG_RETURN_CASH(result);
}


648 649 650
/* cash_mul_int4()
 * Multiply cash by int4.
 */
651 652
Datum
cash_mul_int4(PG_FUNCTION_ARGS)
653
{
654
	Cash		c = PG_GETARG_CASH(0);
655
	int64		i = PG_GETARG_INT64(1);
656
	Cash		result;
657

658 659 660
	result = c * i;
	PG_RETURN_CASH(result);
}
661 662 663 664 665


/* int4_mul_cash()
 * Multiply int4 by cash.
 */
666 667
Datum
int4_mul_cash(PG_FUNCTION_ARGS)
668
{
669 670 671 672 673 674 675
	int32		i = PG_GETARG_INT32(0);
	Cash		c = PG_GETARG_CASH(1);
	Cash		result;

	result = i * c;
	PG_RETURN_CASH(result);
}
676 677 678 679 680 681


/* cash_div_int4()
 * Divide cash by 4-byte integer.
 *
 */
682 683
Datum
cash_div_int4(PG_FUNCTION_ARGS)
684
{
685
	Cash		c = PG_GETARG_CASH(0);
686
	int64		i = PG_GETARG_INT64(1);
687
	Cash		result;
688 689

	if (i == 0)
690 691 692
		ereport(ERROR,
				(errcode(ERRCODE_DIVISION_BY_ZERO),
				 errmsg("division by zero")));
693

694
	result = rint(c / i);
695

696 697
	PG_RETURN_CASH(result);
}
698 699 700 701 702


/* cash_mul_int2()
 * Multiply cash by int2.
 */
703 704
Datum
cash_mul_int2(PG_FUNCTION_ARGS)
705
{
706 707 708
	Cash		c = PG_GETARG_CASH(0);
	int16		s = PG_GETARG_INT16(1);
	Cash		result;
709

710 711 712
	result = c * s;
	PG_RETURN_CASH(result);
}
713 714 715 716

/* int2_mul_cash()
 * Multiply int2 by cash.
 */
717 718
Datum
int2_mul_cash(PG_FUNCTION_ARGS)
719
{
720 721 722
	int16		s = PG_GETARG_INT16(0);
	Cash		c = PG_GETARG_CASH(1);
	Cash		result;
723

724 725 726
	result = s * c;
	PG_RETURN_CASH(result);
}
727 728 729 730 731

/* cash_div_int2()
 * Divide cash by int2.
 *
 */
732 733
Datum
cash_div_int2(PG_FUNCTION_ARGS)
734
{
735 736 737
	Cash		c = PG_GETARG_CASH(0);
	int16		s = PG_GETARG_INT16(1);
	Cash		result;
738 739

	if (s == 0)
740 741 742
		ereport(ERROR,
				(errcode(ERRCODE_DIVISION_BY_ZERO),
				 errmsg("division by zero")));
743

744 745 746
	result = rint(c / s);
	PG_RETURN_CASH(result);
}
747 748 749 750

/* cashlarger()
 * Return larger of two cash values.
 */
751 752
Datum
cashlarger(PG_FUNCTION_ARGS)
753
{
754 755 756
	Cash		c1 = PG_GETARG_CASH(0);
	Cash		c2 = PG_GETARG_CASH(1);
	Cash		result;
757

758
	result = (c1 > c2) ? c1 : c2;
759

760 761
	PG_RETURN_CASH(result);
}
762 763 764 765

/* cashsmaller()
 * Return smaller of two cash values.
 */
766 767
Datum
cashsmaller(PG_FUNCTION_ARGS)
768
{
769 770 771
	Cash		c1 = PG_GETARG_CASH(0);
	Cash		c2 = PG_GETARG_CASH(1);
	Cash		result;
772

773
	result = (c1 < c2) ? c1 : c2;
774

775 776
	PG_RETURN_CASH(result);
}
777

778
/* cash_words()
779
 * This converts a int4 as well but to a representation using words
780 781
 * Obviously way North American centric - sorry
 */
782
Datum
783
cash_words(PG_FUNCTION_ARGS)
784
{
785
	Cash		value = PG_GETARG_CASH(0);
786
	uint64 val;
787
	char		buf[256];
788 789 790 791 792
	char	   *p = buf;
	Cash		m0;
	Cash		m1;
	Cash		m2;
	Cash		m3;
793 794 795
	Cash		m4;
	Cash		m5;
	Cash		m6;
B
Bruce Momjian 已提交
796 797
	text	   *result;

798
	/* work with positive numbers */
799
	if (value < 0)
800
	{
801
		value = -value;
802 803 804 805
		strcpy(buf, "minus ");
		p += 6;
	}
	else
806
		buf[0] = '\0';
807

808
	/* Now treat as unsigned, to avoid trouble at INT_MIN */
809
	val = (uint64) value;
810

811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835
	m0 = val % 100ll;				/* cents */
	m1 = (val / 100ll) % 1000;	/* hundreds */
	m2 = (val / 100000ll) % 1000; /* thousands */
	m3 = val / 100000000ll % 1000;	/* millions */
	m4 = val / 100000000000ll % 1000;	/* billions */
	m5 = val / 100000000000000ll % 1000;	/* trillions */
	m6 = val / 100000000000000000ll % 1000;	/* quadrillions */

	if (m6)
	{
		strcat(buf, num_word(m6));
		strcat(buf, " quadrillion ");
	}

	if (m5)
	{
		strcat(buf, num_word(m5));
		strcat(buf, " trillion ");
	}

	if (m4)
	{
		strcat(buf, num_word(m4));
		strcat(buf, " billion ");
	}
836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854

	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");

855
	strcat(buf, (val / 100) == 1 ? " dollar and " : " dollars and ");
856 857
	strcat(buf, num_word(m0));
	strcat(buf, m0 == 1 ? " cent" : " cents");
858 859

	/* capitalize output */
860
	buf[0] = pg_toupper((unsigned char) buf[0]);
861 862 863

	/* make a text type for output */
	result = (text *) palloc(strlen(buf) + VARHDRSZ);
864
	SET_VARSIZE(result, strlen(buf) + VARHDRSZ);
865
	memcpy(VARDATA(result), buf, strlen(buf));
866

867 868
	PG_RETURN_TEXT_P(result);
}