1 /* //////////////////////////////////////////////////////////////////////
2 // ftos.cc
3 //
4 // Copyright (c) 1996-2003 Bryce W. Harrington [bryce at osdl dot org]
5 //
6 //-----------------------------------------------------------------------
7 // License: This code may be used by anyone for any purpose
8 // so long as the copyright notices and this license
9 // statement remains attached.
10 //-----------------------------------------------------------------------
11 //
12 // string ftos(double val[, char mode[, int sigfig[, int precision[, int options]]]])
13 //
14 // DESCRIPTION
15 // This routine is intended to replace the typical use of sprintf for
16 // converting floating point numbers into strings.
17 //
18 // To one-up sprintf, an additional mode was created - 'h' mode -
19 // which produces numbers in 'engineering notation' - exponents are
20 // always shown in multiples of 3. To non-engineers this mode is
21 // probably irrelevant, but for engineers (and scientists) it is SOP.
22 //
23 // One other new feature is an option to use 'x10^' instead of the
24 // conventional 'E' for exponental notation. This is entirely for
25 // aesthetics since numbers in the 'x10^' form cannot be used as
26 // inputs for most programs.
27 //
28 // For most cases, the routine can simply be used with the defaults
29 // and acceptable results will be produced. No fill zeros or trailing
30 // zeros are shown, and exponential notation is only used for numbers
31 // greater than 1e6 or less than 1e-3.
32 //
33 // The one area where sprintf may surpass this routine is in width control.
34 // No provisions are made in this routine to restrict a number to a
35 // certain number of digits (thus allowing the number to be constrained
36 // to an 8 space column, for instance.) Along with this, it does not
37 // support pre-padding a number with zeros (e.g., '5' -> '0005') and will
38 // not post-pad a number with spaces (i.e., allow left-justification.)
39 //
40 // If width control is this important, then the user will probably want to
41 // use the stdio routines, which really is well suited for outputting
42 // columns of data with a brief amount of code.
43 //
44 // PARAMETERS
45 // val - number to be converted
46 // mode - can be one of four possible values. Default is 'g'
47 //
48 // e: Produces numbers in scientific notation. One digit
49 // is shown on the left side of the decimal, the rest
50 // on the right, and the exponential is always shown.
51 // EXAMPLE: 1.04e-4
52 //
53 // f: Produces numbers with fixed format. Number is shown
54 // exact, with no exponent.
55 // EXAMPLE: 0.000104
56 //
57 // g: If val is greater than 1e6 or less than 1e-3 it will
58 // be shown in 'e' format, otherwise 'f' format will be
59 // used.
60 //
61 // h: Produces numbers in engineering format. Result is
62 // identical to 'f' format for numbers between 1 and
63 // 1e3, otherwise, the number is shown such that it
64 // always begins with a nonzero digit on the left side
65 // (unless number equals zero), and the exponential is
66 // a multiple of 3.
67 // EXAMPLE: 104e-6
68 //
69 // If the mode is expressed as a capital letter (e.g., 'F')
70 // then the exponential part of the number will also be
71 // capitalized (e.g., '1E6' or '1X10^6'.)
72 //
73 // sigfig - the number of significant figures. These are the digits
74 // that are "retained". For example, the following numbers
75 // all have four sigfigs:
76 // 1234 12.34 0.0001234 1.234e-10
77 // the last digit shown will be rounded in the standard
78 // manner (down if the next digit is less than 5, up otherwise.)
79 //
80 // precision - the number of digits to show to the right of the decimal.
81 // For example, all of the following numbers have precisions
82 // of 2:
83 // 1234.00 12.34 0.00 1.23e-10 123.40e-12
84 //
85 // options - several options are allowed to control the look of the
86 // output.
87 //
88 // FORCE_DECIMAL - require the decimal point to be shown for
89 // numbers that do not have any fractional digits (or that
90 // have a precision set to zero)
91 // EXAMPLE: 1.e6
92 // FORCE_EXP_ZERO - pad the 10's zero in exponent if necessary
93 // EXAMPLE: 1e06
94 // FORCE_HUNDRED_EXP_ZERO - pad the 100's zero in exponent if
95 // necessary. Also pads 10's zero in exponent if necessary.
96 // EXAMPLE: 1e006
97 // FORCE_EXP_PLUS - show the '+' in the exponent if exponent
98 // is used.
99 // EXAMPLE: 1e+6
100 // FORCE_EXP - force the output to display the exponent
101 // EXAMPLE: 0e0
102 // FORCE_X10 - use x10^ instead of E
103 // EXAMPLE: 1x10^6
104 // FORCE_PLUS - force output of the '+' for positive numbers
105 // EXAMPLE: +1e6
106 //
107 // Options can be combined using the usual OR method. For
108 // example,
109 //
110 // ftos(123.456, 'f', -1, -1, FORCE_PLUS | FORCE_X10 | FORCE_EXP)
111 //
112 // gives "+123.456x10^0"
113 //
114 // RETURN VALUE
115 // The string representation of the number is returned from the routine.
116 // The ANSI C++ Standard "string" class was used for several important
117 // reasons. First, because the string class manages it's own space, the
118 // ftos routine does not need to concern itself with writing to unallocated
119 // areas of memory or with handling memory reallocation internally. Second,
120 // it allows return of an object, not a pointer to an object; this may not
121 // be as efficient, but it is cleaner and safer than the alternative. Third,
122 // the routine's return value can be directly assigned to a variable, i.e.
123 // string var = ftos(3.1415);
124 // which makes code much easier to comprehend and modify.
125 //
126 // Internally, the ftos routine uses fairly typical string operators (=, +=,
127 // +, etc.) which pretty much any other flavor of string class will define as
128 // well. Thus if one does not have access to the ANSI C++ Standard string
129 // class, the user can substitute another with little difficulty. (If the
130 // alternate class is not named "string" then redefine "string" to whatever
131 // you wish to use. For example,
132 // #define string CString
133 //
134 // November 1996 - Bryce Harrington
135 // Created ftoa and ftos
136 //
137 // December 1996 - Bryce Harrington
138 // Added engineering notation mode, added sigfig capability, added
139 // significant debug code, added options, thoroughly debugged and
140 // tested the code.
141 //
142 //
143 // June 1999 - Bryce Harrington
144 // Modified to run on Linux for WorldForge
145 //
146 // March 2003 - Bryce Harrington
147 // Removed DTAG() macros - use of fprintf(stderr,...) instead
148 // Broke out round/itos/ftos into separate files
149 // Removed curses bits
150 //
151 /////////////////////////////////////////////////////////////////////// */
153 #include <string>
155 // This is the routine used for converting a floating point into a string
156 // This may be included in stdlib.h on some systems and may conflict.
157 // Let me know your system & etc. so I can properly #ifdef this, but
158 // try commenting the following four lines out if you run into conflicts.
159 // extern "C" {
160 // char*
161 // ecvt (double val, size_t ndigit, int *decpt, int *sign);
162 // }
164 using namespace std;
166 #ifndef HAS_ECVT
167 #include <cstdio>
168 #include <glib.h>
169 #endif
172 #include "ftos.h"
174 #include <iostream>
176 // This routine counts from the end of a string like '10229000' to find the index
177 // of the first non-'0' character (5 would be returned for the above number.)
178 int countDigs(char *p)
179 {
180 int length =0;
181 while (*(p+length)!='\0') length++; // Count total length
182 while (length>0 && *(p+length-1)=='0') length--; // Scan backwards for a non-'0'
183 return length;
184 }
186 // This routine determines how many digits make up the left hand
187 // side of the number if the abs value of the number is greater than 1, or the
188 // digits that make up the right hand side if the abs value of the number
189 // is between 0 and 1. Returns 1 if v==0. Return value is positive for numbers
190 // greater than or equal to 1, negative for numbers less than 0.1, and zero for
191 // numbers between 0.1 and 1.
192 int countLhsDigits(double v)
193 {
194 if (v<0) v = -v; // Take abs value
195 else if (v==0) return 1; // Special case if v==0
197 int n=0;
198 for (; v<0.1; v*=10) // Count digits on right hand side (l.t. 0.1)
199 { n--; }
200 for (; v>=1; v/=10) // Count digits on left hand side (g.e. 1.0)
201 { n++; }
202 return n;
203 }
205 // This is the routine that does the work of converting the number into a string.
206 string ftos(double val, char mode, int sigfig, int precision, int options)
207 {
208 // Parse the options to a more usable form
209 // These options allow the user to control some of the ornaments on the
210 // number that is output. By default they are all false. Turning them
211 // on helps to "fix" the format of the number so it lines up in columns
212 // better.
213 // - require the decimal point to be shown for numbers that do not have
214 // any fractional digits (or that have a precision set to zero
215 bool forceDecimal = (options & FORCE_DECIMAL);
216 // - show the 10's and 100's zero in exponent
217 bool forceExpZero = (options & FORCE_EXP_ZERO);
218 bool forceHundredExpZero = (options & FORCE_HUNDRED_EXP_ZERO);
219 // - show the '+' in the exponent if exponent is used
220 bool forceExpPlus = (options & FORCE_EXP_PLUS);
221 // - force the output to display the exponent
222 bool forceExponent = (options & FORCE_EXP);
223 // - use x10^ instead of E
224 bool forcex10 = (options & FORCE_X10);
225 // - force output of the '+' for positive numbers
226 bool forcePlus = (options & FORCE_PLUS);
228 #ifdef DEBUG
229 fprintf(stderr, "Options: ");
230 fprintf(stderr, " %4s = %s ", "x10", (forcex10 ? "on" : "off" ));
231 fprintf(stderr, " %4s = %s ", ".", (forceDecimal ? "on" : "off" ));
232 fprintf(stderr, " %4s = %s ", "e0", (forceExpZero ? "on" : "off" ));
233 fprintf(stderr, " %4s = %s ", "e00", (forceHundredExpZero ? "on" : "off" ));
234 fprintf(stderr, " %4s = %s ", "e+", (forceExpPlus ? "on" : "off" ));
235 fprintf(stderr, " %4s = %s ", "e", (forceExponent ? "on" : "off" ));
236 fprintf(stderr, " %4s = %s \n", "+#", (forcePlus ? "on" : "off" ));
237 #endif
239 // - exponent usage
240 bool useExponent = false;
242 // Determine the case for the 'e' (if used)
243 char E = (forcex10)? 'x' : 'e';
244 if (g_ascii_isupper(mode)) {
245 E = g_ascii_toupper(E);
246 mode = g_ascii_tolower(mode);
247 }
249 // Determine how many decimals we're interested in
250 int L = countLhsDigits(val);
252 #ifdef DEBUG
253 fprintf(stderr, "*** L is %s\n", itos(L).c_str());
254 #endif
256 int count = 0;
257 if (sigfig==0) // bad input - don't want any sigfigs??!!
258 return "";
259 else if (precision>=0) { // Use fixed number of decimal places
260 count = precision;
261 if (mode == 'e') count += 1;
262 else if (mode == 'f') count += L;
263 else if (mode == 'g') count += (L>6 || L<-3)? 1 : L;
264 else if (mode == 'h') count += (L>0)? ((L-1)%3+1) : (L%3+3);
265 if (sigfig>0) count = (sigfig > count)? count : sigfig; // Use sigfig # if it means more decimal places
266 }
267 else if (sigfig>0) // Just use sigfigs
268 count = sigfig;
269 else // prec < 0 and sigfig < 0
270 count = 10;
271 #ifdef DEBUG
272 fprintf(stderr, "*** count is %s\n", itos(count).c_str());
273 #endif
275 // Get number's string rep, sign, and exponent
276 int sign = 0;
277 int decimal=0;
279 #ifdef HAS_ECVT
280 char *p = ecvt(val, count, &decimal, &sign);
281 #else
282 char *p = (char *) g_strdup_printf("%.0f", val);
283 // asprintf(&p, "%.0f", val);
284 #endif
286 #ifdef DEBUG
287 fprintf(stderr, "*** string rep is %s\n", p);
288 fprintf(stderr, "*** decimal is %s\n", itos(decimal).c_str());
289 fprintf(stderr, "*** sign is %s\n", itos(sign).c_str());
290 #endif
292 // Count the number of relevant digits in the resultant number
293 int dig = countDigs(p);
294 if (dig < sigfig) dig = sigfig;
296 #ifdef DEBUG
297 fprintf(stderr, "*** digs is %s\n", itos(dig).c_str());
298 #endif
300 // Determine number of digits to put on left side of the decimal point
301 int lhs=0;
302 // For 'g' mode, decide whether to use 'e' or 'f' format.
303 if (mode=='g') mode = (decimal>6 || decimal<-3)? 'e' : 'f';
304 switch (mode) {
305 case 'e':
306 lhs = 1; // only need one char on left side
307 useExponent = true; // force exponent use
308 break;
310 case 'f':
311 lhs = (decimal<1)? 1 : decimal;
312 // use one char on left for num < 1,
313 // otherwise, use the number of decimal places.
314 useExponent = false; // don't want exponent for 'f' format
315 break;
317 case 'h':
318 if (val==0.0) // special case for if value is zero exactly.
319 lhs = 0; // this prevents code from returning '000.0'
320 else
321 lhs = (decimal<=0)? (decimal)%3 + 3 : (decimal-1)%3+1;
322 useExponent = !(lhs==decimal); // only use exponent if we really need it
323 break;
325 default:
326 return "**bad mode**";
327 }
329 #ifdef DEBUG
330 fprintf(stderr, "*** lhs is %s\n", itos(lhs).c_str());
331 #endif
333 // Figure out the number of digits to show in the right hand side
334 int rhs=0;
335 if (precision>=0)
336 rhs = precision;
337 else if (val == 0.0)
338 rhs = 0;
339 else if (useExponent || decimal>0)
340 rhs = dig-lhs;
341 else
342 rhs = dig-decimal;
344 // can't use a negative rhs value, so turn it to zero if that is the case
345 if (rhs<0) rhs = 0;
347 #ifdef DEBUG
348 fprintf(stderr, "*** rhs is", itos(rhs).c_str());
349 #endif
351 // Determine the exponent
352 int exponent = decimal - lhs;
353 if (val==0.0) exponent=0; // prevent zero from getting an exponent
354 #ifdef DEBUG
355 fprintf(stderr, "*** exponent is %s\n", itos(exponent).c_str());
356 #endif
358 string ascii;
360 // output the sign
361 if (sign) ascii += "-";
362 else if (forcePlus) ascii += "+";
364 // output the left hand side
365 if (!useExponent && decimal<=0) // if fraction, put the 0 out front
366 ascii += '0';
367 else // is either exponential or >= 1, so write the lhs
368 for (; lhs>0; lhs--)
369 ascii += (*p)? *p++ : int('0'); // now fill in the numbers before decimal
371 #ifdef DEBUG
372 fprintf(stderr, "*** ascii + sign + lhs is %s\n", ascii.c_str());
373 #endif
375 // output the decimal point
376 if (forceDecimal || rhs>0)
377 ascii += '.';
379 // output the right hand side
380 if (!useExponent && rhs>0) // first fill in zeros after dp and before numbers
381 while (decimal++ <0 && rhs-->0)
382 ascii += '0';
383 for (; rhs>0 ; rhs--) // now fill in the numbers after decimal
384 ascii += (*p)? *p++ : int('0');
386 #ifdef DEBUG
387 fprintf(stderr, "*** ascii + . + rhs is %s\n", ascii.c_str());
388 #endif
390 if (forceExponent || useExponent) // output the entire exponent if required
391 {
392 ascii += E; // output the E or X
393 if (forcex10) ascii += "10^"; // if using 'x10^' format, output the '10^' part
395 // output the exponent's sign
396 if (exponent < 0) { // Negative exponent
397 exponent = -exponent; // make exponent positive if needed
398 ascii += '-'; // output negative sign
399 }
400 else if (forceExpPlus) // We only want the '+' if it is asked for explicitly
401 ascii += '+';
403 // output the exponent
404 if (forceHundredExpZero || exponent >= 100)
405 ascii += ( (exponent/100) % 10 + '0' );
406 if (forceHundredExpZero || forceExpZero || exponent >= 10)
407 ascii += ( (exponent/10) % 10 + '0' );
408 ascii += ( exponent % 10 + '0' );
410 #ifdef DEBUG
411 fprintf(stderr, "*** ascii + exp is %s\n", ascii.c_str());
412 #endif
413 }
415 #ifdef DEBUG
416 fprintf(stderr, "*** End of ftos with ascii = ", ascii.c_str());
417 #endif
418 /* finally, we can return */
419 return ascii;
420 }
422 #ifdef TESTFTOS
424 int main()
425 {
426 cout << "Normal (g): " << endl;
427 cout << "1.0 = " << ftos(1.0) << endl;
428 cout << "42 = " << ftos(42) << endl;
429 cout << "3.141 = " << ftos(3.141) << endl;
430 cout << "0.01 = " << ftos(0.01) << endl;
431 cout << "1.0e7 = " << ftos(1.0e7) << endl;
432 cout << endl;
434 cout << "Scientific (e): " << endl;
435 cout << "1.0 = " << ftos(1.0, 'e') << endl;
436 cout << "42 = " << ftos(42, 'e') << endl;
437 cout << "3.141 = " << ftos(3.141, 'e') << endl;
438 cout << "0.01 = " << ftos(0.01, 'e') << endl;
439 cout << "1.0e7 = " << ftos(1.0e7, 'e') << endl;
440 cout << endl;
442 cout << "Fixed (f): " << endl;
443 cout << "1.0 = " << ftos(1.0, 'f') << endl;
444 cout << "42 = " << ftos(42, 'f') << endl;
445 cout << "3.141 = " << ftos(3.141, 'f') << endl;
446 cout << "0.01 = " << ftos(0.01, 'f') << endl;
447 cout << "1.0e7 = " << ftos(1.0e7, 'f') << endl;
448 cout << endl;
450 cout << "Engineering (h): " << endl;
451 cout << "1.0 = " << ftos(1.0, 'h') << endl;
452 cout << "42 = " << ftos(42, 'h') << endl;
453 cout << "3.141 = " << ftos(3.141, 'h') << endl;
454 cout << "0.01 = " << ftos(0.01, 'h') << endl;
455 cout << "1.0e7 = " << ftos(1.0e7, 'h') << endl;
456 cout << endl;
458 cout << "Sigfigs: " << endl;
459 cout << "2 sf = " << ftos(1234, 'g', 2) << " "
460 << ftos(12.34, 'g', 2) << " "
461 << ftos(0, 'g', 2) << " "
462 << ftos(123.4e-11, 'g', 2) << endl;
463 cout << "4 sf = " << ftos(1234, 'g', 4) << " "
464 << ftos(12.34, 'g', 4) << " "
465 << ftos(0, 'g', 4) << " "
466 << ftos(123.4e-11, 'g', 4) << endl;
467 cout << "8 sf = " << ftos(1234, 'g', 8) << " "
468 << ftos(12.34, 'g', 8) << " "
469 << ftos(0, 'g', 8) << " "
470 << ftos(123.4e-11, 'g', 8) << endl;
471 cout << endl;
473 cout << "x10 mode: " << endl;
474 cout << "1234 = " << ftos(1234, 'e', 4, -1, FORCE_X10 | FORCE_EXP) << endl;
475 cout << "1.01e10 = " << ftos(1.01e10, 'h', -1, -1, FORCE_X10 | FORCE_EXP) << endl;
476 cout << endl;
478 cout << "itos tests..." << endl;
479 cout << "42 = " << itos(42) << endl;
480 cout << endl;
482 return 0;
483 }
485 #endif // TESTFTOS