/*
 * zio - scanf and printf routines for arbitrary precision integers
 *
 * Copyright (C) 1999-2007,2021-2023,2026  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:48
 * File existed as early as:    1993
 *
 * Share and enjoy!  :-)        http://www.isthe.com/chongo/tech/comp/calc/
 */

/*
 * important <system> header includes
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>

/*
 * calc local src includes
 */
#include "zmath.h"
#include "qmath.h"
#include "config.h"
#include "attribute.h"
#include "errtbl.h"

#include "banned.h" /* include after all other includes */

#define OUTBUFSIZE 200 /* realloc size for output buffers */

#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)
#define PRINTF3(fmt, a1, a2, a3) math_fmt(fmt, a1, a2, a3)
#define PRINTF4(fmt, a1, a2, a3, a4) math_fmt(fmt, a1, a2, a3, a4)

/*
 * Output state that has been saved when diversions are done.
 */
typedef struct iostate IOSTATE;
struct iostate {
    IOSTATE *oldiostates; /* previous saved state */
    long outdigits;       /* digits for output */
    int outmode;          /* output mode */
    int outmode2;         /* secondary output mode */
    FILE *outfp;          /* file unit for output (if any) */
    char *outbuf;         /* output string buffer (if any) */
    size_t outbufsize;    /* current size of string buffer */
    size_t outbufused;    /* space used in string buffer */
    bool outputisstring;  /* true if output is to string buffer */
};

static IOSTATE *oldiostates = NULL; /* list of saved output states */
static FILE *outfp = NULL;          /* file unit for output */
static char *outbuf = NULL;         /* current diverted buffer */
static bool outputisstring = false;
static size_t outbufsize;
static size_t outbufused;

/*
 * zio_init - perform needed initialization work
 *
 * On some systems, one cannot initialize a pointer to a FILE *.
 * This routine, called once at startup is a work-a-round for
 * systems with such bogons.
 */
void
zio_init(void)
{
    static int done = 0; /* 1 => routine already called */

    if (!done) {
        outfp = stdout;
        done = 1;
    }
}

/*
 * Routine to output a character either to a FILE
 * handle or into a string.
 */
void
math_chr(int ch)
{
    char *cp;

    if (!outputisstring) {
        fputc(ch, outfp);
        return;
    }
    if (outbufused >= outbufsize) {
        cp = (char *)realloc(outbuf, outbufsize + OUTBUFSIZE + 1);
        if (cp == NULL) {
            math_error("Cannot realloc output string");
            not_reached();
        }
        outbuf = cp;
        outbufsize += OUTBUFSIZE;
    }
    outbuf[outbufused++] = (char)ch;
}

/*
 * Routine to output a null-terminated string either
 * to a FILE handle or into a string.
 */
void
math_str(char *str)
{
    char *cp;
    size_t len;

    /* firewall */
    if (str == NULL) {
        math_error("%s: str NULL", __func__);
        not_reached();
    }

    if (!outputisstring) {
        fputs(str, outfp);
        return;
    }
    len = strlen(str);
    if ((outbufused + len) > outbufsize) {
        cp = (char *)realloc(outbuf, outbufsize + len + OUTBUFSIZE + 1);
        if (cp == NULL) {
            math_error("Cannot realloc output string");
            not_reached();
        }
        outbuf = cp;
        outbufsize += (len + OUTBUFSIZE);
    }
    memcpy(&outbuf[outbufused], str, len);
    outbufused += len;
}

/*
 * Output a null-terminated string either to a FILE handle or into a string,
 * padded with spaces as needed so as to fit within the specified width.
 * If width is positive, the spaces are added at the front of the string.
 * If width is negative, the spaces are added at the end of the string.
 * The complete string is always output, even if this overflows the width.
 * No characters within the string are handled specially.
 */
void
math_fill(char *str, long width)
{
    /* firewall */
    if (str == NULL) {
        math_error("%s: str NULL", __func__);
        not_reached();
    }

    if (width > 0) {
        width -= (long)strlen(str);
        while (width-- > 0) {
            PUTCHAR(' ');
        }
        PUTSTR(str);
    } else {
        width += (long)strlen(str);
        PUTSTR(str);
        while (width++ < 0) {
            PUTCHAR(' ');
        }
    }
}

