Blame printf/doprntf.c

Packit 5c3484
/* __gmp_doprnt_mpf -- mpf formatted output.
Packit 5c3484
Packit 5c3484
   THE FUNCTIONS IN THIS FILE ARE FOR INTERNAL USE ONLY.  THEY'RE ALMOST
Packit 5c3484
   CERTAIN TO BE SUBJECT TO INCOMPATIBLE CHANGES OR DISAPPEAR COMPLETELY IN
Packit 5c3484
   FUTURE GNU MP RELEASES.
Packit 5c3484
Packit 5c3484
Copyright 2001, 2002, 2011 Free Software Foundation, Inc.
Packit 5c3484
Packit 5c3484
This file is part of the GNU MP Library.
Packit 5c3484
Packit 5c3484
The GNU MP Library is free software; you can redistribute it and/or modify
Packit 5c3484
it under the terms of either:
Packit 5c3484
Packit 5c3484
  * the GNU Lesser General Public License as published by the Free
Packit 5c3484
    Software Foundation; either version 3 of the License, or (at your
Packit 5c3484
    option) any later version.
Packit 5c3484
Packit 5c3484
or
Packit 5c3484
Packit 5c3484
  * the GNU General Public License as published by the Free Software
Packit 5c3484
    Foundation; either version 2 of the License, or (at your option) any
Packit 5c3484
    later version.
Packit 5c3484
Packit 5c3484
or both in parallel, as here.
Packit 5c3484
Packit 5c3484
The GNU MP Library is distributed in the hope that it will be useful, but
Packit 5c3484
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
Packit 5c3484
or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
Packit 5c3484
for more details.
Packit 5c3484
Packit 5c3484
You should have received copies of the GNU General Public License and the
Packit 5c3484
GNU Lesser General Public License along with the GNU MP Library.  If not,
Packit 5c3484
see https://www.gnu.org/licenses/.  */
Packit 5c3484
Packit 5c3484
#include <stdarg.h>    /* for va_list and hence doprnt_funs_t */
Packit 5c3484
#include <ctype.h>
Packit 5c3484
#include <string.h>
Packit 5c3484
#include <stdio.h>
Packit 5c3484
#include <stdlib.h>
Packit 5c3484
Packit 5c3484
#include "gmp.h"
Packit 5c3484
#include "gmp-impl.h"
Packit 5c3484
#include "longlong.h"
Packit 5c3484
Packit 5c3484
Packit 5c3484
/* change this to "#define TRACE(x) x" for diagnostics */
Packit 5c3484
#define TRACE(x)
Packit 5c3484
Packit 5c3484
Packit 5c3484
/* The separate of __gmp_doprnt_float_digits and __gmp_doprnt_float is so
Packit 5c3484
   some C++ can do the mpf_get_str and release it in case of an exception */
Packit 5c3484
Packit 5c3484
#define DIGIT_VALUE(c)                  \
Packit 5c3484
  (isdigit (c)   ? (c) - '0'            \
Packit 5c3484
   : islower (c) ? (c) - 'a' + 10       \
Packit 5c3484
   :               (c) - 'A' + 10)
Packit 5c3484
Packit 5c3484
int
Packit 5c3484
__gmp_doprnt_mpf (const struct doprnt_funs_t *funs,
Packit 5c3484
		  void *data,
Packit 5c3484
		  const struct doprnt_params_t *p,
Packit 5c3484
		  const char *point,
Packit 5c3484
		  mpf_srcptr f)
