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 /////////////////////////////////////////////////////////////////////// */
154 // This is the routine used for converting a floating point into a string
155 // This may be included in stdlib.h on some systems and may conflict.
156 // Let me know your system & etc. so I can properly #ifdef this, but
157 // try commenting the following four lines out if you run into conflicts.
158 // extern "C" {
159 // char*
160 // ecvt (double val, size_t ndigit, int *decpt, int *sign);
161 // }
163 using namespace std;
165 #ifndef HAS_ECVT
166 #include <glib.h>
167 #endif
170 #include "ftos.h"
173 // This routine counts from the end of a string like '10229000' to find the index
174 // of the first non-'0' character (5 would be returned for the above number.)
175 int countDigs(char *p)
176 {
177 int length =0;
178 while (*(p+length)!='\0') length++; // Count total length
179 while (length>0 && *(p+length-1)=='0') length--; // Scan backwards for a non-'0'
180 return length;
181 }
183 // This routine determines how many digits make up the left hand
184 // side of the number if the abs value of the number is greater than 1, or the
185 // digits that make up the right hand side if the abs value of the number
186 // is between 0 and 1. Returns 1 if v==0. Return value is positive for numbers
187 // greater than or equal to 1, negative for numbers less than 0.1, and zero for
188 // numbers between 0.1 and 1.
189 int countLhsDigits(double v)
190 {
191 if (v<0) v = -v; // Take abs value
192 else if (v==0) return 1; // Special case if v==0
194 int n=0;
195 for (; v<0.1; v*=10) // Count digits on right hand side (l.t. 0.1)
196 { n--; }
197 for (; v>=1; v/=10) // Count digits on left hand side (g.e. 1.0)
198 { n++; }
199 return n;
200 }
202 // This is the routine that does the work of converting the number into a string.
203 string ftos(double val, char mode, int sigfig, int precision, int options)
204 {
205 // Parse the options to a more usable form
206 // These options allow the user to control some of the ornaments on the
207 // number that is output. By default they are all false. Turning them
208 // on helps to "fix" the format of the number so it lines up in columns
209 // better.
210 // - require the decimal point to be shown for numbers that do not have
211 // any fractional digits (or that have a precision set to zero
212 bool forceDecimal = (options & FORCE_DECIMAL);
213 // - show the 10's and 100's zero in exponent
214 bool forceExpZero = (options & FORCE_EXP_ZERO);
215 bool forceHundredExpZero = (options & FORCE_HUNDRED_EXP_ZERO);
216 // - show the '+' in the exponent if exponent is used
217 bool forceExpPlus = (options & FORCE_EXP_PLUS);
218 // - force the output to display the exponent
219 bool forceExponent = (options & FORCE_EXP);
220 // - use x10^ instead of E
221 bool forcex10 = (options & FORCE_X10);
222 // - force output of the '+' for positive numbers
223 bool forcePlus = (options & FORCE_PLUS);
225 #ifdef DEBUG
226 fprintf(stderr, "Options: ");
227 fprintf(stderr, " %4s = %s ", "x10", (forcex10 ? "on" : "off" ));
228 fprintf(stderr, " %4s = %s ", ".", (forceDecimal ? "on" : "off" ));
229 fprintf(stderr, " %4s = %s ", "e0", (forceExpZero ? "on" : "off" ));
230 fprintf(stderr, " %4s = %s ", "e00", (forceHundredExpZero ? "on" : "off" ));
231 fprintf(stderr, " %4s = %s ", "e+", (forceExpPlus ? "on" : "off" ));
232 fprintf(stderr, " %4s = %s ", "e", (forceExponent ? "on" : "off" ));
233 fprintf(stderr, " %4s = %s \n", "+#", (forcePlus ? "on" : "off" ));
234 #endif
236 // - exponent usage
237 bool useExponent = false;
239 // Determine the case for the 'e' (if used)
240 char E = (forcex10)? 'x' : 'e';
241 if (g_ascii_isupper(mode)) {
242 E = g_ascii_toupper(E);
243 mode = g_ascii_tolower(mode);
244 }
246 // Determine how many decimals we're interested in
247 int L = countLhsDigits(val);
249 #ifdef DEBUG
250 fprintf(stderr, "*** L is %s\n", itos(L).c_str());
251 #endif
253 int count = 0;
254 if (sigfig==0) // bad input - don't want any sigfigs??!!
255 return "";
256 else if (precision>=0) { // Use fixed number of decimal places
257 count = precision;
258 if (mode == 'e') count += 1;
259 else if (mode == 'f') count += L;
260 else if (mode == 'g') count += (L>6 || L<-3)? 1 : L;
261 else if (mode == 'h') count += (L>0)? ((L-1)%3+1) : (L%3+3);
262 if (sigfig>0) count = (sigfig > count)? count : sigfig; // Use sigfig # if it means more decimal places
263 }
264 else if (sigfig>0) // Just use sigfigs
265 count = sigfig;
266 else // prec < 0 and sigfig < 0
267 count = 10;
268 #ifdef DEBUG
269 fprintf(stderr, "*** count is %s\n", itos(count).c_str());
270 #endif
272 // Get number's string rep, sign, and exponent
273 int sign = 0;
274 int decimal=0;
276 #ifdef HAS_ECVT
277 char *p = ecvt(val, count, &decimal, &sign);
278 #else
279 char *p = (char *) g_strdup_printf("%.0f", val);
280 // asprintf(&p, "%.0f", val);
281 #endif
283 #ifdef DEBUG
284 fprintf(stderr, "*** string rep is %s\n", p);
285 fprintf(stderr, "*** decimal is %s\n", itos(decimal).c_str());
286 fprintf(stderr, "*** sign is %s\n", itos(sign).c_str());
287 #endif
289 // Count the number of relevant digits in the resultant number
290 int dig = countDigs(p);
291 if (dig < sigfig) dig = sigfig;
293 #ifdef DEBUG
294 fprintf(stderr, "*** digs is %s\n", itos(dig).c_str());
295 #endif
297 // Determine number of digits to put on left side of the decimal point
298 int lhs=0;
299 // For 'g' mode, decide whether to use 'e' or 'f' format.
300 if (mode=='g') mode = (decimal>6 || decimal<-3)? 'e' : 'f';
301 switch (mode) {
302 case 'e':
303 lhs = 1; // only need one char on left side
304 useExponent = true; // force exponent use
305 break;
307 case 'f':
308 lhs = (decimal<1)? 1 : decimal;
309 // use one char on left for num < 1,
310 // otherwise, use the number of decimal places.
311 useExponent = false; // don't want exponent for 'f' format
312 break;
314 case 'h':
315 if (val==0.0) // special case for if value is zero exactly.
316 lhs = 0; // this prevents code from returning '000.0'
317 else
318 lhs = (decimal<=0)? (decimal)%3 + 3 : (decimal-1)%3+1;
319 useExponent = !(lhs==decimal); // only use exponent if we really need it
320 break;
322 default:
323 return "**bad mode**";
324 }
326 #ifdef DEBUG
327 fprintf(stderr, "*** lhs is %s\n", itos(lhs).c_str());
328 #endif
330 // Figure out the number of digits to show in the right hand side
331 int rhs=0;
332 if (precision>=0)
333 rhs = precision;
334 else if (val == 0.0)
335 rhs = 0;
336 else if (useExponent || decimal>0)
337 rhs = dig-lhs;
338 else
339 rhs = dig-decimal;
341 // can't use a negative rhs value, so turn it to zero if that is the case
342 if (rhs<0) rhs = 0;
344 #ifdef DEBUG
345 fprintf(stderr, "*** rhs is", itos(rhs).c_str());
346 #endif
348 // Determine the exponent
349 int exponent = decimal - lhs;
350 if (val==0.0) exponent=0; // prevent zero from getting an exponent
351 #ifdef DEBUG
352 fprintf(stderr, "*** exponent is %s\n", itos(exponent).c_str());
353 #endif
355 string ascii;
357 // output the sign
358 if (sign) ascii += "-";
359 else if (forcePlus) ascii += "+";
361 // output the left hand side
362 if (!useExponent && decimal<=0) // if fraction, put the 0 out front
363 ascii += '0';
364 else // is either exponential or >= 1, so write the lhs
365 for (; lhs>0; lhs--)
366 ascii += (*p)? *p++ : int('0'); // now fill in the numbers before decimal
368 #ifdef DEBUG
369 fprintf(stderr, "*** ascii + sign + lhs is %s\n", ascii.c_str());
370 #endif
372 // output the decimal point
373 if (forceDecimal || rhs>0)
374 ascii += '.';
376 // output the right hand side
377 if (!useExponent && rhs>0) // first fill in zeros after dp and before numbers
378 while (decimal++ <0 && rhs-->0)
379 ascii += '0';
380 for (; rhs>0 ; rhs--) // now fill in the numbers after decimal
381 ascii += (*p)? *p++ : int('0');
383 #ifdef DEBUG
384 fprintf(stderr, "*** ascii + . + rhs is %s\n", ascii.c_str());
385 #endif
387 if (forceExponent || useExponent) // output the entire exponent if required
388 {
389 ascii += E; // output the E or X
390 if (forcex10) ascii += "10^"; // if using 'x10^' format, output the '10^' part
392 // output the exponent's sign
393 if (exponent < 0) { // Negative exponent
394 exponent = -exponent; // make exponent positive if needed
395 ascii += '-'; // output negative sign
396 }
397 else if (forceExpPlus) // We only want the '+' if it is asked for explicitly
398 ascii += '+';
400 // output the exponent
401 if (forceHundredExpZero || exponent >= 100)
402 ascii += ( (exponent/100) % 10 + '0' );
403 if (forceHundredExpZero || forceExpZero || exponent >= 10)
404 ascii += ( (exponent/10) % 10 + '0' );
405 ascii += ( exponent % 10 + '0' );
407 #ifdef DEBUG
408 fprintf(stderr, "*** ascii + exp is %s\n", ascii.c_str());
409 #endif
410 }
412 #ifdef DEBUG
413 fprintf(stderr, "*** End of ftos with ascii = ", ascii.c_str());
414 #endif
415 /* finally, we can return */
416 return ascii;
417 }
419 #ifdef TESTFTOS
421 int main()
422 {
423 cout << "Normal (g): " << endl;
424 cout << "1.0 = " << ftos(1.0) << endl;
425 cout << "42 = " << ftos(42) << endl;
426 cout << "3.141 = " << ftos(3.141) << endl;
427 cout << "0.01 = " << ftos(0.01) << endl;
428 cout << "1.0e7 = " << ftos(1.0e7) << endl;
429 cout << endl;
431 cout << "Scientific (e): " << endl;
432 cout << "1.0 = " << ftos(1.0, 'e') << endl;
433 cout << "42 = " << ftos(42, 'e') << endl;
434 cout << "3.141 = " << ftos(3.141, 'e') << endl;
435 cout << "0.01 = " << ftos(0.01, 'e') << endl;
436 cout << "1.0e7 = " << ftos(1.0e7, 'e') << endl;
437 cout << endl;
439 cout << "Fixed (f): " << endl;
440 cout << "1.0 = " << ftos(1.0, 'f') << endl;
441 cout << "42 = " << ftos(42, 'f') << endl;
442 cout << "3.141 = " << ftos(3.141, 'f') << endl;
443 cout << "0.01 = " << ftos(0.01, 'f') << endl;
444 cout << "1.0e7 = " << ftos(1.0e7, 'f') << endl;
445 cout << endl;
447 cout << "Engineering (h): " << endl;
448 cout << "1.0 = " << ftos(1.0, 'h') << endl;
449 cout << "42 = " << ftos(42, 'h') << endl;
450 cout << "3.141 = " << ftos(3.141, 'h') << endl;
451 cout << "0.01 = " << ftos(0.01, 'h') << endl;
452 cout << "1.0e7 = " << ftos(1.0e7, 'h') << endl;
453 cout << endl;
455 cout << "Sigfigs: " << endl;
456 cout << "2 sf = " << ftos(1234, 'g', 2) << " "
457 << ftos(12.34, 'g', 2) << " "
458 << ftos(0, 'g', 2) << " "
459 << ftos(123.4e-11, 'g', 2) << endl;
460 cout << "4 sf = " << ftos(1234, 'g', 4) << " "
461 << ftos(12.34, 'g', 4) << " "
462 << ftos(0, 'g', 4) << " "
463 << ftos(123.4e-11, 'g', 4) << endl;
464 cout << "8 sf = " << ftos(1234, 'g', 8) << " "
465 << ftos(12.34, 'g', 8) << " "
466 << ftos(0, 'g', 8) << " "
467 << ftos(123.4e-11, 'g', 8) << endl;
468 cout << endl;
470 cout << "x10 mode: " << endl;
471 cout << "1234 = " << ftos(1234, 'e', 4, -1, FORCE_X10 | FORCE_EXP) << endl;
472 cout << "1.01e10 = " << ftos(1.01e10, 'h', -1, -1, FORCE_X10 | FORCE_EXP) << endl;
473 cout << endl;
475 cout << "itos tests..." << endl;
476 cout << "42 = " << itos(42) << endl;
477 cout << endl;
479 return 0;
480 }
482 #endif // TESTFTOS