+
+#define EFORMAT 0x01
+#define FFORMAT 0x02
+#define GFORMAT 0x04
+
+static char *
+_cvt(number, prec, startp, endp, fmtch)
+ double number;
+ register int prec;
+ char *startp, *endp, fmtch;
+{
+ register char *p;
+ register int expcnt, format;
+ double fract, integer, tmp, modf();
+ int decpt;
+ char *savep;
+
+ if (prec == -1) /* set default precision */
+ prec = DEFPREC;
+
+ p = endp - 1;
+ if (number < 0) { /* set sign */
+ *startp++ = '-';
+ number = -number;
+ }
+ else if (printsign)
+ *startp++ = '+';
+
+ switch(fmtch) {
+ case 'e':
+ case 'E':
+ format = EFORMAT;
+ break;
+ case 'f':
+ format = FFORMAT;
+ break;
+ case 'g':
+ case 'G':
+ format = GFORMAT;
+ fmtch -= 2;
+ }
+
+ /*
+ * if the alternate flag is set, or, at least one digit of precision
+ * was requested, add a decimal point, unless it's the g/G format
+ * in which case we require two digits of precision, since it counts
+ * precision differently.
+ */
+ decpt = alternate || prec > 1 || !(format&GFORMAT) && prec;
+
+ expcnt = 0;
+ fract = modf(number, &integer);
+ if (integer) {
+ register char *p2;
+
+ /* get integer part of number; count decimal places */
+ for (; integer; ++expcnt) {
+ tmp = modf(integer / 10, &integer);
+ *p-- = (int)((tmp + .03) * 10) + '0';
+ }
+
+ /* copy, in reverse order, to start of buffer */
+ p2 = startp;
+ *p2++ = *++p;
+
+ /*
+ * if the format is g/G, and the resulting exponent will be
+ * greater than the precision, use e/E format. If e/E format,
+ * put in a decimal point as needed, and decrement precision
+ * count for each digit after the decimal point.
+ */
+ if (format&GFORMAT && expcnt - 1 > prec || format&EFORMAT) {
+ if (format&GFORMAT) {
+ format |= EFORMAT;
+
+ /* first digit is precision for g/G format */
+ if (prec)
+ --prec;
+ }
+ if (decpt)
+ *p2++ = '.';
+ for (; ++p < endp && prec; --prec, *p2++ = *p);
+
+ /* precision ran out; round number */
+ if (p < endp) {
+ if (*p > '4') {
+ for (savep = p2--;; *p2-- = '0') {
+ if (*p2 == '.')
+ --p2;
+ if (++*p2 <= '9')
+ break;
+ }
+ p2 = savep;
+ }
+ fract = 0;
+ }
+ }
+ /*
+ * g/G in f format; if run out of precision, replace digits
+ * with zeroes, note, have to round first, otherwise lose
+ * rounding point.
+ */
+ else if (format&GFORMAT) {
+ for (; ++p < endp && prec; --prec, *p2++ = *p);
+ /* precision ran out; round and then add zeroes */
+ if (p < endp) {
+ if (*p > '4') {
+ for (savep = p2--; ++*p2 > '9';
+ *p2-- = '0');
+ p2 = savep;
+ }
+ do {
+ *p2++ = '0';
+ } while (++p < endp);
+ fract = 0;
+ }
+ if (decpt)
+ *p2++ = '.';
+ }
+ /* f format */
+ else {
+ for (; ++p < endp; *p2++ = *p);
+ if (decpt)
+ *p2++ = '.';
+ }
+ p = p2;
+ }
+ /*
+ * it's unclear from the ANSI X3J11 spec if the g/G format should
+ * just result in an empty string, because it's supposed to remove
+ * trailing zeroes. That seems counter-intuitive, so here it does
+ * what f and e/E do; if no fraction, the number was zero, and if
+ * no precision can't show anything after the decimal point.
+ */
+ else if (!fract || !prec) {
+ *startp++ = '0';
+ if (decpt)
+ *startp++ = '.';
+ *startp++ = '\0';
+ return(startp);
+ }
+ /*
+ * if the format is g/G, and the resulting exponent will be less than
+ * -4 use e/E format. If e/E format, compute exponent value.
+ */
+ else if (format&GFORMAT && fract < .0001 || format&EFORMAT) {
+ format |= EFORMAT;
+ if (fract)
+ for (p = startp; fract;) {
+ fract = modf(fract * 10, &tmp);
+ if (!tmp) {
+ --expcnt;
+ continue;
+ }
+ *p++ = (int)tmp + '0';
+ break;
+ }
+ else
+ *p++ = '0';
+
+ /* g/G format, decrement precision for first digit */
+ if (format&GFORMAT && prec)
+ --prec;
+
+ /* add decimal after first non-zero digit */
+ if (decpt)
+ *p++ = '.';
+ }
+ /*
+ * f format or g/G printed as f format; don't worry about decimal
+ * point, if g/G format doesn't need it, will get stripped later.
+ */
+ else {
+ p = startp;
+ *p++ = '0';
+ *p++ = '.';
+ }
+
+ /* finish out requested precision from fractional value */
+ while (prec--)
+ if (fract) {
+ fract = modf(fract * 10, &tmp);
+ *p++ = (int)tmp + '0';
+ }
+ else
+ *p++ = '0';
+
+ /*
+ * if any fractional value left, "round" it back up to the beginning
+ * of the number, fixing the exponent as necessary, and avoiding the
+ * decimal point.
+ */
+ if (fract) {
+ (void)modf(fract * 10, &tmp);
+ if (tmp > 4) {
+ for (savep = p--;; *p-- = '0') {
+ if (*p == '.')
+ --p;
+ if (p == startp) {
+ *p = '1';
+ ++expcnt;
+ break;
+ }
+ if (++*p <= '9')
+ break;
+ }
+ p = savep;
+ }
+ }
+
+ /*
+ * if a g/G format and not alternate flag, lose trailing zeroes,
+ * if e/E or g/G format, and last char is decimal point, lose it.
+ */
+ if (!alternate) {
+ if (format&GFORMAT)
+ for (; p[-1] == '0'; --p);
+ if (format&(GFORMAT|EFORMAT) && p[-1] == '.')
+ --p;
+ }
+
+ /* if an e/E format, add exponent */
+ if (format&EFORMAT) {
+ *p++ = fmtch;
+ if (--expcnt < 0) {
+ expcnt = -expcnt;
+ *p++ = '-';
+ }
+ else
+ *p++ = '+';
+ *p++ = expcnt / 10 + '0';
+ *p++ = expcnt % 10 + '0';
+ }
+ return(p);
+}