/* Copyright 1996 Acorn Computers Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* fpprintf.c: ANSI draft (X3J11 Oct 86) part of section 4.9 code */ /* Copyright (C) Codemist Ltd., 1988 */ /* version 0.01c */ /* Full entrypoints (that support floating point conversions) live in this */ /* file so that the cutdown code can be used when only integer formats are */ /* being used. */ #define __system_io 1 /* makes stdio.h declare more */ #include "hostsys.h" #include "kernel.h" #include <stdio.h> #include <stdarg.h> #include <string.h> #include <math.h> /* fpprintf wants to know about the system part of the FILE descriptor, but it doesn't need to know about _extradata */ typedef struct __extradata {void *dummy;} __extradata; /* Beware the following type and extern must match the one in printf.c */ typedef int (*fp_print)(int ch, double *d, char buff[], int flags, char **lvprefix, int *lvprecision, int *lvbefore_dot, int *lvafter_dot); extern int __vfprintf(FILE *fp, const char *fmt, va_list args, fp_print fn, int lf_terminates); /* The following macros must match those in printf.c: */ #define _LJUSTIFY 01 #define _SIGNED 02 #define _BLANKER 04 #define _VARIANT 010 #define _PRECGIVEN 020 #define _LONGSPECIFIER 040 #define _SHORTSPEC 0100 #define _PADZERO 0200 /* *** DEPRECATED FEATURE *** */ #define _FPCONV 0400 #ifndef NO_FLOATING_POINT #ifdef IEEE #define FLOATING_WIDTH 17 #else #define FLOATING_WIDTH 18 /* upper bound for sensible precision */ #endif static int fp_round(char buff[], int len) /* round (char form of) FP number - return 1 if carry, 0 otherwise */ /* Note that 'len' should be <= 20 - see fp_digits() */ /* The caller ensures that buff[0] is always '0' so that carry is simple */ /* However, beware that this routine does not re-ensure this if carry!! */ { int ch; char *p = &buff[len]; if ((ch = *p)==0) return 0; /* at end of string */ if (ch < '5') return 0; /* round downwards */ if (ch == '5') /* the dodgy case */ { char *p1; for (p1 = p; (ch = *++p1)=='0';); if (ch==0) return 0; /* .5 ulp exactly */ } for (;;) { ch = *--p; if (ch=='9') *p = '0'; else { *p = ch + 1; break; } } if (buff[0]!='0') /* caused by rounding */ { int w; /* renormalize the number */ for (w=len; w>=0; w--) buff[w+1] = buff[w]; return 1; } return 0; } #ifdef HOST_HAS_BCD_FLT static int fp_digits(char *buff, double d) /* This routine turns a 'double' into a character string representation of */ /* its mantissa and returns the exponent after converting to base 10. */ /* It guarantees that buff[0] = '0' to ease problems connected with */ /* rounding and the like. See also comment at first call. */ /* Use FPE2 convert-to-packed-decimal feature to do most of the work */ /* The sign of d is returned in the LSB of x, and x has to be halved to */ /* obtain the 'proper' value it needs. */ { unsigned int a[3], w, d0, d1, d2, d3; int x, i; _stfp(d, a); w = a[0]; /* I allow for a four digit exponent even though sensible values can */ /* only extend to 3 digits. I call this caution! */ if ((w & 0x0ffff000) == 0x0ffff000) { x = 999; /* Infinity will print as 1.0e999 here */ /* as will NaNs */ for (i = 0; i<20; i++) buff[i] = '0'; buff[1] = '1'; } else { d0 = (w>>24) & 0xf; d1 = (w>>20) & 0xf; d2 = (w>>16) & 0xf; d3 = (w>>12) & 0xf; x = ((d0*10 + d1)*10 + d2)*10 + d3; if (w & 0x40000000) x = -x; buff[0] = '0'; for (i = 1; i<4; i++) buff[i] = '0' + ((w>>(12-4*i)) & 0xf); w = a[1]; for (i = 4; i<12; i++) buff[i] = '0' + ((w>>(44-4*i)) & 0xf); w = a[2]; for (i = 12; i<20; i++) buff[i] = '0' + ((w>>(76-4*i)) & 0xf); } buff[20] = 0; x = x<<1; if (a[0] & 0x80000000) x |= 1; return x; } #else /* HOST_HAS_BCD_FLT */ static void pr_dec(int d, char *p, int n) /* print d in decimal, field width n */ { /* store result at p. arg small & +ve. */ while ((n--)>0) { int dDiv10 = _kernel_sdiv10(d); *p-- = '0' + d - dDiv10 * 10; d = dDiv10; } } static int fp_digits(char *buff, double d) /* This routine turns a 'double' into a character string representation of */ /* its mantissa and returns the exponent after converting to base 10. */ /* For this we use one-and-a-half precision done by steam */ /* It guarantees that buff[0] = '0' to ease problems connected with */ /* rounding and the like. See also comment at first call. */ { int hi, mid, lo, dx, sign = 0; if (d < 0.0) d = -d, sign = 1; if (d==0.0) { hi = mid = lo = 0; dx = -5; } else { double d1, d2, d2low, d3, d3low, scale; int w, bx; d1 = frexp(d, &bx); /* exponent & mantissa */ /* fraction d1 is in range 0.5 to 1.0 */ /* remember log_10(2) = 0.3010! */ dx = (301*bx - 5500)/1000; /* decimal exponent */ scale = ldexp(1.0, dx-bx); w = dx; if (w < 0) { w = -w; d3 = 0.2; } else d3 = 5.0; if (w!=0) for (;;) /* scale *= 5**dx */ { if((w&1)!=0) { scale *= d3; if (w==1) break; } d3 *= d3; w = w >> 1; } d2 = d1/scale; /* the initial value selected for dx was computed on the basis of the */ /* binary exponent in the argument value - now I refine dx. If the value */ /* produced to start with was accurate enough I will hardly have to do */ /* any work here. */ while (d2 < 100000.0) { d2 *= 10.0; dx -= 1; scale /= 10.0; } while (d2 >= 1000000.0) { d2 /= 10.0; dx += 1; scale *= 10.0; } hi = (int) d2; for (;;) /* loop to get hi correct */ { d2 = ldexp((double) hi, dx-bx); /* at worst 24 bits in d2 here */ /* even with IBM fp numbers there is no accuracy lost */ d2low = 0.0; w = dx; if (w<0) { w = -w; /* the code here needs to set (d3, d3low) to a one-and-a-half precision */ /* version of the constant 0.2. */ d3 = 0.2; d3low = 0.0; _fp_normalize(d3, d3low); d3low = (1.0 - 5.0*d3)/5.0; } else { d3 = 5.0; d3low = 0.0; } /* Now I want to compute d2 = d2 * d3**dx in extra precision arithmetic */ if (w!=0) for (;;) { if ((w&1)!=0) { d2low = d2*d3low + d2low*(d3 + d3low); d2 *= d3; _fp_normalize(d2, d2low); if (w==1) break; } d3low *= (2.0*d3 + d3low); d3 *= d3; _fp_normalize(d3, d3low); w = w>>1; } if (d2<=d1) break; hi -= 1; /* hardly ever happens */ } d1 -= d2; /* for this to be accurate d2 MUST be less */ /* than d1 so that d1 does not get shifted */ /* prior to the subtraction. */ d1 -= d2low; d1 /= scale; /* Now d1 is a respectably accurate approximation for (d - (double)hi) */ /* scaled by 10**dx */ d1 *= 1000000.0; mid = (int) d1; d1 = 1000000.0 * (d1 - (double) mid); lo = (int) d1; /* Now some postnormalization on the integer results */ /* If I do things this way the code will work if (int) d rounds or */ /* truncates. */ while (lo<0) { lo += 1000000; mid -= 1; } while (lo>=1000000) { lo -= 1000000; mid += 1; } while (mid<0) { mid += 1000000; hi -= 1; } while (mid>=1000000) { mid -= 1000000; hi += 1; } if (hi<100000) { int loDiv100000; int midDiv100000 = _kernel_sdiv(100000, mid); hi = 10*hi + midDiv100000; loDiv100000 = _kernel_sdiv(100000, lo); mid = 10*(mid - midDiv100000 * 100000) + loDiv100000; lo = 10*(lo - loDiv100000 * 100000); dx -= 1; } else if (hi >= 1000000) { int midDiv10; int hiDiv10 = _kernel_sdiv10(hi); mid += 1000000*(hi - hiDiv10 * 10); hi = hiDiv10; midDiv10 = _kernel_sdiv10(mid); lo += 1000000*(mid - midDiv10 * 10); mid = midDiv10; lo = _kernel_sdiv10(lo + 5); /* pretence at rounding */ dx += 1; } } /* Now my result is in three 6-digit chunks (hi, mid, lo) */ /* The number of characters put in the buffer here MUST agree with */ /* FLOATING_PRECISION. This version is for FLOATING_PRECISION = 18. */ buff[0] = '0'; pr_dec(hi, &buff[6], 6); pr_dec(mid, &buff[12], 6); pr_dec(lo, &buff[18], 6); buff[19] = '0'; buff[20] = 0; return ((dx+5)<<1) | sign; } #endif /* HOST_HAS_BCD_FLT */ static int fp_addexp(char *buff, int len, int dx, int ch) { int dxDiv10; buff[len++] = ch; if (dx<0) { dx = -dx; buff[len++] = '-'; } else buff[len++] = '+'; if (dx >= 1000) { int dxDiv1000 = _kernel_sdiv(1000, dx); buff[len++] = '0' + dxDiv1000; dx = dx - dxDiv1000 * 1000; } if (dx >= 100) { int dxDiv100 = _kernel_sdiv(100, dx); buff[len++] = '0' + dxDiv100; dx = dx - dxDiv100 * 100; } dxDiv10 = _kernel_sdiv10(dx); buff[len++] = '0' + dxDiv10; buff[len++] = '0' + dx - dxDiv10 * 10; return len; } #define fp_insert_(buff, pos, c) \ { int w; \ for (w=0; w<=pos; w++) buff[w] = buff[w+1]; \ buff[pos+1] = c; } static int fp_display(int ch, double *lvd, char buff[], int flags, char **lvprefix, int *lvprecision, int *lvbefore_dot, int *lvafter_dot) { int len = 0; double d = *lvd; switch (ch) { /* The following code places characters in the buffer buff[] */ /* to print the floating point number given as d. */ /* It is given flags that indicate what format is required and how */ /* many digits precision are needed. */ /* Floating point values are ALWAYS converted into 18 decimal digits */ /* (the largest number possible reasonable) to start with, and rounding */ /* is then performed on this character representation. This is intended */ /* to avoid all possibility of boundary effects when numbers like .9999 */ /* are being displayed. */ case 'f': { int dx = fp_digits(buff, d); if (dx & 1) *lvprefix = "-"; else *lvprefix = (flags&_SIGNED) ? "+" : (flags&_BLANKER) ? " " : ""; dx = (dx & ~1)/2; if (dx<0) /* insert leading zeros */ { dx = -dx; if (dx>*lvprecision+1) { len = 0; /* prints as zero */ buff[len++] = '0'; buff[len++] = *decimal_point; *lvafter_dot = *lvprecision; } else { len = *lvprecision - dx + 2; if (len > FLOATING_WIDTH + 1) { *lvafter_dot = len - (FLOATING_WIDTH + 2); len = FLOATING_WIDTH+2; } if (fp_round(buff, len)) dx--, len++; /* dx-- because of negation */ /* unfortunately we may have dx=0 now because of the rounding */ if (dx==0) { buff[0] = buff[1]; buff[1] = *decimal_point; } else if (dx==1) { int w; for(w=len; w>0; w--) buff[w+1] = buff[w]; len += 1; buff[0] = '0'; buff[1] = *decimal_point; } else { int w; for(w=len; w>0; w--) buff[w+2] = buff[w]; len += 2; buff[0] = '0'; buff[1] = *decimal_point; buff[2] = '<'; *lvbefore_dot = dx - 1; } } if (*lvafter_dot>0) buff[len++] = '>'; } else /* dx >= 0 */ { len = dx + *lvprecision + 2; if (len > FLOATING_WIDTH+1) { len = FLOATING_WIDTH+2; /* Seemingly endless fun here making sure that the number is printed */ /* without truncation or loss even if it is very big & hence needs very */ /* many digits. Only the first few digits will be significant, of course */ /* but the C specification forces me to print lots of insignificant ones */ /* too. Use flag characters '<' and '>' plus variables (before_dot) and */ /* (after_dot) to keep track of what has happened. */ if (fp_round(buff, len)) dx++, len++; /* number extended */ if (dx<len-1) { fp_insert_(buff, dx, *decimal_point); *lvafter_dot = dx + *lvprecision - FLOATING_WIDTH; if (*lvafter_dot!=0) buff[len++] = '>'; } else { int w; for (w=0; w<len-1; w++) buff[w] = buff[w+1]; buff[len-1] = '<'; *lvbefore_dot = dx - len + 2; buff[len++] = *decimal_point; if (*lvprecision!=0) { *lvafter_dot = *lvprecision; buff[len++] = '>'; } } } else { if (fp_round(buff, len)) dx++, len++; /* number extended */ fp_insert_(buff, dx, *decimal_point); } } if ((*lvprecision==0) && ((flags&_VARIANT)==0)) len -= 1; } break; default: /* case 'g': case 'G': */ { int dx = fp_digits(buff, d); if (dx & 1) *lvprefix = "-"; else *lvprefix = (flags&_SIGNED) ? "+" : (flags&_BLANKER) ? " " : ""; dx = (dx & ~1)/2; if (*lvprecision<1) *lvprecision = 1; len = (*lvprecision>FLOATING_WIDTH) ? FLOATING_WIDTH+1 : *lvprecision + 1; dx += fp_round(buff, len); /* now choose either 'e' or 'f' format, depending on which will lead to */ /* the more compact display of the number. */ if ((dx>=*lvprecision) || (dx<-4)) { buff[0] = buff[1]; /* e or E format */ buff[1] = *decimal_point; } else { ch = 'f'; /* uses f format */ if (dx>=0) /* Insert a decimal point at the correct place for 'f' format printing */ { fp_insert_(buff, dx, *decimal_point); } else /* If the exponent is negative the required format will be something */ /* like 0.xxxx, 0.0xxx or 0.00xx and I need to lengthen the buffer */ { int w; dx = -dx; for (w=len; w>=0; w--) buff[w+dx] = buff[w]; len += dx; for(w=0; w<=dx; w++) buff[w] = '0'; buff[1] = *decimal_point; } } if((flags&_VARIANT)==0) /* trailing 0? */ { *lvafter_dot = -1; if (buff[len]!=*decimal_point) while (buff[len-1]=='0') len--; if (buff[len-1]==*decimal_point) len--; } else /* Allow for the fact that the specified precision may be very large in */ /* which case I put in trailing zeros via the marker character '>' and a */ /* count (after_dot). Not applicable unless the '#' flag has been given */ /* since without '#' trailing zeros in the fraction are killed. */ { if (*lvprecision>FLOATING_WIDTH) { *lvafter_dot = *lvprecision - FLOATING_WIDTH; buff[len++] = '>'; } } if (ch!='f') /* sets 'f' if it prints in f format */ /* and 'e' or 'E' if in e format. */ len = fp_addexp(buff, len, dx, ch + ('e'-'g')); } break; case 'e': case 'E': { int dx = fp_digits(buff, d); if (dx & 1) *lvprefix = "-"; else *lvprefix = (flags&_SIGNED) ? "+" : (flags&_BLANKER) ? " " : ""; dx = (dx & ~1)/2; if (*lvprecision>FLOATING_WIDTH) { *lvafter_dot = *lvprecision - FLOATING_WIDTH; *lvprecision = FLOATING_WIDTH; } len = *lvprecision + 2; dx += fp_round(buff, len); buff[0] = buff[1]; if ((*lvprecision==0) && !(flags&_VARIANT)) len = 1; else buff[1] = *decimal_point; /* Deal with trailing zeros for excessive precision requests */ if (*lvafter_dot>0) buff[len++] = '>'; len = fp_addexp(buff, len, dx, ch); } break; } return len; } #else /* NO_FLOATING_POINT */ static int fp_display(int ch, double *lvd, char buff[], int flags, char **lvprefix, int *lvprecision, int *lvbefore_dot, int *lvafter_dot) { #error NO_FLOATING_POINT option mangled } #endif /* NO_FLOATING_POINT */ int fprintf(FILE *fp, const char *fmt, ...) { va_list a; int n; va_start(a, fmt); n = __vfprintf(fp, fmt, a, fp_display, 0); va_end(a); return n; } int printf(const char *fmt, ...) { va_list a; int n; va_start(a, fmt); n = __vfprintf(stdout, fmt, a, fp_display, 0); va_end(a); return n; } int sprintf(char *buff, const char *fmt, ...) { FILE hack; va_list a; /*************************************************************************/ /* Note that this code interacts in a dubious way with the putc macro. */ /*************************************************************************/ int length; va_start(a, fmt); memclr(&hack, sizeof(FILE)); hack.__flag = _IOSTRG+_IOWRITE; hack.__ptr = (unsigned char *)buff; hack.__ocnt = 0x7fffffff; length = __vfprintf(&hack, fmt, a, fp_display, 0); putc(0,&hack); va_end(a); return(length); } int vfprintf(FILE *p, const char *fmt, va_list args) { return __vfprintf(p, fmt, args, fp_display, 0); } int vprintf(const char *fmt, va_list a) { return __vfprintf(stdout, fmt, a, fp_display, 0); } int vsprintf(char *buff, const char *fmt, va_list a) { FILE hack; /*************************************************************************/ /* Note that this code interacts in a dubious way with the putc macro. */ /*************************************************************************/ int length; memclr(&hack, sizeof(FILE)); hack.__flag = _IOSTRG+_IOWRITE; hack.__ptr = (unsigned char *)buff; hack.__ocnt = 0x7fffffff; length = __vfprintf(&hack, fmt, a, fp_display, 0); putc(0,&hack); return(length); } /* End of fpprintf.c */