/*
 * Routine to output a printf-style formatted string either
 * to a FILE handle or into a string.
 */
void
math_fmt(char *fmt, ...)
{
    va_list ap;
    char buf[BUFSIZ + 1];

    /* firewall */
    if (fmt == NULL) {
        math_error("%s: fmt NULL", __func__);
        not_reached();
    }

    va_start(ap, fmt);
    vsnprintf(buf, BUFSIZ, fmt, ap);
    va_end(ap);
    buf[BUFSIZ] = '\0'; /* paranoia */
    math_str(buf);
}

/*
 * Flush the current output stream.
 */
void
math_flush(void)
{
    if (!outputisstring) {
        fflush(outfp);
    }
}

/*
 * Divert further output so that it is saved into a string that will be
 * returned later when the diversion is completed.  The current state of
 * output is remembered for later restoration.  Diversions can be nested.
 * Output diversion is only intended for saving output to "stdout".
 */
void
math_divertio(void)
{
    register IOSTATE *sp;

    sp = (IOSTATE *)calloc(1, sizeof(IOSTATE));
    if (sp == NULL) {
        math_error("No memory for diverting output");
        not_reached();
    }
    sp->oldiostates = oldiostates;
    sp->outdigits = conf->outdigits;
    sp->outmode = conf->outmode;
    sp->outmode2 = conf->outmode2;
    sp->outfp = outfp;
    sp->outbuf = outbuf;
    sp->outbufsize = outbufsize;
    sp->outbufused = outbufused;
    sp->outputisstring = outputisstring;

    outbufused = 0;
    outbufsize = 0;
    outbuf = (char *)calloc(OUTBUFSIZE + 1, 1);
    if (outbuf == NULL) {
        math_error("Cannot allocate divert string");
        not_reached();
    }
    outbufsize = OUTBUFSIZE;
    outputisstring = true;
    oldiostates = sp;
}

/*
 * Un-divert output and return the saved output as a string.  This also
 * restores the output state to what it was before the diversion began.
 * The string needs freeing by the caller when it is no longer needed.
 */
char *
math_getdivertedio(void)
{
    register IOSTATE *sp;
    char *cp;

    sp = oldiostates;
    if (sp == NULL) {
        math_error("No diverted state to restore");
        not_reached();
    }
    cp = outbuf;
    cp[outbufused] = '\0';
    oldiostates = sp->oldiostates;
    conf->outdigits = sp->outdigits;
    conf->outmode = sp->outmode;
    conf->outmode2 = sp->outmode2;
    outfp = sp->outfp;
    outbuf = sp->outbuf;
    outbufsize = sp->outbufsize;
    outbufused = sp->outbufused;
    outbuf = sp->outbuf;
    outputisstring = sp->outputisstring;
    free(sp);
    return cp;
}

/*
 * Clear all diversions and set output back to the original destination.
 * This is called when resetting the global state of the program.
 */
void
math_cleardiversions(void)
{
    while (oldiostates) {
        free(math_getdivertedio());
    }
}

/*
 * Set the output routines to output to the specified FILE stream.
 * This interacts with output diversion in the following manner.
 *      STDOUT  diversion       action
 *      ----    ---------       ------
 *      yes     yes             set output to diversion string again.
 *      yes     no              set output to stdout.
 *      no      yes             set output to specified file.
 *      no      no              set output to specified file.
 */
void
math_setfp(FILE *newfp)
{
    /* firewall */
    if (newfp == NULL) {
        math_error("%s: newfp NULL", __func__);
        not_reached();
    }

    outfp = newfp;
    outputisstring = (oldiostates && (newfp == stdout));
}

/*
 * Set the output mode for numeric output.
 * This also returns the previous mode.
 */
int
math_setmode(int newmode)
{
    int oldmode;

    if ((newmode <= MODE_DEFAULT) || (newmode > MODE_MAX)) {
        math_error("Setting illegal output mode");
        not_reached();
    }
    oldmode = conf->outmode;
    conf->outmode = newmode;
    return oldmode;
}

/*
 * Set the secondary output mode for numeric output.
 * This also returns the previous mode.
 */
