/* This file contain long double I/O routines for Windows (because Windows API don't support long double at all. They don't work on other architectures. So be curefull. */ /* This source comes from the DJGPP runtime library. It has been hacked to work with XaoS */ /* Copyright (C) 1998 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1997 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1994 DJ Delorie, see COPYING.DJ for details */ #include #include #include #include /*#include */ #include #include #include #include #include #include #ifdef USE_XLDIO static long double powten[] = { 1e1L, 1e2L, 1e4L, 1e8L, 1e16L, 1e32L, 1e64L, 1e128L, 1e256L, 1e512L, 1e1024L, 1e2048L, 1e4096L }; long double x_strtold(CONST char *s, CONST char **sret) { long double r; /* result */ int e, ne; /* exponent */ int sign; /* +- 1.0 */ int esign; int flags = 0; int l2powm1; r = 0.0L; sign = 1; e = ne = 0; esign = 1; while (*s && isspace(*s)) s++; if (*s == '+') s++; else if (*s == '-') { sign = -1; s++; } while ((*s >= '0') && (*s <= '9')) { flags |= 1; r *= 10.0L; r += *s - '0'; s++; } if (*s == '.') { s++; while ((*s >= '0') && (*s <= '9')) { flags |= 2; r *= 10.0L; r += *s - '0'; s++; ne++; } } if (flags == 0) { if (sret) *sret = /*unconst(s, char *) */ s; return 0.0L; } if ((*s == 'e') || (*s == 'E')) { s++; if (*s == '+') s++; else if (*s == '-') { s++; esign = -1; } while ((*s >= '0') && (*s <= '9')) { e *= 10; e += *s - '0'; s++; } } if (esign < 0) { esign = -esign; e = -e; } e = e - ne; if (e < -4096) { /* possibly subnormal number, 10^e would overflow */ r *= 1.0e-2048L; e += 2048; } if (e < 0) { e = -e; esign = -esign; } if (e >= 8192) e = 8191; if (e) { long double d = 1.0L; l2powm1 = 0; while (e) { if (e & 1) d *= powten[l2powm1]; e >>= 1; l2powm1++; } if (esign > 0) r *= d; else r /= d; } if (sret) *sret = /*unconst(s, char *) */ s; return r * sign; } #if 0 main() { printf("%E", (float) x_strtold("1.4E15", NULL)); } #endif #define MAXEXPLD 4952 /* this includes subnormal numbers */ static int is_nan = 0; static char decimal = '.'; static long double pten[] = { 1e1L, 1e2L, 1e4L, 1e8L, 1e16L, 1e32L, 1e64L, 1e128L, 1e256L, 1e512L, 1e1024L, 1e2048L, 1e4096L }; static long double ptenneg[] = { 1e-1L, 1e-2L, 1e-4L, 1e-8L, 1e-16L, 1e-32L, 1e-64L, 1e-128L, 1e-256L, 1e-512L, 1e-1024L, 1e-2048L, 1e-4096L }; static inline char tochar(int n) { if (n >= 9) return '9'; if (n <= 0) return '0'; return n + '0'; } static inline int todigit(char c) { if (c <= '0') return 0; if (c >= '9') return 9; return c - '0'; } #define LONGINT 0x01 /* long integer */ #define LONGDBL 0x02 /* long double */ #define SHORTINT 0x04 /* short integer */ #define ALT 0x08 /* alternate form */ #define LADJUST 0x10 /* left adjustment */ #define ZEROPAD 0x20 /* zero (as opposed to blank) pad */ #define HEXPREFIX 0x40 /* add 0x or 0X prefix */ #define MAXP 4096 #define NP 12 #define P (4294967296.0L * 4294967296.0L * 2.0L) /* 2^65 */ static long double INVPREC = P; static long double PREC = 1.0L / P; #undef P /* * Defining FAST_LDOUBLE_CONVERSION results in a little bit faster * version, which might be less accurate (about 1 bit) for long * double. For 'normal' double it doesn't matter. */ /* #define FAST_LDOUBLE_CONVERSION */ #if 1 #define modfl mymodfl inline long double m_floor(long double x) { register long double __value; volatile unsigned short int __cw, __cwtmp; asm volatile ("fnstcw %0":"=m" (__cw)); __cwtmp = (__cw & 0xf3ff) | 0x0400; /* rounding down */ asm volatile ("fldcw %0"::"m" (__cwtmp)); asm volatile ("frndint":"=t" (__value):"0"(x)); asm volatile ("fldcw %0"::"m" (__cw)); return __value; } static inline long double mymodfl(long double x, long double *pint) { /*int p=(int) x; */ long double p = m_floor(x); long double frac = x - p; if (x < 0) p = p + 1, frac = frac - 1; *pint = p; return frac; } #endif static char *exponentl(char *p, int expv, unsigned char fmtch) { char *t; char expbuf[MAXEXPLD]; *p++ = fmtch; if (expv < 0) { expv = -expv; *p++ = '-'; } else *p++ = '+'; t = expbuf + MAXEXPLD; if (expv > 9) { do { *--t = tochar(expv % 10); } while ((expv /= 10) > 9); *--t = tochar(expv); for (; t < expbuf + MAXEXPLD; *p++ = *t++); } else { *p++ = '0'; *p++ = tochar(expv); } return p; } static int isspeciall(long double d, char *bufp) { struct IEEExp { unsigned manl:32; unsigned manh:32; unsigned exp:15; unsigned sign:1; } *ip = (struct IEEExp *) &d; is_nan = 0; /* don't assume the static is 0 (emacs) */ if (ip->exp != 0x7fff) return (0); if ((ip->manh & 0x7fffffff) || ip->manl) { strcpy(bufp, "NaN"); is_nan = ip->sign ? -1 : 1; /* kludge: we don't need the sign, it's not nice but it should work */ } else (void) strcpy(bufp, "Inf"); return (3); } static char *my_roundl(long double fract, int *expv, char *start, char *end, char ch, char *signp) { long double tmp; if (fract) { if (fract == 0.5L) { char *e = end; if (*e == '.') e--; if (*e == '0' || *e == '2' || *e == '4' || *e == '6' || *e == '8') { tmp = 3.0; goto start; } } (void) modfl(fract * 10.0L, &tmp); } else tmp = todigit(ch); start: if (tmp > 4) for (;; --end) { if (*end == decimal) --end; if (++*end <= '9') break; *end = '0'; if (end == start) { if (expv) { /* e/E; increment exponent */ *end = '1'; ++*expv; } else { /* f; add extra digit */ *--end = '1'; --start; } break; } } /* ``"%.3f", (double)-0.0004'' gives you a negative 0. */ else if (*signp == '-') for (;; --end) { if (*end == decimal) --end; if (*end != '0') break; if (end == start) *signp = 0; } return start; } static int cvtl(long double number, int prec, int flags, char *signp, unsigned char fmtch, char *startp, char *endp) { char *p, *t; long double fract = 0; int dotrim, expcnt, gformat; int doextradps = 0; /* Do extra decimal places if the precision needs it */ int doingzero = 0; /* We're displaying 0.0 */ long double integer, tmp; if ((expcnt = isspeciall(number, startp))) return (expcnt); dotrim = expcnt = gformat = 0; /* fract = modfl(number, &integer); */ integer = number; /* get an extra slot for rounding. */ t = ++startp; p = endp - 1; if (integer) { int i, lp = NP, pt = MAXP; #ifndef FAST_LDOUBLE_CONVERSION long double oint = integer, dd = 1.0L; #endif if (integer > INVPREC) { integer *= PREC; while (lp >= 0) { if (integer >= pten[lp]) { expcnt += pt; integer *= ptenneg[lp]; #ifndef FAST_LDOUBLE_CONVERSION dd *= pten[lp]; #endif } pt >>= 1; lp--; } #ifndef FAST_LDOUBLE_CONVERSION integer = oint / dd; #else integer *= INVPREC; #endif } /* * Do we really need this ? */ for (i = 0; i < expcnt; i++) *p-- = '0'; } number = integer; fract = modfl(number, &integer); /* If integer is zero then we need to look at where the sig figs are */ if (integer < 1) { /* If fract is zero the zero before the decimal point is a sig fig */ if (fract == 0.0) doingzero = 1; /* If fract is non-zero all sig figs are in fractional part */ else doextradps = 1; } /* * get integer portion of number; put into the end of the buffer; the * .01 is added for modf(356.0 / 10, &integer) returning .59999999... */ for (; integer; ++expcnt) { tmp = modfl(integer * 0.1L, &integer); *p-- = tochar((int) ((tmp + .01L) * 10)); } switch (fmtch) { case 'f': /* reverse integer into beginning of buffer */ if (expcnt) for (; ++p < endp; *t++ = *p); else *t++ = '0'; /* * if precision required or alternate flag set, add in a * decimal point. */ if (prec || flags & ALT) *t++ = decimal; /* if requires more precision and some fraction left */ if (fract) { if (prec) do { fract = modfl(fract * 10.0L, &tmp); *t++ = tochar((int) tmp); } while (--prec && fract); if (fract) startp = my_roundl(fract, (int *) NULL, startp, t - 1, (char) 0, signp); } for (; prec--; *t++ = '0'); break; case 'e': case 'E': eformat: if (expcnt) { *t++ = *++p; if (prec || flags & ALT) *t++ = decimal; /* if requires more precision and some integer left */ for (; prec && ++p < endp; --prec) *t++ = *p; /* * if done precision and more of the integer component, * round using it; adjust fract so we don't re-round * later. */ if (!prec && ++p < endp) { fract = 0; startp = my_roundl((long double) 0.0L, &expcnt, startp, t - 1, *p, signp); } /* adjust expcnt for digit in front of decimal */ --expcnt; } /* until first fractional digit, decrement exponent */ else if (fract) { int lp = NP, pt = MAXP; #ifndef FAST_LDOUBLE_CONVERSION long double ofract = fract, dd = 1.0L; #endif expcnt = -1; if (fract < PREC) { fract *= INVPREC; while (lp >= 0) { if (fract <= ptenneg[lp]) { expcnt -= pt; fract *= pten[lp]; #ifndef FAST_LDOUBLE_CONVERSION dd *= pten[lp]; #endif } pt >>= 1; lp--; } #ifndef FAST_LDOUBLE_CONVERSION fract = ofract * dd; #else fract *= PREC; #endif } /* adjust expcnt for digit in front of decimal */ for ( /* expcnt = -1 */ ;; --expcnt) { fract = modfl(fract * 10.0L, &tmp); if (tmp) break; } *t++ = tochar((int) tmp); if (prec || flags & ALT) *t++ = decimal; } else { *t++ = '0'; if (prec || flags & ALT) *t++ = decimal; } /* if requires more precision and some fraction left */ if (fract) { if (prec) do { fract = modfl(fract * 10.0L, &tmp); *t++ = tochar((int) tmp); } while (--prec && fract); if (fract) startp = my_roundl(fract, &expcnt, startp, t - 1, (char) 0, signp); } /* if requires more precision */ for (; prec--; *t++ = '0'); /* unless alternate flag, trim any g/G format trailing 0's */ if (gformat && !(flags & ALT)) { while (t > startp && *--t == '0'); if (*t == decimal) --t; ++t; } t = exponentl(t, expcnt, fmtch); break; case 'g': case 'G': if (prec) { /* If doing zero and precision is greater than 0 count the * 0 before the decimal place */ if (doingzero) --prec; } else { /* a precision of 0 is treated as precision of 1 unless doing zero */ if (!doingzero) ++prec; } /* * ``The style used depends on the value converted; style e * will be used only if the exponent resulting from the * conversion is less than -4 or greater than the precision.'' * -- ANSI X3J11 */ if (expcnt > prec || (!expcnt && fract && fract < .0001)) { /* * g/G format counts "significant digits, not digits of * precision; for the e/E format, this just causes an * off-by-one problem, i.e. g/G considers the digit * before the decimal point significant and e/E doesn't * count it as precision. */ --prec; fmtch -= 2; /* G->E, g->e */ gformat = 1; goto eformat; } /* * reverse integer into beginning of buffer, * note, decrement precision */ if (expcnt) for (; ++p < endp; *t++ = *p, --prec); else *t++ = '0'; /* * if precision required or alternate flag set, add in a * decimal point. If no digits yet, add in leading 0. */ if (prec || flags & ALT) { dotrim = 1; *t++ = decimal; } else dotrim = 0; /* if requires more precision and some fraction left */ while (prec && fract) { fract = modfl(fract * 10.0L, &tmp); *t++ = tochar((int) tmp); /* If we're not adding 0s * or we are but they're sig figs: * decrement the precision */ if ((doextradps != 1) || ((int) tmp != 0)) { doextradps = 0; prec--; } } if (fract) startp = my_roundl(fract, (int *) NULL, startp, t - 1, (char) 0, signp); /* alternate format, adds 0's for precision, else trim 0's */ if (flags & ALT) for (; prec--; *t++ = '0'); else if (dotrim) { while (t > startp && *--t == '0'); if (*t != decimal) ++t; } } return t - startp; } #if 0 main() { static char buf[4096]; int i; cvtl(0.00000000000000000005, 4, 0, &softsign, 'G', buf, buf + sizeof(buf) - 1); printf("%s\n", buf + 1); printf("%.30LG\n", (long double) 234236723234234231235324.47239L); } #endif void x_ldout(long double param, int prec, xio_file stream) { static char buf[4095]; char softsign = 0; int l; if (param < 0) xio_putc('-', stream), param = -param; l = cvtl(param, prec, 0, &softsign, 'G', buf, buf + sizeof(buf)); /*printf("a:%s %i\n",buf+1, prec); */ buf[l + 2] = 0; l = strlen(buf + 1); if (buf[l] == '.') buf[l] = 0; /*printf("b:%s %i\n",buf+1, prec); */ xio_puts(buf + 1, stream); } #endif