/* FUNCTION <>, <>---double or float to string INDEX ecvtbuf INDEX fcvtbuf SYNOPSIS #include char *ecvtbuf(double <[val]>, int <[chars]>, int *<[decpt]>, int *<[sgn]>, char *<[buf]>); char *fcvtbuf(double <[val]>, int <[decimals]>, int *<[decpt]>, int *<[sgn]>, char *<[buf]>); DESCRIPTION <> and <> produce (null-terminated) strings of digits representating the <> number <[val]>. The only difference between <> and <> is the interpretation of the second argument (<[chars]> or <[decimals]>). For <>, the second argument <[chars]> specifies the total number of characters to write (which is also the number of significant digits in the formatted string, since these two functions write only digits). For <>, the second argument <[decimals]> specifies the number of characters to write after the decimal point; all digits for the integer part of <[val]> are always included. Since <> and <> write only digits in the output string, they record the location of the decimal point in <<*<[decpt]>>>, and the sign of the number in <<*<[sgn]>>>. After formatting a number, <<*<[decpt]>>> contains the number of digits to the left of the decimal point. <<*<[sgn]>>> contains <<0>> if the number is positive, and <<1>> if it is negative. For both functions, you supply a pointer <[buf]> to an area of memory to hold the converted string. RETURNS Both functions return a pointer to <[buf]>, the string containing a character representation of <[val]>. PORTABILITY Neither function is ANSI C. Supporting OS subroutines required: <>, <>, <>, <>, <>, <>, <>. */ #include <_ansi.h> #include #include #include #include "mprec.h" #include "local.h" #ifdef _REENT_THREAD_LOCAL _Thread_local char *_tls_cvtbuf; _Thread_local int _tls_cvtlen; #endif static void print_f (struct _reent *ptr, char *buf, double invalue, int ndigit, char type, int dot, int mode) { int decpt; int sign; char *p, *start, *end; start = p = _dtoa_r (ptr, invalue, mode, ndigit, &decpt, &sign, &end); if (decpt == 9999) { strcpy (buf, p); return; } while (*p && decpt > 0) { *buf++ = *p++; decpt--; } /* Even if not in buffer */ while (decpt > 0) { *buf++ = '0'; decpt--; } if (dot || *p) { if (p == start) *buf++ = '0'; if (decpt < 0 && ndigit > 0) *buf++ = '.'; while (decpt < 0 && ndigit > 0) { *buf++ = '0'; decpt++; ndigit--; } /* Print rest of stuff */ while (*p && ndigit > 0) { *buf++ = *p++; ndigit--; } /* And trailing zeros */ while (ndigit > 0) { *buf++ = '0'; ndigit--; } } *buf++ = 0; } /* Print number in e format with width chars after. TYPE is one of 'e' or 'E'. It may also be one of 'g' or 'G' indicating that _gcvt is calling us and we should remove trailing zeroes. WIDTH is the number of digits of precision after the decimal point. */ static void print_e (struct _reent *ptr, char *buf, double invalue, int width, char type, int dot) { int sign; char *end; char *p; int decpt; int top; int ndigit = width; p = _dtoa_r (ptr, invalue, 2, width + 1, &decpt, &sign, &end); if (decpt == 9999) { strcpy (buf, p); return; } *buf++ = *p++; if (ndigit > 0) dot = 1; while (*p && ndigit > 0) { if (dot) { *buf++ = '.'; dot = 0; } *buf++ = *p++; ndigit--; } /* Add trailing zeroes to fill out to ndigits unless this is 'g' format. Also, convert g/G to e/E. */ if (type == 'g') type = 'e'; else if (type == 'G') type = 'E'; else { while (ndigit > 0) { if (dot) { *buf++ = '.'; dot = 0; } *buf++ = '0'; ndigit--; } } /* Add the exponent. */ *buf++ = type; decpt--; if (decpt < 0) { *buf++ = '-'; decpt = -decpt; } else { *buf++ = '+'; } if (decpt > 99) { int top = decpt / 100; *buf++ = top + '0'; decpt -= top * 100; } top = decpt / 10; *buf++ = top + '0'; decpt -= top * 10; *buf++ = decpt + '0'; *buf++ = 0; } #ifndef _REENT_ONLY /* Undocumented behaviour: when given NULL as a buffer, return a pointer to static space in the rent structure. This is only to support ecvt and fcvt, which aren't ANSI anyway. */ char * fcvtbuf (double invalue, int ndigit, int *decpt, int *sign, char *fcvt_buf) { struct _reent *reent = _REENT; char *save; char *p; char *end; int done = 0; if (fcvt_buf == NULL) { if (_REENT_CVTLEN(reent) <= ndigit + 35) { if ((fcvt_buf = (char *) _realloc_r (reent, _REENT_CVTBUF(reent), ndigit + 36)) == NULL) return NULL; _REENT_CVTLEN(reent) = ndigit + 36; _REENT_CVTBUF(reent) = fcvt_buf; } fcvt_buf = _REENT_CVTBUF(reent) ; } save = fcvt_buf; p = _dtoa_r (reent, invalue, 3, ndigit, decpt, sign, &end); /* Now copy */ done = -*decpt; while (p < end) { *fcvt_buf++ = *p++; done++; } /* And unsuppress the trailing zeroes */ while (done < ndigit) { *fcvt_buf++ = '0'; done++; } *fcvt_buf++ = 0; return save; } char * ecvtbuf (double invalue, int ndigit, int *decpt, int *sign, char *fcvt_buf) { struct _reent *reent = _REENT; char *save; char *p; char *end; int done = 0; if (fcvt_buf == NULL) { if (_REENT_CVTLEN(reent) <= ndigit) { if ((fcvt_buf = (char *) _realloc_r (reent, _REENT_CVTBUF(reent), ndigit + 1)) == NULL) return NULL; _REENT_CVTLEN(reent) = ndigit + 1; _REENT_CVTBUF(reent) = fcvt_buf; } fcvt_buf = _REENT_CVTBUF(reent) ; } save = fcvt_buf; p = _dtoa_r (reent, invalue, 2, ndigit, decpt, sign, &end); /* Now copy */ while (p < end) { *fcvt_buf++ = *p++; done++; } /* And unsuppress the trailing zeroes */ while (done < ndigit) { *fcvt_buf++ = '0'; done++; } *fcvt_buf++ = 0; return save; } #endif char * _gcvt (struct _reent *ptr, double invalue, int ndigit, char *buf, char type, int dot) { char *save = buf; if (invalue < 0) { invalue = -invalue; } if (invalue == 0) { *buf++ = '0'; *buf = '\0'; } else /* Which one to print ? ANSI says that anything with more that 4 zeros after the . or more than precision digits before is printed in e with the qualification that trailing zeroes are removed from the fraction portion. */ if (0.0001 >= invalue || invalue >= _mprec_log10 (ndigit)) { /* We subtract 1 from ndigit because in the 'e' format the precision is the number of digits after the . but in 'g' format it is the number of significant digits. We defer changing type to e/E so that print_e() can know it's us calling and thus should remove trailing zeroes. */ print_e (ptr, buf, invalue, ndigit - 1, type, dot); } else { int decpt; int sign; char *end; char *p; /* We always want ndigits of precision, even if that means printing * a bunch of leading zeros for numbers < 1.0 */ p = _dtoa_r (ptr, invalue, 2, ndigit, &decpt, &sign, &end); if (decpt == 9999) { strcpy (buf, p); return save; } while (*p && decpt > 0) { *buf++ = *p++; decpt--; ndigit--; } /* Even if not in buffer */ while (decpt > 0 && ndigit > 0) { *buf++ = '0'; decpt--; ndigit--; } if (dot || *p) { if (buf == save) *buf++ = '0'; *buf++ = '.'; /* Leading zeros don't count towards 'ndigit' */ while (decpt < 0) { *buf++ = '0'; decpt++; } /* Print rest of stuff */ while (*p && ndigit > 0) { *buf++ = *p++; ndigit--; } /* And trailing zeros */ if (dot) { while (ndigit > 0) { *buf++ = '0'; ndigit--; } } } *buf++ = 0; } return save; } char * _dcvt (struct _reent *ptr, char *buffer, double invalue, int precision, int width, char type, int dot) { switch (type) { case 'f': case 'F': print_f (ptr, buffer, invalue, precision, type, precision == 0 ? dot : 1, 3); break; case 'g': case 'G': if (precision == 0) precision = 1; _gcvt (ptr, invalue, precision, buffer, type, dot); break; case 'e': case 'E': print_e (ptr, buffer, invalue, precision, type, dot); } return buffer; }