int
math_setmode2(int newmode)
{
    int oldmode;

    if (newmode != MODE2_OFF && ((newmode <= MODE_DEFAULT) || (newmode > MODE_MAX))) {
        math_error("Setting illegal secondary output mode");
        not_reached();
    }
    oldmode = conf->outmode2;
    conf->outmode2 = newmode;
    return oldmode;
}

/*
 * Set the number of digits for float or exponential output.
 * This also returns the previous number of digits.
 */
LEN
math_setdigits(LEN newdigits)
{
    LEN olddigits;

    if (newdigits < 0) {
        math_error("Setting illegal number of digits");
        not_reached();
    }
    olddigits = conf->outdigits;
    conf->outdigits = newdigits;
    return olddigits;
}

/*
 * Print an integer value as a hex number.
 * Width is the number of columns to print the number in, including the
 * sign if required.  If zero, no extra output is done.  If positive,
 * leading spaces are typed if necessary. If negative, trailing spaces are
 * typed if necessary.  The special characters 0x appear to indicate the
 * number is hex.
 */
/*ARGSUSED*/
void
zprintx(ZVALUE z, long width)
{
    register HALF *hp; /* current word to print */
    int len;           /* number of halfwords to type */
    char *str;

    if (width) {
        math_divertio();
        zprintx(z, 0L);
        str = math_getdivertedio();
        math_fill(str, width);
        free(str);
        return;
    }
    len = z.len - 1;
    if (zisneg(z)) {
        PUTCHAR('-');
    }
    if ((len == 0) && (*z.v <= (HALF)9)) {
        len = '0' + (int)(*z.v);
        PUTCHAR(len & 0xff);
        return;
    }
    hp = z.v + len;
    //  PRINTF1("0x%lx", (PRINT)*hp--);
    PRINTF1("0x%" PRI_HEX, (PRINT)*hp--);
    while (--len >= 0) {
        //      PRINTF1("%08" PRI_HEX, (PRINT)*hp--);
        PRINTF1("%0" PRI_HEX_LEN PRI_HEX, (PRINT)*hp--);
    }
}

/*
 * Print an integer value as a binary number.
 * The special characters 0b appear to indicate the number is binary.
 */
/*ARGSUSED*/
void
zprintb(ZVALUE z, long width)
{
    register HALF *hp; /* current word to print */
    int len;           /* number of halfwords to type */
    HALF val;          /* current value */
    HALF mask;         /* current mask */
    int didprint;      /* nonzero if printed some digits */
    int ch;            /* current char */
    char *str;

    if (width) {
        math_divertio();
        zprintb(z, 0L);
        str = math_getdivertedio();
        math_fill(str, width);
        free(str);
        return;
    }
    len = z.len - 1;
    if (zisneg(z)) {
        PUTCHAR('-');
    }
    if ((len == 0) && (*z.v <= (FULL)1)) {
        len = '0' + (int)(*z.v);
        PUTCHAR(len & 0xff);
        return;
    }
    hp = z.v + len;
    didprint = 0;
    PUTSTR("0b");
    while (len-- >= 0) {
        val = ((len >= 0) ? *hp-- : *hp);
        mask = ((HALF)1 << (BASEB - 1));
        while (mask) {
            ch = '0' + ((mask & val) != 0);
            if (didprint || (ch != '0')) {
                PUTCHAR(ch & 0xff);
                didprint = 1;
            }
            mask >>= 1;
        }
    }
}

/*
 * Print an integer value as an octal number.
 * The number begins with a leading 0 to indicate that it is octal.
 */
