comparison tango/tango/text/convert/Float.d @ 132:1700239cab2e trunk

[svn r136] MAJOR UNSTABLE UPDATE!!! Initial commit after moving to Tango instead of Phobos. Lots of bugfixes... This build is not suitable for most things.
author lindquist
date Fri, 11 Jan 2008 17:57:40 +0100
parents
children
comparison
equal deleted inserted replaced
131:5825d48b27d1 132:1700239cab2e
1 /*******************************************************************************
2
3 copyright: Copyright (c) 2004 Kris Bell. All rights reserved
4
5 license: BSD style: $(LICENSE)
6
7 version: Initial release: Nov 2005
8
9 author: Kris
10
11 A set of functions for converting between string and floating-
12 point values.
13
14 Applying the D "import alias" mechanism to this module is highly
15 recommended, in order to limit namespace pollution:
16 ---
17 import Float = tango.text.convert.Float;
18
19 auto f = Float.parse ("3.14159");
20 ---
21
22 *******************************************************************************/
23
24 module tango.text.convert.Float;
25
26 private import tango.core.Exception;
27
28 private import Integer = tango.text.convert.Integer;
29
30 private alias real NumType;
31
32 private extern (C) NumType log10l(NumType x);
33
34 /******************************************************************************
35
36 Constants
37
38 ******************************************************************************/
39
40 private enum
41 {
42 Dec = 2, // default decimal places
43 Exp = 10, // default switch to scientific notation
44 }
45
46 /******************************************************************************
47
48 Convert a formatted string of digits to a floating-point
49 number. Throws an exception where the input text is not
50 parsable in its entirety.
51
52 ******************************************************************************/
53
54 NumType toFloat(T) (T[] src)
55 {
56 uint len;
57
58 auto x = parse (src, &len);
59 if (len < src.length)
60 throw new IllegalArgumentException ("Float.toFloat :: invalid number");
61 return x;
62 }
63
64 /******************************************************************************
65
66 Template wrapper to make life simpler. Returns a text version
67 of the provided value.
68
69 See format() for details
70
71 ******************************************************************************/
72
73 char[] toString (NumType d, uint decimals=Dec, int e=Exp)
74 {
75 char[64] tmp = void;
76
77 return format (tmp, d, decimals, e).dup;
78 }
79
80 /******************************************************************************
81
82 Template wrapper to make life simpler. Returns a text version
83 of the provided value.
84
85 See format() for details
86
87 ******************************************************************************/
88
89 wchar[] toString16 (NumType d, uint decimals=Dec, int e=Exp)
90 {
91 wchar[64] tmp = void;
92
93 return format (tmp, d, decimals, e).dup;
94 }
95
96 /******************************************************************************
97
98 Template wrapper to make life simpler. Returns a text version
99 of the provided value.
100
101 See format() for details
102
103 ******************************************************************************/
104
105 dchar[] toString32 (NumType d, uint decimals=Dec, int e=Exp)
106 {
107 dchar[64] tmp = void;
108
109 return format (tmp, d, decimals, e).dup;
110 }
111
112 /******************************************************************************
113
114 Convert a float to a string. This produces pretty good results
115 for the most part, though one should use David Gay's dtoa package
116 for best accuracy.
117
118 Note that the approach first normalizes a base10 mantissa, then
119 pulls digits from the left side whilst emitting them (rightward)
120 to the output.
121
122 The e parameter controls the number of exponent places emitted,
123 and can thus control where the output switches to the scientific
124 notation. For example, setting e=2 for 0.01 or 10.0 would result
125 in normal output. Whereas setting e=1 would result in both those
126 values being rendered in scientific notation instead. Setting e
127 to 0 forces that notation on for everything.
128
129 TODO: this should be replaced, as it is not sufficiently accurate
130
131 ******************************************************************************/
132
133 T[] format(T, D=double, U=uint) (T[] dst, D x, U decimals=Dec, int e=Exp)
134 {return format!(T)(dst, x, decimals, e);}
135
136 T[] format(T) (T[] dst, NumType x, uint decimals=Dec, int e=Exp)
137 {
138 static T[] inf = "-inf";
139 static T[] nan = "-nan";
140
141 // extract the sign bit
142 static bool signed (NumType x)
143 {
144 static if (NumType.sizeof is 4)
145 return ((*cast(uint *)&x) & 0x8000_0000) != 0;
146
147 static if (NumType.sizeof is 8)
148 return ((*cast(ulong *)&x) & 0x8000_0000_0000_0000) != 0;
149 else
150 {
151 auto pe = cast(ubyte *)&x;
152 return (pe[9] & 0x80) != 0;
153 }
154 }
155
156 // strip digits from the left of a normalized base-10 number
157 static int toDigit (inout NumType v, inout int count)
158 {
159 int digit;
160
161 // Don't exceed max digits storable in a real
162 // (-1 because the last digit is not always storable)
163 if (--count <= 0)
164 digit = 0;
165 else
166 {
167 // remove leading digit, and bump
168 digit = cast(int) v;
169 v = (v - digit) * 10.0;
170 }
171 return digit + '0';
172 }
173
174 // extract the sign
175 bool sign = signed (x);
176 if (sign)
177 x = -x;
178
179 if (x !<>= x)
180 return sign ? nan : nan[1..$];
181
182 if (x is x.infinity)
183 return sign ? inf : inf[1..$];
184
185 // assume no exponent
186 int exp = 0;
187
188 // don't scale if zero
189 if (x > 0.0)
190 {
191 // extract base10 exponent
192 exp = cast(int) log10l (x);
193
194 // round up a bit
195 auto d = decimals;
196 if (exp < 0)
197 d -= exp;
198 x += 0.5 / pow10 (d);
199
200 // extract base10 exponent
201 exp = cast(int) log10l (x);
202
203 // normalize base10 mantissa (0 < m < 10)
204 int len = exp;
205 if (exp < 0)
206 x *= pow10 (len = -exp);
207 else
208 x /= pow10 (exp);
209
210 // switch to short display if not enough space
211 if (len >= e)
212 e = 0;
213 }
214
215 T* p = dst.ptr;
216 int count = NumType.dig;
217
218 // emit sign
219 if (sign)
220 *p++ = '-';
221
222 // are we doing +/-exp format?
223 if (e is 0)
224 {
225 assert (dst.length > decimals + 7);
226
227 // emit first digit, and decimal point
228 *p++ = toDigit (x, count);
229 if (decimals)
230 {
231 *p++ = '.';
232 if (exp < 0)
233 count += exp;
234 }
235
236 // emit rest of mantissa
237 while (decimals-- > 0)
238 *p++ = toDigit (x, count);
239
240 // emit exponent, if non zero
241 if (exp)
242 {
243 *p++ = 'e';
244 *p++ = (exp < 0) ? '-' : '+';
245 if (exp < 0)
246 exp = -exp;
247
248 if (exp >= 100)
249 {
250 *p++ = (exp/100) + '0';
251 exp %= 100;
252 }
253
254 *p++ = (exp/10) + '0';
255 *p++ = (exp%10) + '0';
256 }
257 }
258 else
259 {
260 assert (dst.length >= (((exp < 0) ? 0 : exp) + decimals + 1));
261
262 // if fraction only, emit a leading zero
263 if (exp < 0)
264 *p++ = '0';
265 else
266 // emit all digits to the left of point
267 for (; exp >= 0; --exp)
268 *p++ = toDigit (x, count);
269
270 // emit point
271 if (decimals)
272 *p++ = '.';
273
274 // emit leading fractional zeros?
275 for (++exp; exp < 0 && decimals > 0; --decimals, ++exp)
276 *p++ = '0';
277
278 // output remaining digits, if any. Trailing
279 // zeros are also returned from toDigit()
280 while (decimals-- > 0)
281 *p++ = toDigit (x, count);
282 }
283
284 return dst [0..(p - dst.ptr)];
285 }
286
287
288 /******************************************************************************
289
290 Convert a formatted string of digits to a floating-point number.
291 Good for general use, but use David Gay's dtoa package if serious
292 rounding adjustments should be applied.
293
294 ******************************************************************************/
295
296 NumType parse(T) (T[] src, uint* ate=null)
297 {
298 T c;
299 T* p;
300 int exp;
301 bool sign;
302 uint radix;
303 NumType value = 0.0;
304
305 // remove leading space, and sign
306 c = *(p = src.ptr + Integer.trim (src, sign, radix));
307
308 // handle non-decimal representations
309 if (radix != 10)
310 {
311 long v = Integer.parse (src, radix, ate);
312 return *cast(NumType*) &v;
313 }
314
315 // set begin and end checks
316 auto begin = p;
317 auto end = src.ptr + src.length;
318
319 // read leading digits; note that leading
320 // zeros are simply multiplied away
321 while (c >= '0' && c <= '9' && p < end)
322 {
323 value = value * 10 + (c - '0');
324 c = *++p;
325 }
326
327 // gobble up the point
328 if (c is '.' && p < end)
329 c = *++p;
330
331 // read fractional digits; note that we accumulate
332 // all digits ... very long numbers impact accuracy
333 // to a degree, but perhaps not as much as one might
334 // expect. A prior version limited the digit count,
335 // but did not show marked improvement. For maximum
336 // accuracy when reading and writing, use David Gay's
337 // dtoa package instead
338 while (c >= '0' && c <= '9' && p < end)
339 {
340 value = value * 10 + (c - '0');
341 c = *++p;
342 --exp;
343 }
344
345 // did we get something?
346 if (value)
347 {
348 // parse base10 exponent?
349 if ((c is 'e' || c is 'E') && p < end )
350 {
351 uint eaten;
352 exp += Integer.parse (src[(++p-src.ptr) .. $], 0, &eaten);
353 p += eaten;
354 }
355
356 // adjust mantissa; note that the exponent has
357 // already been adjusted for fractional digits
358 if (exp < 0)
359 value /= pow10 (-exp);
360 else
361 value *= pow10 (exp);
362 }
363 else
364 // was it was nan instead?
365 if (p is begin)
366 if (p[0..3] == "inf")
367 p += 3, value = value.infinity;
368 else
369 if (p[0..3] == "nan")
370 p += 3, value = value.nan;
371
372 // set parse length, and return value
373 if (ate)
374 *ate = p - src.ptr;
375
376 if (sign)
377 value = -value;
378 return value;
379 }
380
381 /******************************************************************************
382
383 Truncate trailing '0' and '.' from a string, such that 200.000
384 becomes 200, and 20.10 becomes 20.1
385
386 Returns a potentially shorter slice of what you give it.
387
388 ******************************************************************************/
389
390 T[] truncate(T) (T[] s)
391 {
392 auto tmp = s;
393 auto i = tmp.length;
394 foreach (idx, c; tmp)
395 if (c is '.')
396 while (--i >= idx)
397 if (tmp[i] != '0')
398 {
399 if (tmp[i] is '.')
400 --i;
401 s = tmp [0 .. i+1];
402 while (--i >= idx)
403 if (tmp[i] is 'e')
404 return tmp;
405 break;
406 }
407 return s;
408 }
409
410 /******************************************************************************
411
412 Internal function to convert an exponent specifier to a floating
413 point value.
414
415 ******************************************************************************/
416
417 private NumType pow10 (uint exp)
418 {
419 static NumType[] Powers =
420 [
421 1.0e1L,
422 1.0e2L,
423 1.0e4L,
424 1.0e8L,
425 1.0e16L,
426 1.0e32L,
427 1.0e64L,
428 1.0e128L,
429 1.0e256L,
430 ];
431
432 if (exp >= 512)
433 throw new IllegalArgumentException ("Float.pow10 :: exponent too large");
434
435 NumType mult = 1.0;
436 foreach (NumType power; Powers)
437 {
438 if (exp & 1)
439 mult *= power;
440 if ((exp >>= 1) is 0)
441 break;
442 }
443 return mult;
444 }
445
446
447 /******************************************************************************
448
449 ******************************************************************************/
450
451 debug (UnitTest)
452 {
453 unittest
454 {
455 char[64] tmp;
456
457 auto f = parse ("nan");
458 assert (format(tmp, f) == "nan");
459 f = parse ("inf");
460 assert (format(tmp, f) == "inf");
461 f = parse ("-nan");
462 assert (format(tmp, f) == "-nan");
463 f = parse (" -inf");
464 assert (format(tmp, f) == "-inf");
465
466 assert (format (tmp, 3.14159, 6) == "3.141590");
467 assert (format (tmp, 3.14159, 4) == "3.1416");
468 assert (parse ("3.5") == 3.5);
469 assert (format(tmp, parse ("3.14159"), 6) == "3.141590");
470 }
471 }
472
473
474 debug (Float)
475 {
476 import tango.io.Console;
477
478 void main()
479 {
480 char[20] tmp;
481
482 Cout (format(tmp, 1)).newline;
483 Cout (format(tmp, 0)).newline;
484 Cout (format(tmp, 0.000001)).newline;
485
486 Cout (format(tmp, 3.14159, 6, 0)).newline;
487 Cout (format(tmp, 3e100, 6, 3)).newline;
488 Cout (format(tmp, 314159, 6)).newline;
489 Cout (format(tmp, 314159123213, 6, 15)).newline;
490 Cout (format(tmp, 3.14159, 6, 2)).newline;
491 Cout (format(tmp, 3.14159, 6, 2)).newline;
492 Cout (format(tmp, 0.00003333, 6, 2)).newline;
493 Cout (format(tmp, 0.00333333, 6, 3)).newline;
494 Cout (format(tmp, 0.03333333, 6, 2)).newline;
495 Cout.newline;
496
497 Cout (format(tmp, -3.14159, 6, 0)).newline;
498 Cout (format(tmp, -3e100, 6, 3)).newline;
499 Cout (format(tmp, -314159, 6)).newline;
500 Cout (format(tmp, -314159123213, 6, 15)).newline;
501 Cout (format(tmp, -3.14159, 6, 2)).newline;
502 Cout (format(tmp, -3.14159, 6, 2)).newline;
503 Cout (format(tmp, -0.00003333, 6, 2)).newline;
504 Cout (format(tmp, -0.00333333, 6, 3)).newline;
505 Cout (format(tmp, -0.03333333, 6, 2)).newline;
506 Cout.newline;
507
508 Cout (truncate(format(tmp, 30, 6))).newline;
509 Cout (truncate(format(tmp, 3.14159, 6, 0))).newline;
510 Cout (truncate(format(tmp, 3e100, 6, 3))).newline;
511 Cout (truncate(format(tmp, 314159, 6))).newline;
512 Cout (truncate(format(tmp, 314159123213, 6, 15))).newline;
513 Cout (truncate(format(tmp, 3.14159, 6, 2))).newline;
514 Cout (truncate(format(tmp, 3.14159, 6, 2))).newline;
515 Cout (truncate(format(tmp, 0.00003333, 6, 2))).newline;
516 Cout (truncate(format(tmp, 0.00333333, 6, 3))).newline;
517 Cout (truncate(format(tmp, 0.03333333, 6, 2))).newline;
518
519 }
520 }