Files
calc/qio.c
Landon Curt Noll db77e29a23 convert ASCII TABs to ASCII SPACEs
Converted all ASCII tabs to ASCII spaces using a 8 character
tab stop, for all files, except for all Makefiles (plus rpm.mk).
The `git diff -w` reports no changes.
2024-07-11 22:03:52 -07:00

793 lines
25 KiB
C

/*
* qio - scanf and printf routines for arbitrary precision rational numbers
*
* Copyright (C) 1999-2007,2021-2023 David I. Bell
*
* Calc is open software; you can redistribute it and/or modify it under
* the terms of the version 2.1 of the GNU Lesser General Public License
* as published by the Free Software Foundation.
*
* Calc 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 Lesser General
* Public License for more details.
*
* A copy of version 2.1 of the GNU Lesser General Public License is
* distributed with calc under the filename COPYING-LGPL. You should have
* received a copy with calc; if not, write to Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Under source code control: 1993/07/30 19:42:46
* File existed as early as: 1993
*
* Share and enjoy! :-) http://www.isthe.com/chongo/tech/comp/calc/
*/
#include "qmath.h"
#include "config.h"
#include "args.h"
#include "have_unused.h"
#include "errtbl.h"
#include "banned.h" /* include after system header <> includes */
#define PUTCHAR(ch) math_chr(ch)
#define PUTSTR(str) math_str(str)
#define PRINTF1(fmt, a1) math_fmt(fmt, a1)
#define PRINTF2(fmt, a1, a2) math_fmt(fmt, a1, a2)
STATIC long scalefactor;
STATIC ZVALUE scalenumber = { 0, 0, 0 };
/*
* Print a formatted string containing arbitrary numbers, similar to printf.
* ALL numeric arguments to this routine are rational NUMBERs.
* Various forms of printing such numbers are supplied, in addition
* to strings and characters. Output can actually be to any FILE
* stream or a string.
*/
void
qprintf(char *fmt, ...)
{
va_list ap;
NUMBER *q;
int ch, sign = 1;
long width = 0, precision = 0;
int trigger = 0;
va_start(ap, fmt);
while ((ch = *fmt++) != '\0') {
if (trigger == 0) {
if (ch == '\\') {
ch = *fmt++;
switch (ch) {
case 'a': ch = '\a'; break;
case 'b': ch = '\b'; break;
case 'f': ch = '\f'; break;
case 'n': ch = '\n'; break;
case 'r': ch = '\r'; break;
case 't': ch = '\t'; break;
case 'v': ch = '\v'; break;
case 0:
va_end(ap);
return;
}
PUTCHAR(ch);
continue;
}
if (ch != '%') {
PUTCHAR(ch);
continue;
}
ch = *fmt++;
width = 0; precision = 8; sign = 1;
trigger = 1;
}
switch (ch) {
case 'd':
q = va_arg(ap, NUMBER *);
qprintfd(q, width);
break;
case 'f':
q = va_arg(ap, NUMBER *);
qprintff(q, width, precision);
break;
case 'e':
q = va_arg(ap, NUMBER *);
qprintfe(q, width, precision);
break;
case 'g':
q = va_arg(ap, NUMBER *);
/* XXX - we need a qprintfg function */
#if 0 /* XXX - we need a qprintfg() function */
qprintfg(q, width, precision);
#else /* XXX - use qprintfe until we have a qprintfg() function */
qprintfe(q, width, precision);
#endif /* XXX - we need a qprintfg() function */
break;
case 'r':
case 'R':
q = va_arg(ap, NUMBER *);
qprintfr(q, width, (bool) (ch == 'R'));
break;
case 'N':
q = va_arg(ap, NUMBER *);
zprintval(q->num, 0L, width);
break;
case 'D':
q = va_arg(ap, NUMBER *);
zprintval(q->den, 0L, width);
break;
case 'o':
q = va_arg(ap, NUMBER *);
qprintfo(q, width);
break;
case 'x':
q = va_arg(ap, NUMBER *);
qprintfx(q, width);
break;
case 'b':
q = va_arg(ap, NUMBER *);
qprintfb(q, width);
break;
case 's':
PUTSTR(va_arg(ap, char *));
break;
case 'c':
PUTCHAR(va_arg(ap, int));
break;
case 0:
va_end(ap);
return;
case '-':
sign = -1;
ch = *fmt++;
/*FALLTHRU*/
default:
if (('0' <= ch && ch <= '9') ||
ch == '.' || ch == '*') {
if (ch == '*') {
q = va_arg(ap, NUMBER *);
width = sign * qtoi(q);
ch = *fmt++;
} else if (ch != '.') {
width = ch - '0';
while ('0' <= (ch = *fmt++) &&
ch <= '9')
width = width * 10 + ch - '0';
width *= sign;
}
if (ch == '.') {
if ((ch = *fmt++) == '*') {
q = va_arg(ap, NUMBER *);
precision = qtoi(q);
ch = *fmt++;
} else {
precision = 0;
while ('0' <= (ch = *fmt++) &&
ch <= '9')
precision *= 10+ch-'0';
}
}
}
}
}
va_end(ap);
}
/*
* Print a number in the specified output mode.
* If MODE_DEFAULT is given, then the default output mode is used.
* Any approximate output is flagged with a leading tilde.
* Integers are always printed as themselves.
*/
void
qprintnum(NUMBER *q, int outmode, LEN outdigits)
{
NUMBER tmpval;
long prec, exp;
int outmode2 = MODE2_OFF;
if (outmode == MODE_DEFAULT) {
outmode = conf->outmode;
outmode2 = conf->outmode2;
}
switch (outmode) {
case MODE_INT:
if (conf->tilde_ok && qisfrac(q)) {
PUTCHAR('~');
if (conf->tilde_space && qisfrac(q)) {
PUTCHAR(' ');
}
}
qprintfd(q, 0L);
break;
case MODE_REAL:
prec = qdecplaces(q);
if ((prec < 0) || (prec > outdigits)) {
if (conf->tilde_ok) {
PUTCHAR('~');
if (conf->tilde_space) {
PUTCHAR(' ');
}
}
}
if (conf->fullzero || (prec < 0) ||
(prec > outdigits))
prec = outdigits;
qprintff(q, 0L, prec);
break;
case MODE_FRAC:
qprintfr(q, 0L, false);
break;
case MODE_EXP:
if (qiszero(q)) {
PUTCHAR('0');
return;
}
tmpval = *q;
tmpval.num.sign = 0;
exp = qilog10(&tmpval);
if (exp == 0) { /* in range to output as real */
qprintnum(q, MODE_REAL, outdigits);
return;
}
tmpval.num = _one_;
tmpval.den = _one_;
if (exp > 0)
ztenpow(exp, &tmpval.den);
else
ztenpow(-exp, &tmpval.num);
q = qmul(q, &tmpval);
zfree(tmpval.num);
zfree(tmpval.den);
qprintnum(q, MODE_REAL, outdigits);
qfree(q);
PRINTF1("e%ld", exp);
break;
case MODE_ENG:
if (qiszero(q)) {
PUTCHAR('0');
return;
}
tmpval = *q;
tmpval.num.sign = 0;
exp = qilog10(&tmpval);
if (exp == 0) { /* in range to output as real */
qprintnum(q, MODE_REAL, outdigits);
return;
}
tmpval.num = _one_;
tmpval.den = _one_;
if (exp > 0) {
exp -= exp % 3;
ztenpow(exp, &tmpval.den);
} else {
long remainder = exp % 3;
if (remainder)
exp -= remainder + 3;
ztenpow(-exp, &tmpval.num);
}
q = qmul(q, &tmpval);
zfree(tmpval.num);
zfree(tmpval.den);
qprintnum(q, MODE_REAL, outdigits);
qfree(q);
if (exp) PRINTF1("e%ld", exp);
break;
case MODE_REAL_AUTO:
{
const int P = conf->outdigits ? conf->outdigits : 1;
tmpval = *q;
tmpval.num.sign = 0;
exp = qilog10(&tmpval);
if (P > exp && exp >= -P) {
qprintnum(q, MODE_REAL, P - 1 - exp);
} else {
qprintnum(q, MODE_EXP, P - 1);
}
break;
}
case MODE_HEX:
qprintfx(q, 0L);
break;
case MODE_OCTAL:
qprintfo(q, 0L);
break;
case MODE_BINARY:
qprintfb(q, 0L);
break;
default:
math_error("Bad mode for print");
not_reached();
}
if (outmode2 != MODE2_OFF) {
PUTSTR(" /* ");
qprintnum(q, outmode2, outdigits);
PUTSTR(" */");
}
}
/*
* Print a number in floating point representation.
* Example: 193.784
*/
void
qprintff(NUMBER *q, long width, long precision)
{
ZVALUE z, z1;
if (precision != scalefactor) {
if (scalenumber.v)
zfree(scalenumber);
ztenpow(precision, &scalenumber);
scalefactor = precision;
}
if (scalenumber.v)
zmul(q->num, scalenumber, &z);
else
z = q->num;
if (qisfrac(q)) {
zquo(z, q->den, &z1, conf->outround);
if (z.v != q->num.v)
zfree(z);
z = z1;
}
if (qisneg(q) && ziszero(z))
PUTCHAR('-');
zprintval(z, precision, width);
if (z.v != q->num.v)
zfree(z);
}
/*
* Print a number in exponential notation.
* Example: 4.1856e34
*/
/*ARGSUSED*/
void
qprintfe(NUMBER *q, long UNUSED(width), long precision)
{
long exponent;
NUMBER q2;
ZVALUE num, zden, tenpow, tmp;
if (qiszero(q)) {
PUTSTR("0.0");
return;
}
num = q->num;
zden = q->den;
num.sign = 0;
exponent = zdigits(num) - zdigits(zden);
if (exponent > 0) {
ztenpow(exponent, &tenpow);
zmul(zden, tenpow, &tmp);
zfree(tenpow);
zden = tmp;
}
if (exponent < 0) {
ztenpow(-exponent, &tenpow);
zmul(num, tenpow, &tmp);
zfree(tenpow);
num = tmp;
}
if (zrel(num, zden) < 0) {
zmuli(num, 10L, &tmp);
if (num.v != q->num.v)
zfree(num);
num = tmp;
exponent--;
}
q2.num = num;
q2.den = zden;
q2.num.sign = q->num.sign;
qprintff(&q2, 0L, precision);
if (exponent)
PRINTF1("e%ld", exponent);
if (num.v != q->num.v)
zfree(num);
if (zden.v != q->den.v)
zfree(zden);
}
/*
* Print a number in rational representation.
* Example: 397/37
*/
void
qprintfr(NUMBER *q, long width, bool force)
{
zprintval(q->num, 0L, width);
if (force || qisfrac(q)) {
if (conf->fraction_space) {
PUTCHAR(' ');
}
PUTCHAR('/');
if (conf->fraction_space) {
PUTCHAR(' ');
}
zprintval(q->den, 0L, width);
}
}
/*
* Print a number as an integer (truncating fractional part).
* Example: 958421
*/
void
qprintfd(NUMBER *q, long width)
{
ZVALUE z;
if (qisfrac(q)) {
zquo(q->num, q->den, &z, conf->outround);
zprintval(z, 0L, width);
zfree(z);
} else {
zprintval(q->num, 0L, width);
}
}
/*
* Print a number in hex.
* This prints the numerator and denominator in hex.
*/
void
qprintfx(NUMBER *q, long width)
{
zprintx(q->num, width);
if (qisfrac(q)) {
if (conf->fraction_space) {
PUTCHAR(' ');
}
PUTCHAR('/');
if (conf->fraction_space) {
PUTCHAR(' ');
}
zprintx(q->den, 0L);
}
}
/*
* Print a number in binary.
* This prints the numerator and denominator in binary.
*/
void
qprintfb(NUMBER *q, long width)
{
zprintb(q->num, width);
if (qisfrac(q)) {
if (conf->fraction_space) {
PUTCHAR(' ');
}
PUTCHAR('/');
if (conf->fraction_space) {
PUTCHAR(' ');
}
zprintb(q->den, 0L);
}
}
/*
* Print a number in octal.
* This prints the numerator and denominator in octal.
*/
void
qprintfo(NUMBER *q, long width)
{
zprinto(q->num, width);
if (qisfrac(q)) {
if (conf->fraction_space) {
PUTCHAR(' ');
}
PUTCHAR('/');
if (conf->fraction_space) {
PUTCHAR(' ');
}
zprinto(q->den, 0L);
}
}
/*
* Convert a string to a number in rational, floating point,
* exponential notation, hex, or octal.
* q = str2q(string);
*/
NUMBER *
str2q(char *s)
{
register NUMBER *q;
register char *t;
ZVALUE div, newnum, newden, tmp;
long decimals, exp;
bool hex, negexp;
q = qalloc();
decimals = 0;
exp = 0;
negexp = false;
hex = false;
t = s;
if ((*t == '+') || (*t == '-'))
t++;
if ((*t == '0') && ((t[1] == 'x') || (t[1] == 'X'))) {
hex = true;
t += 2;
}
while (((*t >= '0') && (*t <= '9')) || (hex &&
(((*t >= 'a') && (*t <= 'f')) || ((*t >= 'A') && (*t <= 'F')))))
t++;
if (*t == '/') {
t++;
str2z(t, &q->den);
} else if ((*t == '.') || (*t == 'e') || (*t == 'E')) {
if (*t == '.') {
t++;
while ((*t >= '0') && (*t <= '9')) {
t++;
decimals++;
}
}
/*
* Parse exponent if any
*/
if ((*t == 'e') || (*t == 'E')) {
t++;
if (*t == '+')
t++;
else if (*t == '-') {
negexp = true;
t++;
}
while ((*t >= '0') && (*t <= '9')) {
exp = (exp * 10) + *t++ - '0';
if (exp > (MAXLONG/10L)) {
math_error("Exponent too large");
not_reached();
}
}
}
ztenpow(decimals, &q->den);
}
str2z(s, &q->num);
if (qiszero(q)) {
qfree(q);
return qlink(&_qzero_);
}
/*
* Apply the exponential if any
*/
if (exp) {
ztenpow(exp, &tmp);
if (negexp) {
zmul(q->den, tmp, &newden);
zfree(q->den);
q->den = newden;
} else {
zmul(q->num, tmp, &newnum);
zfree(q->num);
q->num = newnum;
}
zfree(tmp);
}
/*
* Reduce the fraction to lowest terms
*/
if (!zisunit(q->num) && !zisunit(q->den)) {
zgcd(q->num, q->den, &div);
if (!zisunit(div)) {
zequo(q->num, div, &newnum);
zfree(q->num);
zequo(q->den, div, &newden);
zfree(q->den);
q->num = newnum;
q->den = newden;
}
zfree(div);
}
return q;
}
/*
* Parse a number in any of the various legal forms, and return the count
* of characters that are part of a legal number. Numbers can be either a
* decimal integer, possibly two decimal integers separated with a slash, a
* floating point or exponential number, a hex number beginning with "0x",
* a binary number beginning with "0b", or an octal number beginning with "0".
* The flags argument modifies the end of number testing for ease in handling
* fractions or complex numbers. Minus one is returned if the number format
* is definitely illegal.
*/
long
qparse(char *cp, int flags)
{
char *oldcp;
oldcp = cp;
if ((*cp == '+') || (*cp == '-'))
cp++;
if ((*cp == '+') || (*cp == '-'))
return -1;
/* hex */
if ((*cp == '0') && ((cp[1] == 'x') || (cp[1] == 'X'))) {
cp += 2;
while (((*cp >= '0') && (*cp <= '9')) ||
((*cp >= 'a') && (*cp <= 'f')) ||
((*cp >= 'A') && (*cp <= 'F')))
cp++;
if (((*cp == 'i') || (*cp == 'I')) && (flags & QPF_IMAG))
cp++;
if ((*cp == '.') || ((*cp == '/') && (flags & QPF_SLASH)) ||
((*cp >= '0') && (*cp <= '9')) ||
((*cp >= 'a') && (*cp <= 'z')) ||
((*cp >= 'A') && (*cp <= 'Z')))
return -1;
return (cp - oldcp);
}
/* binary */
if ((*cp == '0') && ((cp[1] == 'b') || (cp[1] == 'B'))) {
cp += 2;
while ((*cp == '0') || (*cp == '1'))
cp++;
if (((*cp == 'i') || (*cp == 'I')) && (flags & QPF_IMAG))
cp++;
if ((*cp == '.') || ((*cp == '/') && (flags & QPF_SLASH)) ||
((*cp >= '0') && (*cp <= '9')) ||
((*cp >= 'a') && (*cp <= 'z')) ||
((*cp >= 'A') && (*cp <= 'Z')))
return -1;
return (cp - oldcp);
}
/* octal */
if ((*cp == '0') && (cp[1] >= '0') && (cp[1] <= '9')) {
while ((*cp >= '0') && (*cp <= '7'))
cp++;
if (((*cp == 'i') || (*cp == 'I')) && (flags & QPF_IMAG))
cp++;
if ((*cp == '.') || ((*cp == '/') && (flags & QPF_SLASH)) ||
((*cp >= '0') && (*cp <= '9')) ||
((*cp >= 'a') && (*cp <= 'z')) ||
((*cp >= 'A') && (*cp <= 'Z')))
return -1;
return (cp - oldcp);
}
/*
* Number is decimal but can still be a fraction or real or exponential
*/
while ((*cp >= '0') && (*cp <= '9'))
cp++;
if (*cp == '/' && flags & QPF_SLASH) { /* fraction */
cp++;
while ((*cp >= '0') && (*cp <= '9'))
cp++;
if (((*cp == 'i') || (*cp == 'I')) && (flags & QPF_IMAG))
cp++;
if ((*cp == '.') || ((*cp == '/') && (flags & QPF_SLASH)) ||
((*cp >= '0') && (*cp <= '9')) ||
((*cp >= 'a') && (*cp <= 'z')) ||
((*cp >= 'A') && (*cp <= 'Z')))
return -1;
return (cp - oldcp);
}
if (*cp == '.') { /* floating point */
cp++;
while ((*cp >= '0') && (*cp <= '9'))
cp++;
}
if ((*cp == 'e') || (*cp == 'E')) { /* exponential */
cp++;
if ((*cp == '+') || (*cp == '-'))
cp++;
if ((*cp == '+') || (*cp == '-'))
return -1;
while ((*cp >= '0') && (*cp <= '9'))
cp++;
}
if (((*cp == 'i') || (*cp == 'I')) && (flags & QPF_IMAG))
cp++;
if ((*cp == '.') || ((*cp == '/') && (flags & QPF_SLASH)) ||
((*cp >= '0') && (*cp <= '9')) ||
((*cp >= 'a') && (*cp <= 'z')) ||
((*cp >= 'A') && (*cp <= 'Z')))
return -1;
return (cp - oldcp);
}
/*
* Print an integer which is guaranteed to fit in the specified number
* of columns, using embedded '...' characters if numerator and/or
* denominator is too large.
*/
void
fitprint(NUMBER *q, long width)
{
long numdigits, dendigits, digits;
long width1, width2;
long n, k;
if (width < 8)
width = 8;
numdigits = zdigits(q->num);
n = numdigits;
k = 0;
while (++k, n)
n /= 10;
if (qisint(q)) {
width -= k;
k = 16 - k;
if (k < 2)
k = 2;
PRINTF1("(%ld)", numdigits);
while (k-- > 0)
PUTCHAR(' ');
fitzprint(q->num, numdigits, width);
return;
}
dendigits = zdigits(q->den);
PRINTF2("(%ld/%ld)", numdigits, dendigits);
digits = numdigits + dendigits;
n = dendigits;
while (++k, n)
n /= 10;
width -= k;
k = 16 - k;
if (k < 2)
k = 2;
while (k-- > 0)
PUTCHAR(' ');
if (digits <= width) {
qprintf("%r", q);
return;
}
width1 = (width * numdigits)/digits;
if (width1 < 8)
width1 = 8;
width2 = width - width1;
if (width2 < 8) {
width2 = 8;
width1 = width - width2;
}
fitzprint(q->num, numdigits, width1);
if (conf->fraction_space) {
PUTCHAR(' ');
}
PUTCHAR('/');
if (conf->fraction_space) {
PUTCHAR(' ');
}
fitzprint(q->den, dendigits, width2);
}