/*ARGSUSED*/
void
zprinto(ZVALUE z, long width)
{
    register HALF *hp;   /* current word to print */
    int len;             /* number of halfwords to type */
#if BASEB == 32          /* Yes, the larger base needs a smaller type! */
    HALF num1 = '0';     /* numbers to type */
    HALF num2 = (HALF)0; /* numbers to type */
    HALF num3;           /* numbers to type */
    HALF num4;           /* numbers to type */
#elif BASEB == 16
    FULL num1 = '0';     /* numbers to type */
    FULL num2 = (FULL)0; /* numbers to type */
#  error "BASEB is not 32 nor 16, do not yet know what variables needed by the zprinto function"
#endif
    int rem; /* remainder number of halfwords */
    char *str;

    if (width) {
        math_divertio();
        zprinto(z, 0L);
        str = math_getdivertedio();
        math_fill(str, width);
        free(str);
        return;
    }
    if (zisneg(z)) {
        PUTCHAR('-');
    }
    len = z.len;
    if ((len == 1) && (*z.v <= (FULL)7)) {
        num1 = '0' + (int)(*z.v);
        PUTCHAR((int)(num1 & 0xff));
        return;
    }
    hp = z.v + len - 1;
    rem = len % 3;
#if BASEB == 32
    switch (rem) { /* handle odd amounts first */
    case 0:
        num1 = ((hp[0]) >> 8);
        num2 = (((hp[0] & 0xff) << 16) + (hp[-1] >> 16));
        num3 = (((hp[-1] & 0xffff) << 8) + (hp[-2] >> 24));
        num4 = (hp[-2] & 0xffffff);
        if (num1) {
            //          PRINTF4("0%lo%08lo%08lo%08lo", (PRINT)num1, (PRINT)num2, (PRINT)num3, (PRINT)num4);
            PRINTF4("0%" PRI_OCT "%0" PRI_OCT_LEN PRI_OCT "%0" PRI_OCT_LEN PRI_OCT "%0" PRI_OCT_LEN PRI_OCT, (PRINT)num1,
                    (PRINT)num2, (PRINT)num3, (PRINT)num4);
        } else {
            //          PRINTF3("0%lo%08lo%08lo", (PRINT)num2, (PRINT)num3, (PRINT)num4);
            PRINTF3("0%" PRI_OCT "%0" PRI_OCT_LEN PRI_OCT "%0" PRI_OCT_LEN PRI_OCT, (PRINT)num2, (PRINT)num3, (PRINT)num4);
        }
        rem = 3;
        break;
    case 1:
        //      PRINTF1("0%lo", (PRINT)hp[0]);
        PRINTF1("0%" PRI_OCT, (PRINT)hp[0]);
        break;
    case 2:
        num1 = ((hp[0]) >> 16);
        num2 = (((hp[0] & 0xffff) << 8) + (hp[-1] >> 24));
        num3 = (hp[-1] & 0xffffff);
        if (num1) {
            //          PRINTF3("0%lo%08lo%08lo", (PRINT)num1, (PRINT)num2, (PRINT)num3);
            PRINTF3("0%" PRI_OCT "%0" PRI_OCT_LEN PRI_OCT "%0" PRI_OCT_LEN PRI_OCT, (PRINT)num1, (PRINT)num2, (PRINT)num3);
        } else {
            //          PRINTF2("0%lo%08lo", (PRINT)num2, (PRINT)num3);
            PRINTF2("0%" PRI_OCT "%0" PRI_OCT_LEN PRI_OCT, (PRINT)num2, (PRINT)num3);
        }
        break;
    }
    len -= rem;
    if (len > 0) {
        hp -= rem;
        while (len > 0) { /* finish in groups of 3 words */
                          //          PRINTF4("%08lo%08lo%08lo%08lo",
                          //                  (PRINT)((hp[0]) >> 8), (PRINT)(((hp[0] & 0xff) << 16) + (hp[-1] >> 16)),
                          //                  (PRINT)(((hp[-1] & 0xffff) << 8) + (hp[-2] >> 24)), (PRINT)(hp[-2] & 0xffffff));
            PRINTF4("0%" PRI_OCT "%0" PRI_OCT_LEN PRI_OCT "%0" PRI_OCT_LEN PRI_OCT "%0" PRI_OCT_LEN PRI_OCT, (PRINT)((hp[0]) >> 8),
                    (PRINT)(((hp[0] & 0xff) << 16) + (hp[-1] >> 16)), (PRINT)(((hp[-1] & 0xffff) << 8) + (hp[-2] >> 24)),
                    (PRINT)(hp[-2] & 0xffffff));
            hp -= 3;
            len -= 3;
        }
    }

#elif BASEB == 16

    switch (rem) { /* handle odd amounts first */
    case 0:
        num1 = ((((FULL)hp[0]) << 8) + (((FULL)hp[-1]) >> 8));
        num2 = ((((FULL)(hp[-1] & 0xff)) << 16) + ((FULL)hp[-2]));
        rem = 3;
        break;
    case 1:
        num1 = 0;
        num2 = (FULL)hp[0];
        break;
    case 2:
        num1 = (((FULL)hp[0]) >> 8);
        num2 = ((((FULL)(hp[0] & 0xff)) << 16) + ((FULL)hp[-1]));
        break;
    }
    if (num1) {
        //      PRINTF2("0%lo%08lo", num1, num2);
        PRINTF2("0%" PRI_OCT "%0" PRI_OCT_LEN PRI_OCT, num1, num2);
    } else {
        //      PRINTF1("0%lo", num2);
        PRINTF1("0%" PRI_OCT, , num2);
    }
    len -= rem;
    if (len > 0) {
        hp -= rem;
        while (len > 0) { /* finish in groups of 3 halfwords */
                          //          PRINTF2("%08lo%08lo",
                          //                  ((((FULL)hp[0]) << 8) + (((FULL)hp[-1]) >> 8)),
                          //                  ((((FULL)(hp[-1] & 0xff)) << 16) + ((FULL)hp[-2])));
            PRINTF2("%0" PRI_OCT_LEN PRI_OCT "%0" PRI_OCT_LEN PRI_OCT, ((((PRINT)hp[0]) << 8) + (((PRINT)hp[-1]) >> 8)),
                    ((((PRINT)(hp[-1] & 0xff)) << 16) + ((PRINT)hp[-2])));
            hp -= 3;
            len -= 3;
        }
    }

#else

#  error "BASEB is not 32 nor 16, do not yet know how print in the zprinto function"

#endif
}

