home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Fresh Fish 5
/
FreshFish_July-August1994.bin
/
bbs
/
gnu
/
dc-0.2-src.lha
/
src
/
amiga
/
dc-0.2
/
decimal.c
< prev
next >
Wrap
C/C++ Source or Header
|
1993-05-21
|
30KB
|
1,236 lines
/*
* Arbitrary precision decimal arithmetic.
*
* Copyright (C) 1984 Free Software Foundation, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can either send email to this
* program's author (see below) or write to: The Free Software Foundation,
* Inc.; 675 Mass Ave. Cambridge, MA 02139, USA.
*/
/* Some known problems:
Another problem with decimal_div is found when you try to
divide a number with > scale fraction digits by 1. The
expected result is simply truncation, but all sorts of things
happen instead. An example is that the result of .99999998/1
with scale set to 6 is .000001
There are some problems in the behavior of the decimal package
related to printing and parsing. The
printer is weird about very large output radices, tending to want
to output single ASCII characters for any and all digits (even
in radices > 127). The UNIX bc approach is to print digit groups
separated by spaces. There is a rather overwrought workaround in
the function decputc() in bcmisc.c, but it would be better if
decimal.c got a fix for this. */
/* For stand-alone testing, compile with -DTEST.
This DTESTable feature defines a `main' function
which is a simple loop that accepts input of the form
number space op space number newline
where op is +, -, *, /, %, p or r,
and performs the operation and prints the operands and result.
`p' means print the first number in the radix spec'd by the second.
`r' means read the first one in the radix specified by the second
(and print the result in decimal).
Divide in this test keeps three fraction digits. */
#include "decimal.h"
#define MAX(a, b) (((a) > (b) ? (a) : (b)))
/* Some constant decimal numbers */
struct decimal decimal_zero = {0, 0, 0, 0, 0};
struct decimal decimal_one = {0, 0, 1, 0, 1};
/*** Assumes RADIX is even ***/
struct decimal decimal_half = {0, 1, 0, 0, RADIX / 2};
decimal static decimal_add1 (), decimal_sub1 ();
static void add_scaled ();
static int subtract_scaled ();
/* Create and return a decimal number that has `before' digits before
the decimal point and `after' digits after. The digits themselves are
initialized to zero. */
decimal
make_decimal (before, after)
int before, after;
{
decimal result;
if (before >= 1<<16)
{
decimal_error ("%d too many decimal digits", before);
return 0;
}
if (after >= 1<<15)
{
decimal_error ("%d too many decimal digits", after);
return 0;
}
result = (decimal) malloc (sizeof (struct decimal) + before + after - 1);
result->sign = 0;
result->before = before;
result->after = after;
result->refcnt = 0;
bzero (result->contents, before + after);
return result;
}
/* Create a copy of the decimal number `b' and return it. */
decimal
decimal_copy (b)
decimal b;
{
decimal result = make_decimal (b->before, b->after);
bcopy (b->contents, result->contents, LENGTH(b));
result->sign = b->sign;
return result;
}
/* Copy a decimal number `b' but extend or truncate to exactly
`digits' fraction digits. */
static decimal
decimal_copy_1 (b, digits)
decimal b;
int digits;
{
if (digits > b->after)
{
decimal result = make_decimal (b->before, digits);
bcopy (b->contents, result->contents + (digits - (int) b->after), LENGTH(b));
return result;
}
else
return decimal_round_digits (b, digits);
}
/* flush specified number `digits' of trailing fraction digits,
and flush any trailing fraction zero digits exposed after they are gone.
The number `b' is actually modified; no new storage is allocated.
That is why this is not global. */
static void
flush_trailing_digits (b, digits)
decimal b;
int digits;
{
int flush = digits;
int maxdig = b->after;
while (flush < maxdig && !b->contents [flush])
flush++;
if (flush)
{
int i;
b->after -= flush;
for (i = 0; i < LENGTH (b); i++)
b->contents[i] = b->contents[flush + i];
}
}
/* Return nonzero integer if the value of decimal number `b' is zero. */
int
decimal_zerop (b)
decimal b;
{
return !LENGTH(b);
}
/* Compare two decimal numbers arithmetically.
The value is < 0 if b1 < b2, > 0 if b1 > b2, 0 if b1 = b2.
This is the same way that `strcmp' reports the result of comparing
strings. */
int
decimal_compare (b1, b2)
decimal b1, b2;
{
int l1, l2;
char *p1, *p2, *s1, *s2;
int i;
/* If signs differ, deduce result from the signs */
if (b2->sign && !b1->sign) return 1;
if (b1->sign && !b2->sign) return -1;
/* If same sign but number of nonfraction digits differs,
the one with more of them is farther from zero. */
if (b1->before != b2->before)
if (b1->sign)
return (int) (b2->before - b1->before);
else
return (int) (b1->before - b2->before);
/* Else compare the numbers digit by digit from high end */
l1 = LENGTH(b1);
l2 = LENGTH(b2);
s1 = b1->contents; /* Start of number -- don't back up digit pointer past here */
s2 = b2->contents;
p1 = b1->contents + l1; /* Scanning pointer, for fetching digits. */
p2 = b2->contents + l2;
for (i = MAX(l1, l2); i >= 0; i--)
{
int r = ((p1 != s1) ? *--p1 : 0) - ((p2 != s2) ? *--p2 : 0);
if (r)
return b1->sign ? -r : r;
}
return 0;
}
/* Return the number of digits stored in decimal number `b' */
int
decimal_length (b)
decimal b;
{
return LENGTH(b);
}
/* Return the number of fraction digits stored in decimal number `b'. */
int
decimal_after (b)
decimal b;
{
return b->after;
}
/* Round decimal number `b' to have only `digits' fraction digits.
Result is rounded to nearest unit in the last remaining digit.
Return the result, another decimal number. */
decimal
decimal_round_digits (b, digits)
decimal b;
int digits;
{
decimal result;
int old;
if (b->after <= digits) return decimal_copy (b);
if (digits < 0)
{
decimal_error ("request to keep negative number of digits %d", digits);
return decimal_copy (b);
}
result = make_decimal (b->before + 1, b->after);
result->sign = b->sign;
bcopy (b->contents, result->contents, LENGTH(b));
old = result->after;
/* Add .5 * last place to keep, so that we round rather than truncate */
/* Note this ignores sign of result, so if result is negative
it is subtracting */
add_scaled (result, DECIMAL_HALF, 1, old - digits - 1);
/* Flush desired digits, and any trailing zeros exposed by them. */
flush_trailing_digits (result, old - digits);
/* Flush leading digits -- always is one, unless was a carry into it */
while (result->before > 0
&& result->contents[LENGTH(result) - 1] == 0)
result->before--;
return result;
}
/* Truncate decimal number `b' to have only `digits' fraction digits.
Any fraction digits in `b' beyond that are dropped and ignored.
Truncation is toward zero.
Return the result, another decimal number. */
decimal
decimal_trunc_digits (b, digits)
decimal b;
int digits;
{
decimal result = decimal_copy (b);
int old = result->after;
if (old <= digits) return result;
if (digits < 0)
{
decimal_error ("request to keep negative number of digits %d", digits);
return result;
}
flush_trailing_digits (result, old - digits);
return result;
}
/* Return the fractional part of decimal number `b':
that is, `b' - decimal_trunc_digits (`b') */
decimal
decimal_fraction (b)
decimal b;
{
decimal result = make_decimal (0, b->after);
bcopy (b->contents, result->contents, b->after);
return result;
}
/* return an integ