This is the mail archive of the
newlib@sourceware.org
mailing list for the newlib project.
implement printf("%a")
- From: Eric Blake <ebb9 at byu dot net>
- To: newlib at sources dot redhat dot com
- Date: Wed, 9 May 2007 18:08:49 +0000 (UTC)
- Subject: implement printf("%a")
Here it goes; tested on cygwin using the gnulib printf-posix testsuite [1];
this passes all tests that used to require a gnulib replacement function.
It is not a general-purpose solution - it assumes ASCII encoding, it assumes
the current rounding mode is always round-to-even (ie. fegetround would return
FE_TONEAREST, if it were implemented in newlib), and it assumes that
FLT_RADIX==2 (it might work for FLT_RADIX==16 although I don't have access to
hardware like that; but will not work for FLT_RADIX==10; I don't know of any
other choices for FLT_RADIX on actual hardware); I think these assumptions were
already in place in newlib so I'm not hurting newlib portability.
The following implementation choices are made (which C99 and POSIX leave up to
the implementation): it ensures the digit prior to the decimal point is less
than FLT_RADIX unless rounding occurred; it ensures the digit prior to the
decimal point is non-zero even when the value is denormal; and it strips
trailing zeros if precision is not specified.
Note - this implementation currently mishandles printf("%La") in cases where
(double)long_double != long_double
and I think it infloops if long_double is big enough that (double)long_double
results in infinity. The solution to this limitation is to implement frexpl,
which I have been working on but don't have ready yet.
If newlib ever implements <fenv.h> and fesetround(), then further tweaks would
be needed to get rounding to comply with POSIX.
[1] http://git.sv.gnu.org/gitweb/?p=gnulib.git;a=blob;f=tests/test-snprintf-
posix.h;h=8803edf9d70c485e31ed7a0d7dcbf88b113299a3;hb=60908d3a1b0600040e74330157
e667cb4055625d
2007-05-09 Eric Blake <ebb9@byu.net>
* libc/stdio/vfprintf.c (_VFPRINTF_R, cvt, exponent, chclass)
(get_arg): Support %a and %A.
Index: libc/stdio/vfprintf.c
===================================================================
RCS file: /cvs/src/src/newlib/libc/stdio/vfprintf.c,v
retrieving revision 1.56
diff -u -p -p -r1.56 vfprintf.c
--- libc/stdio/vfprintf.c 8 May 2007 03:59:13 -0000 1.56
+++ libc/stdio/vfprintf.c 9 May 2007 18:04:21 -0000
@@ -138,13 +138,8 @@ static char *rcsid = "$Id: vfprintf.c,v
#include <limits.h>
#include <stdint.h>
#include <wchar.h>
-#include <string.h>
#include <sys/lock.h>
-#ifdef _HAVE_STDC
#include <stdarg.h>
-#else
-#include <varargs.h>
-#endif
#include "local.h"
#include "fvwrite.h"
#include "vfieeefp.h"
@@ -243,10 +238,12 @@ _DEFUN(__sbprintf, (rptr, fp, fmt, ap),
#ifdef _NO_LONGDBL
static char *
-_EXFUN(cvt, (struct _reent *, double, int, int, char *, int *, int, int *));
+_EXFUN(cvt, (struct _reent *, double, int, int, char *, int *, int, int *,
+ char *));
#else
static char *
-_EXFUN(cvt, (struct _reent *, _LONG_DOUBLE, int, int, char *, int *, int, int
*));
+_EXFUN(cvt, (struct _reent *, _LONG_DOUBLE, int, int, char *, int *, int,
+ int *, char *));
extern int _EXFUN(_ldcheck,(_LONG_DOUBLE *));
#endif
@@ -375,7 +372,7 @@ _DEFUN(_VFPRINTF_R, (data, fp, fmt0, ap)
int prec; /* precision from format (%.3d), or -1 */
char sign; /* sign prefix (' ', '+', '-', or \0) */
#ifdef FLOATING_POINT
- char *decimal_point = localeconv()->decimal_point;
+ char *decimal_point = _localeconv_r (data)->decimal_point;
char softsign; /* temporary negative sign for floats */
#ifdef _NO_LONGDBL
union { int i; double d; } _double_ = {0};
@@ -814,18 +811,14 @@ reswitch: switch (ch) {
base = DEC;
goto number;
#ifdef FLOATING_POINT
+ case 'a':
+ case 'A':
case 'e':
case 'E':
case 'f':
case 'F':
case 'g':
case 'G':
- if (prec == -1) {
- prec = DEFPREC;
- } else if ((ch == 'g' || ch == 'G') && prec == 0) {
- prec = 1;
- }
-
#ifdef _NO_LONGDBL
if (flags & LONGDBL) {
_fpvalue = (double) GET_ARG (N, ap,
_LONG_DOUBLE);
@@ -892,10 +885,33 @@ reswitch: switch (ch) {
}
#endif /* !_NO_LONGDBL */
+ if (ch == 'a' || ch == 'A') {
+ ox[0] = '0';
+ ox[1] = ch == 'a' ? 'x' : 'X';
+ flags |= HEXPREFIX;
+ if (prec >= sizeof buf)
+ {
+ if ((malloc_buf =
+ (char *)_malloc_r (data, prec + 1))
+ == NULL)
+ {
+ fp->_flags |= __SERR;
+ goto error;
+ }
+ cp = malloc_buf;
+ }
+ else
+ cp = buf;
+ } else if (prec == -1) {
+ prec = DEFPREC;
+ } else if ((ch == 'g' || ch == 'G') && prec == 0) {
+ prec = 1;
+ }
+
flags |= FPT;
cp = cvt (data, _fpvalue, prec, flags, &softsign,
- &expt, ch, &ndig);
+ &expt, ch, &ndig, cp);
if (ch == 'g' || ch == 'G') {
if (expt <= -4 || expt > prec)
@@ -905,7 +921,7 @@ reswitch: switch (ch) {
}
else if (ch == 'F')
ch = 'f';
- if (ch <= 'e') { /* 'e' or 'E' fmt */
+ if (ch <= 'e') { /* 'a', 'A', 'e', or 'E' fmt */
--expt;
expsize = exponent (expstr, expt, ch);
size = expsize + ndig;
@@ -1067,8 +1083,11 @@ reswitch: switch (ch) {
hex: _uquad = UARG ();
base = HEX;
/* leading 0x/X only if non-zero */
- if (flags & ALT && _uquad != 0)
+ if (flags & ALT && _uquad != 0) {
+ ox[0] = '0';
+ ox[1] = ch;
flags |= HEXPREFIX;
+ }
/* unsigned conversions */
nosign: sign = '\0';
@@ -1161,7 +1180,7 @@ number: if ((dprec = prec) >= 0)
* required by a decimal [diouxX] precision, then print the
* string proper, then emit zeroes required by any leftover
* floating precision; finally, if LADJUST, pad with blanks.
- * If flags&FPT, ch must be in [eEfg].
+ * If flags&FPT, ch must be in [aAeEfg].
*
* Compute actual size, so we know how much to pad.
* size excludes decimal prec; realsz includes it.
@@ -1169,7 +1188,7 @@ number: if ((dprec = prec) >= 0)
realsz = dprec > size ? dprec : size;
if (sign)
realsz++;
- else if (flags & HEXPREFIX)
+ if (flags & HEXPREFIX)
realsz+= 2;
/* right-adjusting blank padding */
@@ -1177,13 +1196,10 @@ number: if ((dprec = prec) >= 0)
PAD (width - realsz, blanks);
/* prefix */
- if (sign) {
+ if (sign)
PRINT (&sign, 1);
- } else if (flags & HEXPREFIX) {
- ox[0] = '0';
- ox[1] = ch;
+ if (flags & HEXPREFIX)
PRINT (ox, 2);
- }
/* right-adjusting zero padding */
if ((flags & (LADJUST|ZEROPAD)) == ZEROPAD)
@@ -1223,11 +1239,11 @@ number: if ((dprec = prec) >= 0)
PRINT (".", 1);
PRINT (cp, ndig - expt);
}
- } else { /* 'e' or 'E' */
+ } else { /* 'a', 'A', 'e', or 'E' */
if (ndig > 1 || flags & ALT) {
- ox[0] = *cp++;
- ox[1] = '.';
- PRINT (ox, 2);
+ PRINT (cp, 1);
+ cp++;
+ PRINT (".", 1);
if (_fpvalue) {
PRINT (cp, ndig - 1);
} else /* 0.[0..] */
@@ -1267,19 +1283,32 @@ error:
#ifdef FLOATING_POINT
-#ifdef _NO_LONGDBL
+# ifdef _NO_LONGDBL
extern char *_dtoa_r _PARAMS((struct _reent *, double, int,
int, int *, int *, char **));
-#else
+# define _DTOA_R _dtoa_r
+# define FREXP frexp
+# else
extern char *_ldtoa_r _PARAMS((struct _reent *, _LONG_DOUBLE, int,
int, int *, int *, char **));
-#undef word0
-#define word0(x) ldword0(x)
-#endif
+# define _DTOA_R _ldtoa_r
+/* FIXME - frexpl is not yet supported. */
+/* # define FREXP frexpl */
+# define FREXP(f,e) ((_LONG_DOUBLE) frexp ((double)f, e))
+# undef word0
+# define word0(x) ldword0(x)
+# endif
+/* Using reentrant DATA, convert VALUE into a string of digits with no
+ decimal point, using NDIGITS precision and FLAGS as guides to
+ whether trailing zeros must be included. Set *SIGN to nonzero if
+ VALUE was negative. Set DECPT to the exponent plus one. Set
+ LENGTH to the length of the returned string. CH must be one of
+ [aAeEfFgG]; if it is [aA], then the return string lives in BUF,
+ otherwise the return value shares the mprec reentrant storage. */
#ifdef _NO_LONGDBL
static char *
-_DEFUN(cvt, (data, value, ndigits, flags, sign, decpt, ch, length),
+_DEFUN(cvt, (data, value, ndigits, flags, sign, decpt, ch, length, buf),
struct _reent *data _AND
double value _AND
int ndigits _AND
@@ -1287,10 +1316,11 @@ _DEFUN(cvt, (data, value, ndigits, flags
char *sign _AND
int *decpt _AND
int ch _AND
- int *length)
+ int *length _AND
+ char *buf)
#else
static char *
-_DEFUN(cvt, (data, value, ndigits, flags, sign, decpt, ch, length),
+_DEFUN(cvt, (data, value, ndigits, flags, sign, decpt, ch, length, buf),
struct _reent *data _AND
_LONG_DOUBLE value _AND
int ndigits _AND
@@ -1298,13 +1328,14 @@ _DEFUN(cvt, (data, value, ndigits, flags
char *sign _AND
int *decpt _AND
int ch _AND
- int *length)
+ int *length _AND
+ char *buf)
#endif
{
int mode, dsgn;
char *digits, *bp, *rve;
#ifdef _NO_LONGDBL
- union double_union tmp;
+ union double_union tmp;
#else
union
{
@@ -1313,40 +1344,67 @@ _DEFUN(cvt, (data, value, ndigits, flags
} ld;
#endif
- if (ch == 'f' || ch == 'F') {
- mode = 3; /* ndigits after the decimal point */
- } else {
- /* To obtain ndigits after the decimal point for the 'e'
- * and 'E' formats, round to ndigits + 1 significant
- * figures.
- */
- if (ch == 'e' || ch == 'E') {
- ndigits++;
- }
- mode = 2; /* ndigits significant digits */
- }
-
#ifdef _NO_LONGDBL
- tmp.d = value;
-
+ tmp.d = value;
if (word0 (tmp) & Sign_bit) { /* this will check for < 0 and -0.0 */
value = -value;
*sign = '-';
- } else
+ } else
*sign = '\000';
-
- digits = _dtoa_r (data, value, mode, ndigits, decpt, &dsgn, &rve);
#else /* !_NO_LONGDBL */
ld.val = value;
if (ld.ieee.sign) { /* this will check for < 0 and -0.0 */
value = -value;
*sign = '-';
- } else
+ } else
*sign = '\000';
-
- digits = _ldtoa_r (data, value, mode, ndigits, decpt, &dsgn, &rve);
#endif /* !_NO_LONGDBL */
+ if (ch == 'a' || ch == 'A') {
+ /* This code assumes FLT_RADIX is a power of 2. The initial
+ division ensures the digit before the decimal will be less
+ than FLT_RADIX (unless it is rounded later). There is no
+ loss of precision in these calculations. */
+ value = FREXP (value, decpt) / 8;
+ if (!value)
+ *decpt = 1;
+ digits = ch == 'a' ? "0123456789abcdef" : "0123456789ABCDEF";
+ bp = buf;
+ do {
+ value *= 16;
+ mode = (int) value;
+ value -= mode;
+ *bp++ = digits[mode];
+ } while (ndigits-- && value);
+ if (value > 0.5 || (value == 0.5 && mode & 1)) {
+ /* round to even */
+ rve = bp;
+ while (*--rve == digits[0xf]) {
+ *rve = '0';
+ }
+ *rve = *rve == '9' ? digits[0xa] : *rve + 1;
+ } else {
+ while (ndigits-- >= 0) {
+ *bp++ = '0';
+ }
+ }
+ *length = bp - buf;
+ return buf;
+ } else if (ch == 'f' || ch == 'F') {
+ mode = 3; /* ndigits after the decimal point */
+ } else {
+ /* To obtain ndigits after the decimal point for the 'e'
+ * and 'E' formats, round to ndigits + 1 significant
+ * figures.
+ */
+ if (ch == 'e' || ch == 'E') {
+ ndigits++;
+ }
+ mode = 2; /* ndigits significant digits */
+ }
+
+ digits = _DTOA_R (data, value, mode, ndigits, decpt, &dsgn, &rve);
+
if ((ch != 'g' && ch != 'G') || flags & ALT) { /* Print trailing zeros
*/
bp = digits + ndigits;
if (ch == 'f' || ch == 'F') {
@@ -1370,26 +1428,28 @@ _DEFUN(exponent, (p0, exp, fmtch),
int fmtch)
{
register char *p, *t;
- char expbuf[40];
+ char expbuf[10];
+ int isa = fmtch == 'a' || fmtch == 'A';
p = p0;
- *p++ = fmtch;
+ *p++ = isa ? 'p' - 'a' + fmtch : fmtch;
if (exp < 0) {
exp = -exp;
*p++ = '-';
}
else
*p++ = '+';
- t = expbuf + 40;
+ t = expbuf + 10;
if (exp > 9) {
do {
*--t = to_char (exp % 10);
} while ((exp /= 10) > 9);
*--t = to_char (exp);
- for (; t < expbuf + 40; *p++ = *t++);
+ for (; t < expbuf + 10; *p++ = *t++);
}
else {
- *p++ = '0';
+ if (!isa)
+ *p++ = '0';
*p++ = to_char (exp);
}
return (p - p0);
@@ -1480,11 +1540,11 @@ _CONST static CH_CLASS chclass[256] = {
/* 28-2f */ OTHER, OTHER, STAR, FLAG, OTHER, FLAG, DOT,
OTHER,
/* 30-37 */ ZERO, DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, DIGIT,
DIGIT,
/* 38-3f */ DIGIT, DIGIT, OTHER, OTHER, OTHER, OTHER, OTHER,
OTHER,
- /* 40-47 */ OTHER, OTHER, OTHER, SPEC, SPEC, SPEC, SPEC,
SPEC,
+ /* 40-47 */ OTHER, SPEC, OTHER, SPEC, SPEC, SPEC, SPEC,
SPEC,
/* 48-4f */ OTHER, OTHER, OTHER, OTHER, MODFR, OTHER, OTHER,
SPEC,
/* 50-57 */ OTHER, OTHER, OTHER, SPEC, OTHER, SPEC, OTHER,
OTHER,
/* 58-5f */ SPEC, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER,
OTHER,
- /* 60-67 */ OTHER, OTHER, OTHER, SPEC, SPEC, SPEC, SPEC,
SPEC,
+ /* 60-67 */ OTHER, SPEC, OTHER, SPEC, SPEC, SPEC, SPEC,
SPEC,
/* 68-6f */ MODFR, SPEC, MODFR, OTHER, MODFR, OTHER, SPEC,
SPEC,
/* 70-77 */ SPEC, MODFR, OTHER, SPEC, MODFR, SPEC, OTHER,
OTHER,
/* 78-7f */ SPEC, OTHER, MODFR, OTHER, OTHER, OTHER, OTHER,
OTHER,
@@ -1693,6 +1753,8 @@ _DEFUN(get_arg, (data, n, fmt, ap, numar
case 'O':
spec_type = LONG_INT;
break;
+ case 'a':
+ case 'A':
case 'f':
case 'F':
case 'g':