/*
 * Print a decimal integer to the terminal.
 * This works by dividing the number by 10^2^N for some N, and
 * then doing this recursively on the quotient and remainder.
 * Decimals supplies number of decimal places to print, with a decimal
 * point at the right location, with zero meaning no decimal point.
 * Width is the number of columns to print the number in, including the
 * decimal point and sign if required.  If zero, no extra output is done.
 * If positive, leading spaces are typed if necessary. If negative, trailing
 * spaces are typed if necessary.  As examples of the effects of these values,
 * (345,0,0) = "345", (345,2,0) = "3.45", (345,5,8) = "  .00345".
 *
 * given:
 *      z               number to be printed
 *      decimals        number of decimal places
 *      width           number of columns to print in
 */
void
zprintval(ZVALUE z, long decimals, long width)
{
    int depth;            /* maximum depth */
    int n;                /* current index into array */
    long i;               /* number to print */
    long leadspaces;      /* number of leading spaces to print */
    long putpoint;        /* digits until print decimal point */
    long digits;          /* number of digits of raw number */
    bool output;          /* true if have output something */
    bool neg;             /* true if negative */
    ZVALUE quo, rem;      /* quotient and remainder */
    ZVALUE leftnums[32];  /* left parts of the number */
    ZVALUE rightnums[32]; /* right parts of the number */

    if (decimals < 0) {
        decimals = 0;
    }
    if (width < 0) {
        width = 0;
    }
    neg = (z.sign != 0);

    leadspaces = width - neg - (decimals > 0);
    z.sign = 0;
    /*
     * Find the 2^N power of ten which is greater than or equal
     * to the number, calculating it the first time if necessary.
     */
    _tenpowers_[0] = _ten_;
    depth = 0;
    while ((_tenpowers_[depth].len < z.len) || (zrel(_tenpowers_[depth], z) <= 0)) {
        depth++;
        if (_tenpowers_[depth].len == 0) {
            if (depth <= TEN_MAX) {
                zsquare(_tenpowers_[depth - 1], &_tenpowers_[depth]);
            } else {
                math_error("cannot compute 10^2^(TEN_MAX+1)");
                not_reached();
            }
        }
    }
    /*
     * Divide by smaller 2^N powers of ten until the parts are small
     * enough to output.  This algorithm walks through a binary tree
     * where each node is a piece of the number to print, and such that
     * we visit left nodes first.  We do the needed recursion in line.
     */
    digits = 1;
    output = false;
    n = 0;
    putpoint = 0;
    rightnums[0].len = 0;
    leftnums[0] = z;
    for (;;) {
        while (n < depth) {
            i = depth - n - 1;
            zdiv(leftnums[n], _tenpowers_[i], &quo, &rem, 0);
            if (!ziszero(quo)) {
                digits += (1L << i);
            }
            n++;
            leftnums[n] = quo;
            rightnums[n] = rem;
        }
        i = (long)(leftnums[n].v[0]);
        if (output || i || (n == 0)) {
            if (!output) {
                output = true;
                if (decimals < digits) {
                    leadspaces -= digits;
                } else {
                    leadspaces -= decimals + conf->leadzero;
                }
                while (--leadspaces >= 0) {
                    PUTCHAR(' ');
                }
                if (neg) {
                    PUTCHAR('-');
                }
                if (decimals) {
                    putpoint = (digits - decimals);
                    if (putpoint <= 0) {
                        if (conf->leadzero) {
                            PUTCHAR('0');
                        }
                        PUTCHAR('.');
                        while (++putpoint <= 0) {
                            PUTCHAR('0');
                        }
                        putpoint = 0;
                    }
                }
            }
            i += '0';
            PUTCHAR((int)(i & 0xff));
            if (--putpoint == 0) {
                PUTCHAR('.');
            }
        }
        while (rightnums[n].len == 0) {
            if (n <= 0) {
                return;
            }
            if (leftnums[n].len) {
                zfree(leftnums[n]);
            }
            n--;
        }
        zfree(leftnums[n]);
        leftnums[n] = rightnums[n];
        rightnums[n].len = 0;
    }
}

