fall back to e format if float is too big
[fw/sdcc] / device / lib / vprintf.c
1 /*-------------------------------------------------------------------------
2   vprintf.c - formatted output conversion
3  
4              Written By - Martijn van Balen aed@iae.nl (1999)
5              Added %f By - johan.knol@iduna.nl (2000)
6
7    This program is free software; you can redistribute it and/or modify it
8    under the terms of the GNU General Public License as published by the
9    Free Software Foundation; either version 2, or (at your option) any
10    later version.
11    
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16    
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20    
21    In other words, you are welcome to use, share and improve this program.
22    You are forbidden to forbid anyone else to use, share and improve
23    what you give them.   Help stamp out software-hoarding!  
24 -------------------------------------------------------------------------*/
25
26 /* this module uses some global variables instead function parameters, so: */
27 #ifdef SDCC_STACK_AUTO
28 #warning "this module cannot yet be use as a reentrant one"
29 #endif
30
31 #if defined(__ds390)
32 #define USE_FLOATS 1
33 #endif
34
35 #include <stdarg.h>
36 #include <string.h>
37 #include <ctype.h>
38 #include <stdio.h>
39
40 #define PTR value.p 
41
42 #ifdef SDCC_ds390
43 #define NULL_STRING "<NULL>"
44 #define NULL_STRING_LENGTH 6
45 #endif
46
47 /****************************************************************************/
48
49 //typedef char * ptr_t;
50 #define ptr_t char *
51
52 #ifdef toupper
53 #undef toupper
54 #endif
55
56 //#define toupper(c) ((c)&=~0x20)
57 #define toupper(c) ((c)&=0xDF)
58
59 typedef union
60 {
61   unsigned char  byte[5];
62   long           l;
63   unsigned long  ul;
64   float          f;
65   char           *p;
66 } value_t;
67
68
69 static code char memory_id[] = "IXCP-";
70
71 static ptr_t output_ptr;
72 static bit   output_to_string;
73 static bit   lower_case;
74 static bit   lsd;
75
76 /* this one NEEDS to be in data */
77 static data value_t value;
78
79 static unsigned char radix;
80
81 // jwk: TODO: this makes the whole dammed thing nonreentrent
82 static int charsOutputted;
83
84 /****************************************************************************/
85
86 static void output_char( char c ) reentrant
87 {
88   if (output_to_string)
89   {
90     *output_ptr++ = c;
91   }
92   else
93   {
94     putchar( c );
95   }
96   charsOutputted++;
97 }
98
99 /*--------------------------------------------------------------------------*/
100
101 static void output_digit( unsigned char n ) reentrant
102 {
103   output_char( n <= 9 ? '0'+n : 
104                (lower_case ? n+(char)('a'-10) : n+(char)('A'-10)) );
105 }
106
107 /*--------------------------------------------------------------------------*/
108
109 static void output_2digits( unsigned char b ) reentrant
110 {
111   output_digit( b>>4 );
112   output_digit( b&0x0F );
113 }
114         
115 /*--------------------------------------------------------------------------*/
116
117 static void calculate_digit( void )
118 {
119   unsigned char i;
120
121   for( i = 32; i != 0; i-- )
122   {
123 _asm
124   clr  c
125   mov  a,_value+0  
126   rlc  a
127   mov  _value+0,a
128   mov  a,_value+1
129   rlc  a
130   mov  _value+1,a
131   mov  a,_value+2
132   rlc  a
133   mov  _value+2,a
134   mov  a,_value+3
135   rlc  a
136   mov  _value+3,a
137   mov  a,_value+4
138   rlc  a
139   mov  _value+4,a
140 _endasm;
141
142     if (radix <= value.byte[4] )
143     {
144       value.byte[4] -= radix;
145       value.byte[0]++;
146     }
147   }
148 }
149
150 #if USE_FLOATS
151
152 /* This is a very inefficient but direct approach, since we have no math
153    library yet (e.g. log()).
154    It does most of the modifiers, but has some restrictions. E.g. the 
155    abs(float) shouldn't be bigger than an unsigned long (that's 
156    about 4294967295), but still makes it usefull for most real-life
157    applications.
158 */
159
160 #define DEFAULT_FLOAT_PRECISION 6
161
162 float output_floatE(float f, char decimals)
163 {
164   signed char exp;
165   char sign = '+';
166
167   if (f < 0) { f = -f; sign = '-'; }
168   for (exp = 0; f >= 10.0; exp++) f /=10.0;
169   for (       ; f < 1.0;   exp--) f *=10.0;
170   printf("%c%d.%d%fe%d\n", sign, decimals+2, decimals, f, exp); 
171
172
173 static void output_float (float f, unsigned char reqWidth, 
174                           signed char reqDecimals,
175                           bit left, bit zero, bit sign, bit space)
176 {
177   char negative=0;
178   unsigned long integerPart;
179   float decimalPart;
180   char fpBuffer[128];
181   char fpBI=0, fpBD;
182   unsigned char minWidth, i;
183
184   // save the sign
185   if (f<0) {
186     negative=1;
187     f=-f;
188   }
189
190   if (f>0x00ffffff) {
191     // this part is from Frank van der Hulst
192     signed char exp;
193     
194     for (exp = 0; f >= 10.0; exp++) f /=10.0;
195     for (       ; f < 1.0;   exp--) f *=10.0;
196
197     if (negative) {
198       putchar ('-');
199     } else {
200       if (sign) {
201         putchar ('+');
202       }
203     }
204     output_float(f, 0, reqDecimals, 0, 0, 0, 0);
205     putchar ('e');
206     if (exp<0) {
207       putchar ('-');
208       exp = -exp;
209     }
210     putchar ('0'+exp/10);
211     putchar ('0'+exp%10);
212     return;
213   }
214
215   // split the float
216   integerPart=f;
217   decimalPart=f-integerPart;
218
219   // fill the buffer with the integerPart (in reversed order!)
220   while (integerPart) {
221     fpBuffer[fpBI++]='0' + integerPart%10;
222     integerPart /= 10;
223   }
224   if (!fpBI) {
225     // we need at least a 0
226     fpBuffer[fpBI++]='0';
227   }
228
229   // display some decimals as default
230   if (reqDecimals==-1)
231     reqDecimals=DEFAULT_FLOAT_PRECISION;
232   
233   // fill buffer with the decimalPart (in normal order)
234   fpBD=fpBI;
235   if (i=reqDecimals /* that's an assignment */) {
236     do {
237       decimalPart *= 10.0;
238       // truncate the float
239       integerPart=decimalPart;
240       fpBuffer[fpBD++]='0' + integerPart;
241       decimalPart-=integerPart;
242     } while (--i);
243   }
244   
245   minWidth=fpBI; // we need at least these
246   minWidth+=reqDecimals?reqDecimals+1:0; // maybe these
247   if (negative || sign || space)
248     minWidth++; // and maybe even this :)
249   
250   if (!left && reqWidth>i) {
251     if (zero) {
252       if (negative) output_char('-');
253       else if (sign) output_char('+');
254       else if (space) output_char(' ');
255       while (reqWidth-->minWidth)
256         output_char ('0');
257     } else {
258       while (reqWidth-->minWidth)
259         output_char (' ');
260       if (negative) output_char('-');
261       else if (sign) output_char('+');
262       else if (space) output_char (' ');
263     }
264   } else {
265     if (negative) output_char('-');
266     else if (sign) output_char('+');
267     else if (space) output_char(' ');
268   }
269
270   // output the integer part
271   i=fpBI-1;
272   do {
273     output_char (fpBuffer[i]);
274   } while (i--);
275   
276   // ouput the decimal part
277   if (reqDecimals) {
278     output_char ('.');
279     i=fpBI;
280     while (reqDecimals--)
281       output_char (fpBuffer[i++]);
282   }
283
284   if (left && reqWidth>minWidth) {
285     while (reqWidth-->minWidth)
286       output_char(' ');
287   }
288 }
289 #endif
290
291 /*--------------------------------------------------------------------------*/
292
293 int vsprintf (const char *buf, const char *format, va_list ap)
294 {
295   static bit            left_justify;
296   static bit            zero_padding;
297   static bit            prefix_sign;
298   static bit            prefix_space;
299   static bit            signed_argument;
300   static bit            char_argument;
301   static bit            long_argument;
302   static bit            float_argument;
303
304   unsigned char  width;
305   signed char decimals;
306   unsigned char  length;
307   char           c;
308
309   // reset output chars
310   charsOutputted=0;
311
312   output_ptr = buf;
313   if ( !buf )
314   {
315     output_to_string = 0;
316   }
317   else
318   {
319     output_to_string = 1;
320   }
321
322 #ifdef SDCC_ds390
323   if (format==0) {
324     format=NULL_STRING;
325   }
326 #endif
327  
328   while( c=*format++ )
329   {
330     if ( c=='%' )
331     {
332       left_justify    = 0;
333       zero_padding    = 0;
334       prefix_sign     = 0;
335       prefix_space    = 0;
336       signed_argument = 0;
337       radix           = 0;
338       char_argument   = 0;
339       long_argument   = 0;
340       float_argument  = 0;
341       width           = 0;
342       decimals        = -1;
343
344 get_conversion_spec:
345
346       c = *format++;
347
348       if (c=='%') {
349         output_char(c);
350         continue;
351       }
352
353       if (isdigit(c)) {
354         if (decimals==-1) {
355           width = 10*width + (c - '0');
356           if (width == 0) {
357             /* first character of width is a zero */
358             zero_padding = 1;
359           }
360         } else {
361           decimals = 10*decimals + (c-'0');
362         }
363         goto get_conversion_spec;
364       }
365
366       if (c=='.') {
367         if (decimals=-1) decimals=0;
368         else 
369           ; // duplicate, ignore
370         goto get_conversion_spec;
371       }
372
373       lower_case = islower(c);
374       if (lower_case)
375       {
376         c = toupper(c);
377       }
378
379       switch( c )
380       {
381       case '-':
382         left_justify = 1;
383         goto get_conversion_spec;
384       case '+':
385         prefix_sign = 1;
386         goto get_conversion_spec;
387       case ' ':
388         prefix_space = 1;
389         goto get_conversion_spec;
390       case 'B':
391         char_argument = 1;
392         goto get_conversion_spec;
393       case 'L':
394         long_argument = 1;
395         goto get_conversion_spec;
396
397       case 'C':
398         output_char( va_arg(ap,int) );
399         break;
400
401       case 'S':
402         PTR = va_arg(ap,ptr_t);
403
404 #ifdef SDCC_ds390
405         if (PTR==0) {
406           PTR=NULL_STRING;
407           length=NULL_STRING_LENGTH;
408         } else {
409           length = strlen(PTR);
410         }
411 #else
412         length = strlen(PTR);
413 #endif
414         if ( ( !left_justify ) && (length < width) )
415         {
416           width -= length;
417           while( width-- != 0 )
418           {
419             output_char( ' ' );
420           }
421         }
422
423         while ( *PTR )
424           output_char( *PTR++ );
425
426         if ( left_justify && (length < width))
427         {
428           width -= length;
429           while( width-- != 0 )
430           {
431             output_char( ' ' );
432           }
433         }
434         break;
435
436       case 'P':
437         PTR = va_arg(ap,ptr_t);
438
439 #ifdef SDCC_ds390
440         output_char(memory_id[(value.byte[3] > 3) ? 4 : value.byte[3]] );
441         output_char(':');
442         output_char('0');
443         output_char('x');
444         output_2digits(value.byte[2]);
445         output_2digits(value.byte[1]);
446         output_2digits(value.byte[0]);
447 #else
448         output_char( memory_id[(value.byte[2] > 3) ? 4 : value.byte[2]] );
449         output_char(':');
450         output_char('0');
451         output_char('x');
452         if ((value.byte[2] != 0x00 /* DSEG */) && 
453             (value.byte[2] != 0x03 /* SSEG */))
454           output_2digits( value.byte[1] );
455         output_2digits( value.byte[0] );
456 #endif
457         break;
458
459       case 'D':
460       case 'I':
461         signed_argument = 1;
462         radix = 10;
463         break;
464
465       case 'O':
466         radix = 8;
467         break;
468
469       case 'U':
470         radix = 10;
471         break;
472
473       case 'X':
474         radix = 16;
475         break;
476
477       case 'F':
478         float_argument=1;
479         break;
480         
481       default:
482         // nothing special, just output the character
483         output_char( c );
484         break;
485       }
486
487       if (float_argument) {
488         value.f=va_arg(ap,float);
489 #if !USE_FLOATS
490         PTR="<NO FLOAT>";
491         while (c=*PTR++)
492           output_char (c);
493         // treat as long hex
494         //radix=16;
495         //long_argument=1;
496         //zero_padding=1;
497         //width=8;
498 #else
499         // ignore b and l conversion spec for now
500         output_float(value.f, width, decimals, left_justify, zero_padding, 
501                      prefix_sign, prefix_space);
502 #endif
503       } else if (radix != 0)
504       {
505         // Apperently we have to output an integral type
506         // with radix "radix"
507
508         // store value in byte[0] (LSB) ... byte[3] (MSB)
509         if (char_argument)
510         {
511           value.l = va_arg(ap,char);
512           if (!signed_argument)
513           {
514             value.byte[1] = 0x00;
515             value.byte[2] = 0x00;
516             value.byte[3] = 0x00;
517           }
518         }
519         else if (long_argument)
520         {
521           value.l = va_arg(ap,long);
522         }
523         else // must be int
524         {
525           value.l = va_arg(ap,int);
526           if (!signed_argument)
527           {
528             value.byte[2] = 0x00;
529             value.byte[3] = 0x00;
530           }
531         }
532
533         if ( signed_argument )
534         {
535           if (value.l < 0)
536             value.l = -value.l;
537           else
538             signed_argument = 0;
539         }
540
541         length=0;
542         lsd = 1;
543
544         //jwk20000814: do this at least once, e.g.: printf ("%d", (int)0);
545         do {
546           value.byte[4] = 0;
547           calculate_digit();
548
549 _asm
550   jb   _lsd,1$
551   pop  b                ; b = <lsd>
552   mov  a,_value+4       ; a = <msd>
553   swap a
554   orl  b,a              ; b = <msd><lsd>
555   push b
556   sjmp 2$
557 1$:
558   mov  a,_value+4       ; a = <lsd>
559   push acc
560 2$:
561 _endasm;
562
563           length++;
564           lsd = ~lsd;
565         } while( (value.byte[0] != 0) || (value.byte[1] != 0) ||
566                  (value.byte[2] != 0) || (value.byte[3] != 0) );
567         
568         if (width == 0)
569         {
570           // default width. We set it to 1 to output
571           // at least one character is case the value itself
572           // is zero (i.e. length==0)
573           width=1;
574         }
575
576         /* prepend spaces if needed */
577         if (!zero_padding)
578         {
579           while ( width > length+1 )
580           {
581             output_char( ' ' );
582             width--;
583           }
584         }
585
586         if (signed_argument) // this now means the original value was negative
587         {
588           output_char( '-' );
589           // adjust width to compensate for this character
590           width--;
591         }
592         else if (length != 0)
593         {
594           // value > 0
595           if (prefix_sign)
596           {
597             output_char( '+' );
598             // adjust width to compensate for this character
599             width--;
600           }
601           else if (prefix_space)
602           {
603             output_char( ' ' );
604             // adjust width to compensate for this character
605             width--;
606           }
607         }
608
609         /* prepend zeroes/spaces if needed */
610         while ( width-- > length )
611         {
612           output_char( zero_padding ? '0' : ' ' );
613         }
614
615         /* output the digits */
616         while( length-- )
617         {
618           lsd = ~lsd;
619
620 _asm
621   jb   _lsd,3$
622   pop  acc              ; a = <msd><lsd>
623   nop                   ; to disable the "optimizer"
624   push acc
625   swap a
626   anl  a,#0x0F          ; a = <msd>
627   sjmp 4$
628 3$:
629   pop  acc
630   anl  a,#0x0F          ; a = <lsd>
631 4$:
632   mov  _value+4,a
633 _endasm;
634
635           output_digit( value.byte[4] );
636         }
637       }
638     }
639     else
640     {
641       // nothing special, just output the character
642       output_char( c );
643     }
644   }
645        
646   // Copy \0 to the end of buf
647   // Modified by JB 17/12/99
648   if (output_to_string) {
649     output_char(0);
650     return charsOutputted-1;
651   } else {
652     return charsOutputted;
653   }
654 }
655
656 /*--------------------------------------------------------------------------*/
657
658 int vprintf (const char *format, va_list ap)
659 {
660   return vsprintf( 0, format, ap );
661 }