Packit 5c3484
{
Packit 5c3484
  int         prec, ndigits, free_size, len, newlen, justify, justlen, explen;
Packit 5c3484
  int         showbaselen, sign, signlen, intlen, intzeros, pointlen;
Packit 5c3484
  int         fraczeros, fraclen, preczeros;
Packit 5c3484
  char        *s, *free_ptr;
Packit 5c3484
  mp_exp_t    exp;
Packit 5c3484
  char        exponent[GMP_LIMB_BITS + 10];
Packit 5c3484
  const char  *showbase;
Packit 5c3484
  int         retval = 0;
Packit 5c3484
Packit 5c3484
  TRACE (printf ("__gmp_doprnt_float\n");
Packit 5c3484
	 printf ("  conv=%d prec=%d\n", p->conv, p->prec));
Packit 5c3484
Packit 5c3484
  prec = p->prec;
Packit 5c3484
  if (prec <= -1)
Packit 5c3484
    {
Packit 5c3484
      /* all digits */
Packit 5c3484
      ndigits = 0;
Packit 5c3484
Packit 5c3484
      /* arrange the fixed/scientific decision on a "prec" implied by how
Packit 5c3484
	 many significant digits there are */
Packit 5c3484
      if (p->conv == DOPRNT_CONV_GENERAL)
Packit 5c3484
	MPF_SIGNIFICANT_DIGITS (prec, PREC(f), ABS(p->base));
Packit 5c3484
    }
Packit 5c3484
  else
Packit 5c3484
    {
Packit 5c3484
      switch (p->conv) {
Packit 5c3484
      case DOPRNT_CONV_FIXED:
Packit 5c3484
	/* Precision is digits after the radix point.  Try not to generate
Packit 5c3484
	   too many more than will actually be required.  If f>=1 then
Packit 5c3484
	   overestimate the integer part, and add prec.  If f<1 then
Packit 5c3484
	   underestimate the zeros between the radix point and the first
Packit 5c3484
	   digit and subtract that from prec.  In either case add 2 so the
Packit 5c3484
	   round to nearest can be applied accurately.  Finally, we add 1 to
Packit 5c3484
	   handle the case of 1-eps where EXP(f) = 0 but mpf_get_str returns
Packit 5c3484
	   exp as 1.  */
Packit 5c3484
	ndigits = prec + 2 + 1
Packit 5c3484
	  + EXP(f) * (mp_bases[ABS(p->base)].chars_per_limb + (EXP(f)>=0));
Packit 5c3484
	ndigits = MAX (ndigits, 1);
Packit 5c3484
	break;
Packit 5c3484
Packit 5c3484
      case DOPRNT_CONV_SCIENTIFIC:
Packit 5c3484
	/* precision is digits after the radix point, and there's one digit
Packit 5c3484
	   before */
Packit 5c3484
	ndigits = prec + 1;
Packit 5c3484
	break;
Packit 5c3484
Packit 5c3484
      default:
Packit 5c3484
	ASSERT (0);
Packit 5c3484
	/*FALLTHRU*/
Packit 5c3484
Packit 5c3484
      case DOPRNT_CONV_GENERAL:
Packit 5c3484
	/* precision is total digits, but be sure to ask mpf_get_str for at
Packit 5c3484
	   least 1, not 0 */
Packit 5c3484
	ndigits = MAX (prec, 1);
Packit 5c3484
	break;
Packit 5c3484
      }
Packit 5c3484
    }
Packit 5c3484
  TRACE (printf ("  ndigits %d\n", ndigits));
Packit 5c3484
Packit 5c3484
  s = mpf_get_str (NULL, &exp, p->base, ndigits, f);
Packit 5c3484
  len = strlen (s);
Packit 5c3484
  free_ptr = s;
Packit 5c3484
  free_size = len + 1;
Packit 5c3484
  TRACE (printf ("  s   %s\n", s);
Packit 5c3484
	 printf ("  exp %ld\n", exp);
Packit 5c3484
	 printf ("  len %d\n", len));
Packit 5c3484
Packit 5c3484
  /* For fixed mode check the ndigits formed above was in fact enough for
Packit 5c3484
     the integer part plus p->prec after the radix point. */
Packit 5c3484
  ASSERT ((p->conv == DOPRNT_CONV_FIXED && p->prec > -1)
Packit 5c3484
	  ? ndigits >= MAX (1, exp + p->prec + 2) : 1);
Packit 5c3484
Packit 5c3484
  sign = p->sign;
Packit 5c3484
  if (s[0] == '-')
Packit 5c3484
    {
Packit 5c3484
      sign = s[0];
Packit 5c3484
      s++, len--;
Packit 5c3484
    }
Packit 5c3484
  signlen = (sign != '\0');
Packit 5c3484
  TRACE (printf ("  sign %c  signlen %d\n", sign, signlen));
Packit 5c3484
Packit 5c3484
  switch (p->conv) {
Packit 5c3484
  case DOPRNT_CONV_FIXED:
Packit 5c3484
    if (prec <= -1)
Packit 5c3484
      prec = MAX (0, len-exp);   /* retain all digits */
Packit 5c3484
Packit 5c3484
    /* Truncate if necessary so fraction will be at most prec digits. */
Packit 5c3484
    ASSERT (prec >= 0);
Packit 5c3484
    newlen = exp + prec;
Packit 5c3484
    if (newlen < 0)
Packit 5c3484
      {
Packit 5c3484
	/* first non-zero digit is below target prec, and at least one zero
Packit 5c3484
	   digit in between, so print zero */
Packit 5c3484
	len = 0;
Packit 5c3484
	exp = 0;
Packit 5c3484
      }
Packit 5c3484
    else if (len <= newlen)
Packit 5c3484
      {
Packit 5c3484
	/* already got few enough digits */
Packit 5c3484
      }
Packit 5c3484
    else
Packit 5c3484
      {
Packit 5c3484
	/* discard excess digits and round to nearest */
Packit 5c3484
Packit 5c3484
	const char  *num_to_text = (p->base >= 0
Packit 5c3484
				    ? "0123456789abcdefghijklmnopqrstuvwxyz"
Packit 5c3484
				    : "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
Packit 5c3484
	int  base = ABS(p->base);
Packit 5c3484
	int  n;
Packit 5c3484
Packit 5c3484
	ASSERT (base <= 36);
Packit 5c3484
Packit 5c3484
	len = newlen;
Packit 5c3484
	n = DIGIT_VALUE (s[len]);
Packit 5c3484
	TRACE (printf ("  rounding with %d\n", n));
Packit 5c3484
	if (n >= (base + 1) / 2)
Packit 5c3484
	  {
Packit 5c3484
	    /* propagate a carry */
Packit 5c3484
	    for (;;)
Packit 5c3484
	      {
Packit 5c3484
		if (len == 0)
Packit 5c3484
		  {
Packit 5c3484
		    s[0] = '1';
Packit 5c3484
		    len = 1;
Packit 5c3484
		    exp++;
Packit 5c3484
		    break;
Packit 5c3484
		  }
Packit 5c3484
		n = DIGIT_VALUE (s[len-1]);
Packit 5c3484
		ASSERT (n >= 0 && n < base);
Packit 5c3484
		n++;
Packit 5c3484
		if (n != base)
Packit 5c3484
		  {
Packit 5c3484
		    TRACE (printf ("  storing now %d\n", n));
Packit 5c3484
		    s[len-1] = num_to_text[n];
Packit 5c3484
		    break;
Packit 5c3484
		  }
Packit 5c3484
		len--;
Packit 5c3484
	      }
Packit 5c3484
	  }
Packit 5c3484
	else
Packit 5c3484
	  {
Packit 5c3484
	    /* truncate only, strip any trailing zeros now exposed */
Packit 5c3484
	    while (len > 0 && s[len-1] == '0')
Packit 5c3484
	      len--;
Packit 5c3484
	  }
Packit 5c3484
Packit 5c3484
	/* Can have newlen==0, in which case the truncate was just to check
Packit 5c3484
	   for a carry turning it into "1".  If we're left with len==0 then
Packit 5c3484
	   adjust exp to match.  */
Packit 5c3484
	if (len == 0)
Packit 5c3484
	  exp = 0;
Packit 5c3484
      }
Packit 5c3484
Packit 5c3484
  fixed:
Packit 5c3484
    ASSERT (len == 0 ? exp == 0 : 1);
Packit 5c3484
    if (exp <= 0)
Packit 5c3484
      {
Packit 5c3484
	TRACE (printf ("  fixed 0.000sss\n"));
Packit 5c3484
	intlen = 0;
Packit 5c3484
	intzeros = 1;
Packit 5c3484
	fraczeros = -exp;
Packit 5c3484
	fraclen = len;
Packit 5c3484
      }
Packit 5c3484
    else
Packit 5c3484
      {
Packit 5c3484
	TRACE (printf ("  fixed sss.sss or sss000\n"));
Packit 5c3484
	intlen = MIN (len, exp);
Packit 5c3484
	intzeros = exp - intlen;
Packit 5c3484
	fraczeros = 0;
Packit 5c3484
	fraclen = len - intlen;
Packit 5c3484
      }
Packit 5c3484
    explen = 0;
Packit 5c3484
    break;
Packit 5c3484
Packit 5c3484
  case DOPRNT_CONV_SCIENTIFIC:
Packit 5c3484
    {
Packit 5c3484
      long int expval;
Packit 5c3484
      char  expsign;
Packit 5c3484
Packit 5c3484
      if (prec <= -1)
Packit 5c3484
	prec = MAX (0, len-1);   /* retain all digits */
Packit 5c3484
Packit 5c3484
    scientific:
Packit 5c3484
      TRACE (printf ("  scientific s.sss\n"));
Packit 5c3484
Packit 5c3484
      intlen = MIN (1, len);
Packit 5c3484
      intzeros = (intlen == 0 ? 1 : 0);
Packit 5c3484
      fraczeros = 0;
Packit 5c3484
      fraclen = len - intlen;
Packit 5c3484
Packit 5c3484
      expval = (exp-intlen);
Packit 5c3484
      if (p->exptimes4)
Packit 5c3484
	expval <<= 2;
Packit 5c3484
Packit 5c3484
      /* Split out the sign since %o or %x in expfmt give negatives as twos
Packit 5c3484
	 complement, not with a sign. */
Packit 5c3484
      expsign = (expval >= 0 ? '+' : '-');
Packit 5c3484
      expval = ABS (expval);
Packit 5c3484
Packit 5c3484
#if HAVE_VSNPRINTF
Packit 5c3484
      explen = snprintf (exponent, sizeof(exponent),
Packit 5c3484
			 p->expfmt, expsign, expval);
Packit 5c3484
      /* test for < sizeof-1 since a glibc 2.0.x return of sizeof-1 might
Packit 5c3484
	 mean truncation */
Packit 5c3484
      ASSERT (explen >= 0 && explen < sizeof(exponent)-1);
Packit 5c3484
#else
Packit 5c3484
      sprintf (exponent, p->expfmt, expsign, expval);
Packit 5c3484
      explen = strlen (exponent);
Packit 5c3484
      ASSERT (explen < sizeof(exponent));
Packit 5c3484
#endif
Packit 5c3484
      TRACE (printf ("  expfmt %s gives %s\n", p->expfmt, exponent));
Packit 5c3484
    }
Packit 5c3484
    break;
Packit 5c3484
Packit 5c3484
  default:
Packit 5c3484
    ASSERT (0);
Packit 5c3484
    /*FALLTHRU*/  /* to stop variables looking uninitialized */
Packit 5c3484
Packit 5c3484
  case DOPRNT_CONV_GENERAL:
Packit 5c3484
    /* The exponent for "scientific" will be exp-1, choose scientific if
Packit 5c3484
       this is < -4 or >= prec (and minimum 1 for prec).  For f==0 will have
Packit 5c3484
       exp==0 and get the desired "fixed".  This rule follows glibc.  For
Packit 5c3484
       fixed there's no need to truncate, the desired ndigits will already
Packit 5c3484
       be as required.  */
Packit 5c3484
    if (exp-1 < -4 || exp-1 >= MAX (1, prec))
Packit 5c3484
      goto scientific;
Packit 5c3484
    else
Packit 5c3484
      goto fixed;
Packit 5c3484
  }
Packit 5c3484
Packit 5c3484
  TRACE (printf ("  intlen %d intzeros %d fraczeros %d fraclen %d\n",
Packit 5c3484
		 intlen, intzeros, fraczeros, fraclen));
Packit 5c3484
  ASSERT (p->prec <= -1
Packit 5c3484
	  ? intlen + fraclen == strlen (s)
Packit 5c3484
	  : intlen + fraclen <= strlen (s));
Packit 5c3484
Packit 5c3484
  if (p->showtrailing)
Packit 5c3484
    {
Packit 5c3484
      /* Pad to requested precision with trailing zeros, for general this is
Packit 5c3484
	 all digits, for fixed and scientific just the fraction.  */
Packit 5c3484
      preczeros = prec - (fraczeros + fraclen
Packit 5c3484
			  + (p->conv == DOPRNT_CONV_GENERAL
Packit 5c3484
			     ? intlen + intzeros : 0));
Packit 5c3484
      preczeros = MAX (0, preczeros);
Packit 5c3484
    }
Packit 5c3484
  else
Packit 5c3484
    preczeros = 0;
Packit 5c3484
  TRACE (printf ("  prec=%d showtrailing=%d, pad with preczeros %d\n",
Packit 5c3484
		 prec, p->showtrailing, preczeros));
Packit 5c3484
Packit 5c3484
  /* radix point if needed, or if forced */
Packit 5c3484
  pointlen = ((fraczeros + fraclen + preczeros) != 0 || p->showpoint != 0)
Packit 5c3484
    ? strlen (point) : 0;
Packit 5c3484
  TRACE (printf ("  point |%s|  pointlen %d\n", point, pointlen));
Packit 5c3484
Packit 5c3484
  /* Notice the test for a non-zero value is done after any truncation for
Packit 5c3484
     DOPRNT_CONV_FIXED. */
Packit 5c3484
  showbase = NULL;
Packit 5c3484
  showbaselen = 0;
Packit 5c3484
  switch (p->showbase) {
Packit 5c3484
  default:
Packit 5c3484
    ASSERT (0);
Packit 5c3484
    /*FALLTHRU*/
Packit 5c3484
  case DOPRNT_SHOWBASE_NO:
Packit 5c3484
    break;
Packit 5c3484
  case DOPRNT_SHOWBASE_NONZERO:
Packit 5c3484
    if (intlen == 0 && fraclen == 0)
Packit 5c3484
      break;
Packit 5c3484
    /*FALLTHRU*/
Packit 5c3484
  case DOPRNT_SHOWBASE_YES:
Packit 5c3484
    switch (p->base) {
Packit 5c3484
    case 16:  showbase = "0x"; showbaselen = 2; break;
Packit 5c3484
    case -16: showbase = "0X"; showbaselen = 2; break;
Packit 5c3484
    case 8:   showbase = "0";  showbaselen = 1; break;
Packit 5c3484
    }
Packit 5c3484
    break;
Packit 5c3484
  }
Packit 5c3484
  TRACE (printf ("  showbase %s showbaselen %d\n",
Packit 5c3484
		 showbase == NULL ? "" : showbase, showbaselen));
Packit 5c3484
Packit 5c3484
  /* left over field width */
Packit 5c3484
  justlen = p->width - (signlen + showbaselen + intlen + intzeros + pointlen
Packit 5c3484
			+ fraczeros + fraclen + preczeros + explen);
Packit 5c3484
  TRACE (printf ("  justlen %d fill 0x%X\n", justlen, p->fill));
Packit 5c3484
Packit 5c3484
  justify = p->justify;
Packit 5c3484
  if (justlen <= 0) /* no justifying if exceed width */
Packit 5c3484
    justify = DOPRNT_JUSTIFY_NONE;
Packit 5c3484
Packit 5c3484
  TRACE (printf ("  justify type %d  intlen %d pointlen %d fraclen %d\n",
Packit 5c3484
		 justify, intlen, pointlen, fraclen));
Packit 5c3484
Packit 5c3484
  if (justify == DOPRNT_JUSTIFY_RIGHT)         /* pad for right */
Packit 5c3484
    DOPRNT_REPS (p->fill, justlen);
Packit 5c3484
Packit 5c3484
  if (signlen)                                 /* sign */
Packit 5c3484
    DOPRNT_REPS (sign, 1);
Packit 5c3484
Packit 5c3484
  DOPRNT_MEMORY_MAYBE (showbase, showbaselen); /* base */
Packit 5c3484
Packit 5c3484
  if (justify == DOPRNT_JUSTIFY_INTERNAL)      /* pad for internal */
Packit 5c3484
    DOPRNT_REPS (p->fill, justlen);
Packit 5c3484
Packit 5c3484
  DOPRNT_MEMORY (s, intlen);                   /* integer */
Packit 5c3484
  DOPRNT_REPS_MAYBE ('0', intzeros);
Packit 5c3484
Packit 5c3484
  DOPRNT_MEMORY_MAYBE (point, pointlen);       /* point */
Packit 5c3484
Packit 5c3484
  DOPRNT_REPS_MAYBE ('0', fraczeros);          /* frac */
Packit 5c3484
  DOPRNT_MEMORY_MAYBE (s+intlen, fraclen);
Packit 5c3484
Packit 5c3484
  DOPRNT_REPS_MAYBE ('0', preczeros);          /* prec */
Packit 5c3484
Packit 5c3484
  DOPRNT_MEMORY_MAYBE (exponent, explen);      /* exp */
Packit 5c3484
Packit 5c3484
  if (justify == DOPRNT_JUSTIFY_LEFT)          /* pad for left */
Packit 5c3484
    DOPRNT_REPS (p->fill, justlen);
Packit 5c3484
Packit 5c3484
 done:
Packit 5c3484
  (*__gmp_free_func) (free_ptr, free_size);
Packit 5c3484
  return retval;
Packit 5c3484
Packit 5c3484
 error:
Packit 5c3484
  retval = -1;
Packit 5c3484
  goto done;
Packit 5c3484
}