/*
 * Read an integer value in decimal, hex, octal, or binary.
 * Hex numbers are indicated by a leading "0x", binary with a leading "0b",
 * and octal by a leading "0".  Periods are skipped over, but any other
 * extraneous character stops the scan.
 */
void
str2z(char *s, ZVALUE *res)
{
    ZVALUE z, ztmp, digit;
    HALF digval;
    bool minus;
    long shift;

    /* firewall */
    if (s == NULL) {
        math_error("%s: s NULL", __func__);
        not_reached();
    }
    if (res == NULL) {
        math_error("%s: res NULL", __func__);
        not_reached();
    }

    minus = false;
    shift = 0;
    if (*s == '+') {
        s++;
    } else if (*s == '-') {
        minus = true;
        s++;
    }
    if (*s == '0') { /* possibly hex, octal, or binary */
        s++;
        if ((*s >= '0') && (*s <= '7')) {
            shift = 3;
        } else if ((*s == 'x') || (*s == 'X')) {
            shift = 4;
            s++;
        } else if ((*s == 'b') || (*s == 'B')) {
            shift = 1;
            s++;
        }
    }
    digit.v = &digval;
    digit.len = 1;
    digit.sign = 0;
    z = _zero_;
    while (*s) {
        digval = *s++;
        if ((digval >= '0') && (digval <= '9')) {
            digval -= '0';
        } else if ((digval >= 'a') && (digval <= 'f') && shift) {
            digval -= ('a' - 10);
        } else if ((digval >= 'A') && (digval <= 'F') && shift) {
            digval -= ('A' - 10);
        } else if (digval == '.') {
            continue;
        } else {
            break;
        }
        if (shift) {
            zshift(z, shift, &ztmp);
        } else {
            zmuli(z, 10L, &ztmp);
        }
        zfree(z);
        zadd(ztmp, digit, &z);
        zfree(ztmp);
    }
    ztrim(&z);
    if (minus && !ziszero(z)) {
        z.sign = 1;
    }
    *res = z;
}

void
fitzprint(ZVALUE z, long digits, long show)
{
    ZVALUE ztmp1, ztmp2;
    long i;

    if (digits <= show) {
        zprintval(z, 0, 0);
        return;
    }
    show /= 2;
    ztenpow(digits - show, &ztmp1);
    (void)zquo(z, ztmp1, &ztmp2, 1);
    zprintval(ztmp2, 0, 0);
    zfree(ztmp1);
    zfree(ztmp2);
    printf("...");
    ztenpow(show, &ztmp1);
    (void)zmod(z, ztmp1, &ztmp2, 0);
    i = zdigits(ztmp2);
    while (i++ < show) {
        printf("0");
    }
    zprintval(ztmp2, 0, 0);
    zfree(ztmp1);
    zfree(ztmp2);
}

/* END CODE */
