1 /*******************************************************************************
2 
3     A set of functions for converting between string and floating-
4     point values.
5 
6     Applying the D "import alias" mechanism to this module is highly
7     recommended, in order to limit namespace pollution:
8     ---
9     import Float = ocean.text.convert.Float;
10 
11     auto f = Float.parse ("3.14159");
12     ---
13 
14     Copyright:
15         Copyright (c) 2004 Kris Bell.
16         Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH.
17         All rights reserved.
18 
19     License:
20         Tango Dual License: 3-Clause BSD License / Academic Free License v3.0.
21         See LICENSE_TANGO.txt for details.
22 
23     Version:
24         Nov 2005: Initial release
25         Jan 2010: added internal ecvt()
26 
27     Authors: Kris
28 
29 ********************************************************************************/
30 
31 module ocean.text.convert.Float;
32 
33 import ocean.transition;
34 
35 import ocean.core.ExceptionDefinitions;
36 import ocean.math.IEEE;
37 import ocean.core.Verify;
38 
39 static import tsm = core.stdc.math;
40 static import Integer = ocean.text.convert.Integer_tango;
41 
42 private alias real NumType;
43 
44 /******************************************************************************
45 
46   optional math functions
47 
48  ******************************************************************************/
49 
50 private extern (C)
51 {
52     real log10l (real x);
53     real ceill (real num);
54     real modfl (real num, real *i);
55     real powl  (real base, real exp);
56 }
57 
58 /******************************************************************************
59 
60   Constants
61 
62  ******************************************************************************/
63 
64 private enum
65 {
66     Pad = 0,                // default trailing decimal zero
67     Dec = 2,                // default decimal places
68     Exp = 10,               // default switch to scientific notation
69 }
70 
71 /******************************************************************************
72 
73   Convert a formatted string of digits to a floating-point
74   number. Throws an exception where the input text is not
75   parsable in its entirety.
76 
77  ******************************************************************************/
78 
79 NumType toFloat(T) (T[] src)
80 {
81     uint len;
82 
83     auto x = parse (src, &len);
84     if (len < src.length || len == 0)
85         throw new IllegalArgumentException ("Float.toFloat :: invalid number");
86     return x;
87 }
88 
89 /******************************************************************************
90 
91   Template wrapper to make life simpler. Returns a text version
92   of the provided value.
93 
94   See format() for details
95 
96  ******************************************************************************/
97 
98 char[] toString (NumType d, uint decimals=Dec, int e=Exp)
99 {
100     char[64] tmp = void;
101 
102     return format (tmp, d, decimals, e).dup;
103 }
104 
105 /******************************************************************************
106 
107   Truncate trailing '0' and '.' from a string, such that 200.000
108   becomes 200, and 20.10 becomes 20.1
109 
110   Returns a potentially shorter slice of what you give it.
111 
112  ******************************************************************************/
113 
114 T[] truncate(T) (T[] s)
115 {
116     auto tmp = s;
117     int i = tmp.length;
118     foreach (int idx, T c; tmp)
119     {
120         if (c is '.')
121         {
122             while (--i >= idx)
123             {
124                 if (tmp[i] != '0')
125                 {
126                     if (tmp[i] is '.')
127                         --i;
128                     s = tmp [0 .. i+1];
129                     while (--i >= idx)
130                         if (tmp[i] is 'e')
131                             return tmp;
132                     break;
133                 }
134             }
135         }
136     }
137     return s;
138 }
139 
140 /******************************************************************************
141 
142   Extract a sign-bit
143 
144  ******************************************************************************/
145 
146 private bool negative (NumType x)
147 {
148     static if (NumType.sizeof is 4)
149         return ((*cast(uint *)&x) & 0x8000_0000) != 0;
150     else
151         static if (NumType.sizeof is 8)
152             return ((*cast(ulong *)&x) & 0x8000_0000_0000_0000) != 0;
153     else
154     {
155         auto pe = cast(ubyte *)&x;
156         return (pe[9] & 0x80) != 0;
157     }
158 }
159 
160 
161 /*******************************************************************************
162 
163     Format a floating-point value according to a format string
164 
165     Defaults to 2 decimal places and 10 exponent, as the other format overload
166     does.
167 
168     Format specifiers (additive unless stated otherwise):
169         '.' = Do not pad
170         'e' or 'E' = Display exponential notation
171         Any number = Set the decimal precision
172 
173     Params:
174         T      = character type
175         V      = Floating point type
176         output = Where to write the string to - expected to be large enough
177         v      = Number to format
178         fmt    = Format string, see this function's description
179 
180     Returns:
181         A const reference to `output`
182 
183 *******************************************************************************/
184 
185 public Const!(T)[] format (T, V) (T[] output, V v, in T[] fmt)
186 {
187     static assert(is(V : Const!(real)),
188                   "Float.format only support floating point types or types that"
189                   ~ "implicitly convert to them");
190 
191     int dec = Dec;
192     int exp = Exp;
193     bool pad = true;
194 
195     for (auto p = fmt.ptr, e = p + fmt.length; p < e; ++p)
196         switch (*p)
197         {
198         case '.':
199             pad = false;
200             break;
201         case 'e':
202         case 'E':
203             exp = 0;
204             break;
205         default:
206             Unqual!(T) c = *p;
207             if (c >= '0' && c <= '9')
208             {
209                 dec = c - '0', c = p[1];
210                 if (c >= '0' && c <= '9' && ++p < e)
211                     dec = dec * 10 + c - '0';
212             }
213             break;
214         }
215 
216     return format!(T)(output, v, dec, exp, pad);
217 }
218 
219 /******************************************************************************
220 
221   Convert a floating-point number to a string.
222 
223   The e parameter controls the number of exponent places emitted,
224   and can thus control where the output switches to the scientific
225   notation. For example, setting e=2 for 0.01 or 10.0 would result
226   in normal output. Whereas setting e=1 would result in both those
227   values being rendered in scientific notation instead. Setting e
228   to 0 forces that notation on for everything. Parameter pad will
229   append trailing '0' decimals when set ~ otherwise trailing '0's
230   will be elided
231 
232  ******************************************************************************/
233 
234 T[] format(T) (T[] dst, NumType x, int decimals=Dec, int e=Exp, bool pad=Pad)
235 {
236     Const!(char)*  end, str;
237     int       exp,
238               sign,
239               mode=5;
240     char[32]  buf = void;
241 
242     // test exponent to determine mode
243     exp = (x == 0) ? 1 : cast(int) log10l (x < 0 ? -x : x);
244     if (exp <= -e || exp >= e)
245         mode = 2, ++decimals;
246 
247     str = convertl (buf.ptr, x, decimals, &exp, &sign, mode is 5);
248 
249     auto p = dst.ptr;
250     if (sign)
251         *p++ = '-';
252 
253     if (exp is 9999)
254         while (*str)
255             *p++ = *str++;
256     else
257     {
258         if (mode is 2)
259         {
260             --exp;
261             *p++ = *str++;
262             if (*str || pad)
263             {
264                 auto d = p;
265                 *p++ = '.';
266                 while (*str)
267                     *p++ = *str++;
268                 if (pad)
269                     while (p-d < decimals)
270                         *p++ = '0';
271             }
272             *p++ = 'e';
273             if (exp < 0)
274                 *p++ = '-', exp = -exp;
275             else
276                 *p++ = '+';
277             if (exp >= 1000)
278             {
279                 *p++ = cast(T)((exp/1000) + '0');
280                 exp %= 1000;
281             }
282             if (exp >= 100)
283             {
284                 *p++ = cast(char) (exp / 100 + '0');
285                 exp %= 100;
286             }
287             *p++ = cast(char) (exp / 10 + '0');
288             *p++ = cast(char) (exp % 10 + '0');
289         }
290         else
291         {
292             if (exp <= 0)
293                 *p++ = '0';
294             else
295                 for (; exp > 0; --exp)
296                     *p++ = (*str) ? *str++ : '0';
297             if (*str || pad)
298             {
299                 *p++ = '.';
300                 auto d = p;
301                 for (; exp < 0; ++exp)
302                     *p++ = '0';
303                 while (*str)
304                     *p++ = *str++;
305                 if (pad)
306                     while (p-d < decimals)
307                         *p++ = '0';
308             }
309         }
310     }
311 
312     // stuff a C terminator in there too ...
313     *p = 0;
314     return dst[0..(p - dst.ptr)];
315 }
316 
317 
318 /******************************************************************************
319 
320   ecvt() and fcvt() for 80bit FP, which DMD does not include. Based
321   upon the following:
322 
323   Copyright (c) 2009 Ian Piumarta
324 
325   All rights reserved.
326 
327   Permission is hereby granted, free of charge, to any person
328   obtaining a copy of this software and associated documentation
329   files (the 'Software'), to deal in the Software without restriction,
330   including without limitation the rights to use, copy, modify, merge,
331   publish, distribute, and/or sell copies of the Software, and to permit
332   persons to whom the Software is furnished to do so, provided that the
333   above copyright notice(s) and this permission notice appear in all
334   copies of the Software.
335 
336  ******************************************************************************/
337 
338 private Const!(char)* convertl (char* buf, real value, int ndigit,
339     int *decpt, int *sign, int fflag)
340 {
341     if ((*sign = negative(value)) != 0)
342         value = -value;
343 
344     *decpt = 9999;
345     if (tsm.isnan(value))
346         return "nan\0".ptr;
347 
348     if (isInfinity(value))
349         return "inf\0".ptr;
350 
351     int exp10 = (value == 0) ? !fflag : cast(int) ceill(log10l(value));
352     if (exp10 < -4931)
353         exp10 = -4931;
354     value *= powl (10.0, -exp10);
355     if (value)
356     {
357         while (value <  0.1) { value *= 10;  --exp10; }
358         while (value >= 1.0) { value /= 10;  ++exp10; }
359     }
360     verify(isZero(value) || (0.1 <= value && value < 1.0));
361     //auto zero = pad ? int.max : 1;
362     auto zero = 1;
363     if (fflag)
364     {
365         // if (! pad)
366         zero = exp10;
367         if (ndigit + exp10 < 0)
368         {
369             *decpt= -ndigit;
370             return "\0".ptr;
371         }
372         ndigit += exp10;
373     }
374     *decpt = exp10;
375     int ptr = 1;
376 
377     if (ndigit > real.dig)
378         ndigit = real.dig;
379     //printf ("< flag %d, digits %d, exp10 %d, decpt %d\n", fflag, ndigit, exp10, *decpt);
380     while (ptr <= ndigit)
381     {
382         real i = void;
383         value = modfl (value * 10, &i);
384         buf [ptr++]= cast(char) ('0' + cast(int) i);
385     }
386 
387     if (value >= 0.5)
388         while (--ptr && ++buf[ptr] > '9')
389             buf[ptr] = (ptr > zero) ? '\0' : '0';
390     else
391         for (auto i=ptr; i && --i > zero && buf[i] is '0';)
392             buf[i] = '\0';
393 
394     if (ptr)
395     {
396         buf [ndigit + 1] = '\0';
397         return buf + 1;
398     }
399     if (fflag)
400     {
401         ++ndigit;
402     }
403     buf[0]= '1';
404     ++*decpt;
405     buf[ndigit]= '\0';
406     return buf;
407 }
408 
409 
410 /******************************************************************************
411 
412   Convert a formatted string of digits to a floating-point number.
413   Good for general use, but use David Gay's dtoa package if serious
414   rounding adjustments should be applied.
415 
416  ******************************************************************************/
417 
418 NumType parse(T) (in T[] src, uint* ate=null)
419 {
420     T           c;
421     Const!(T)*  p;
422     int         exp;
423     bool        sign;
424     uint        radix;
425     NumType     value = 0.0;
426 
427     static bool match (Const!(T)* aa, in T[] bb)
428     {
429         foreach (b; bb)
430         {
431             T a = *aa++;
432             if (a >= 'A' && a <= 'Z')
433                 a += 'a' - 'A';
434             if (a != b)
435                 return false;
436         }
437         return true;
438     }
439 
440     // remove leading space, and sign
441     p = src.ptr + Integer.trim (src, sign, radix);
442 
443     // bail out if the string is empty
444     if (src.length == 0 || p > &src[$-1])
445         return NumType.nan;
446     c = *p;
447 
448     // handle non-decimal representations
449     if (radix != 10)
450     {
451         long v = Integer.parse (src, radix, ate);
452         return cast(NumType) v;
453     }
454 
455     // set begin and end checks
456     auto begin = p;
457     auto end = src.ptr + src.length;
458 
459     // read leading digits; note that leading
460     // zeros are simply multiplied away
461     while (c >= '0' && c <= '9' && p < end)
462     {
463         value = value * 10 + (c - '0');
464         c = *++p;
465     }
466 
467     // gobble up the point
468     if (c is '.' && p < end)
469         c = *++p;
470 
471     // read fractional digits; note that we accumulate
472     // all digits ... very long numbers impact accuracy
473     // to a degree, but perhaps not as much as one might
474     // expect. A prior version limited the digit count,
475     // but did not show marked improvement. For maximum
476     // accuracy when reading and writing, use David Gay's
477     // dtoa package instead
478     while (c >= '0' && c <= '9' && p < end)
479     {
480         value = value * 10 + (c - '0');
481         c = *++p;
482         --exp;
483     }
484 
485     // did we get something?
486     if (p > begin)
487     {
488         // parse base10 exponent?
489         if ((c is 'e' || c is 'E') && p < end )
490         {
491             uint eaten;
492             exp += Integer.parse (src[(++p-src.ptr) .. $], 0, &eaten);
493             p += eaten;
494         }
495 
496         // adjust mantissa; note that the exponent has
497         // already been adjusted for fractional digits
498         if (exp < 0)
499             value /= pow10 (-exp);
500         else
501             value *= pow10 (exp);
502     }
503     else
504     {
505         if (end - p >= 3)
506         {
507             switch (*p)
508             {
509                 case 'I': case 'i':
510                     if (match (p+1, "nf"))
511                     {
512                         value = value.infinity;
513                         p += 3;
514                         if (end - p >= 5 && match (p, "inity"))
515                             p += 5;
516                     }
517                     break;
518 
519                 case 'N': case 'n':
520                     if (match (p+1, "an"))
521                     {
522                         value = value.nan;
523                         p += 3;
524                     }
525                     break;
526                 default:
527                     break;
528             }
529         }
530     }
531 
532     // set parse length, and return value
533     if (ate)
534     {
535         ptrdiff_t diff = p - src.ptr;
536         verify (diff >= 0 && diff <= uint.max);
537         *ate = cast(uint) diff;
538     }
539 
540     if (sign)
541         value = -value;
542     return value;
543 }
544 
545 /******************************************************************************
546 
547   Internal function to convert an exponent specifier to a floating
548   point value.
549 
550  ******************************************************************************/
551 
552 private NumType pow10 (uint exp)
553 {
554     static NumType[] Powers = [
555         1.0e1L,
556         1.0e2L,
557         1.0e4L,
558         1.0e8L,
559         1.0e16L,
560         1.0e32L,
561         1.0e64L,
562         1.0e128L,
563         1.0e256L,
564         1.0e512L,
565         1.0e1024L,
566         1.0e2048L,
567         1.0e4096L,
568         1.0e8192L,
569     ];
570 
571     if (exp >= 16384)
572         throw new IllegalArgumentException ("Float.pow10 :: exponent too large");
573 
574     NumType mult = 1.0;
575     foreach (NumType power; Powers)
576     {
577         if (exp & 1)
578             mult *= power;
579         if ((exp >>= 1) == 0)
580             break;
581     }
582     return mult;
583 }
584 
585 /******************************************************************************
586 
587  ******************************************************************************/
588 
589 debug (Float)
590 {
591     import ocean.io.Console;
592 
593     void main()
594     {
595         char[500] tmp;
596         /+
597             Cout (format(tmp, NumType.max)).newline;
598         Cout (format(tmp, -NumType.nan)).newline;
599         Cout (format(tmp, -NumType.infinity)).newline;
600         Cout (format(tmp, toFloat("nan"w))).newline;
601         Cout (format(tmp, toFloat("-nan"d))).newline;
602         Cout (format(tmp, toFloat("inf"))).newline;
603         Cout (format(tmp, toFloat("-inf"))).newline;
604         +/
605             Cout (format(tmp, toFloat ("0.000000e+00"))).newline;
606         Cout (format(tmp, toFloat("0x8000000000000000"))).newline;
607         Cout (format(tmp, 1)).newline;
608         Cout (format(tmp, -0)).newline;
609         Cout (format(tmp, 0.000001)).newline.newline;
610 
611         Cout (format(tmp, 3.14159, 6, 0)).newline;
612         Cout (format(tmp, 3.0e10, 6, 3)).newline;
613         Cout (format(tmp, 314159, 6)).newline;
614         Cout (format(tmp, 314159123213, 6, 15)).newline;
615         Cout (format(tmp, 3.14159, 6, 2)).newline;
616         Cout (format(tmp, 3.14159, 3, 2)).newline;
617         Cout (format(tmp, 0.00003333, 6, 2)).newline;
618         Cout (format(tmp, 0.00333333, 6, 3)).newline;
619         Cout (format(tmp, 0.03333333, 6, 2)).newline;
620         Cout.newline;
621 
622         Cout (format(tmp, -3.14159, 6, 0)).newline;
623         Cout (format(tmp, -3e100, 6, 3)).newline;
624         Cout (format(tmp, -314159, 6)).newline;
625         Cout (format(tmp, -314159123213, 6, 15)).newline;
626         Cout (format(tmp, -3.14159, 6, 2)).newline;
627         Cout (format(tmp, -3.14159, 2, 2)).newline;
628         Cout (format(tmp, -0.00003333, 6, 2)).newline;
629         Cout (format(tmp, -0.00333333, 6, 3)).newline;
630         Cout (format(tmp, -0.03333333, 6, 2)).newline;
631         Cout.newline;
632 
633         Cout (format(tmp, -0.9999999, 7, 3)).newline;
634         Cout (format(tmp, -3.0e100, 6, 3)).newline;
635         Cout ((format(tmp, 1.0, 6))).newline;
636         Cout ((format(tmp, 30, 6))).newline;
637         Cout ((format(tmp, 3.14159, 6, 0))).newline;
638         Cout ((format(tmp, 3e100, 6, 3))).newline;
639         Cout ((format(tmp, 314159, 6))).newline;
640         Cout ((format(tmp, 314159123213.0, 3, 15))).newline;
641         Cout ((format(tmp, 3.14159, 6, 2))).newline;
642         Cout ((format(tmp, 3.14159, 4, 2))).newline;
643         Cout ((format(tmp, 0.00003333, 6, 2))).newline;
644         Cout ((format(tmp, 0.00333333, 6, 3))).newline;
645         Cout ((format(tmp, 0.03333333, 6, 2))).newline;
646         Cout (format(tmp, NumType.min, 6)).newline;
647         Cout (format(tmp, -1)).newline;
648         Cout (format(tmp, toFloat(format(tmp, -1)))).newline;
649         Cout.newline;
650     }
651 }