diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tango/tango/text/convert/Float.d	Fri Jan 11 17:57:40 2008 +0100
@@ -0,0 +1,520 @@
+/*******************************************************************************
+
+        copyright:      Copyright (c) 2004 Kris Bell. All rights reserved
+
+        license:        BSD style: $(LICENSE)
+        
+        version:        Initial release: Nov 2005
+
+        author:         Kris
+
+        A set of functions for converting between string and floating-
+        point values.
+
+        Applying the D "import alias" mechanism to this module is highly
+        recommended, in order to limit namespace pollution:
+        ---
+        import Float = tango.text.convert.Float;
+
+        auto f = Float.parse ("3.14159");
+        ---
+        
+*******************************************************************************/
+
+module tango.text.convert.Float;
+
+private import tango.core.Exception;
+
+private import Integer = tango.text.convert.Integer;
+
+private alias real NumType;
+
+private extern (C) NumType log10l(NumType x);
+
+/******************************************************************************
+
+        Constants
+                
+******************************************************************************/
+
+private enum 
+{
+        Dec = 2,                // default decimal places
+        Exp = 10,               // default switch to scientific notation
+}
+
+/******************************************************************************
+
+        Convert a formatted string of digits to a floating-point
+        number. Throws an exception where the input text is not
+        parsable in its entirety.
+        
+******************************************************************************/
+
+NumType toFloat(T) (T[] src)
+{
+        uint len;
+
+        auto x = parse (src, &len);
+        if (len < src.length)
+            throw new IllegalArgumentException ("Float.toFloat :: invalid number");
+        return x;
+}
+
+/******************************************************************************
+
+        Template wrapper to make life simpler. Returns a text version
+        of the provided value.
+
+        See format() for details
+
+******************************************************************************/
+
+char[] toString (NumType d, uint decimals=Dec, int e=Exp)
+{
+        char[64] tmp = void;
+        
+        return format (tmp, d, decimals, e).dup;
+}
+               
+/******************************************************************************
+
+        Template wrapper to make life simpler. Returns a text version
+        of the provided value.
+
+        See format() for details
+
+******************************************************************************/
+
+wchar[] toString16 (NumType d, uint decimals=Dec, int e=Exp)
+{
+        wchar[64] tmp = void;
+        
+        return format (tmp, d, decimals, e).dup;
+}
+               
+/******************************************************************************
+
+        Template wrapper to make life simpler. Returns a text version
+        of the provided value.
+
+        See format() for details
+
+******************************************************************************/
+
+dchar[] toString32 (NumType d, uint decimals=Dec, int e=Exp)
+{
+        dchar[64] tmp = void;
+        
+        return format (tmp, d, decimals, e).dup;
+}
+               
+/******************************************************************************
+
+        Convert a float to a string. This produces pretty good results
+        for the most part, though one should use David Gay's dtoa package
+        for best accuracy.
+
+        Note that the approach first normalizes a base10 mantissa, then
+        pulls digits from the left side whilst emitting them (rightward)
+        to the output.
+
+        The e parameter controls the number of exponent places emitted, 
+        and can thus control where the output switches to the scientific 
+        notation. For example, setting e=2 for 0.01 or 10.0 would result
+        in normal output. Whereas setting e=1 would result in both those
+        values being rendered in scientific notation instead. Setting e
+        to 0 forces that notation on for everything.
+
+        TODO: this should be replaced, as it is not sufficiently accurate 
+
+******************************************************************************/
+
+T[] format(T, D=double, U=uint) (T[] dst, D x, U decimals=Dec, int e=Exp)
+{return format!(T)(dst, x, decimals, e);}
+
+T[] format(T) (T[] dst, NumType x, uint decimals=Dec, int e=Exp)
+{
+        static T[] inf = "-inf";
+        static T[] nan = "-nan";
+
+        // extract the sign bit
+        static bool signed (NumType x)
+        {
+                static if (NumType.sizeof is 4) 
+                           return ((*cast(uint *)&x) & 0x8000_0000) != 0;
+
+                static if (NumType.sizeof is 8) 
+                           return ((*cast(ulong *)&x) & 0x8000_0000_0000_0000) != 0;
+                       else
+                          {
+                          auto pe = cast(ubyte *)&x;
+                          return (pe[9] & 0x80) != 0;
+                          }
+        }
+
+        // strip digits from the left of a normalized base-10 number
+        static int toDigit (inout NumType v, inout int count)
+        {
+                int digit;
+
+                // Don't exceed max digits storable in a real
+                // (-1 because the last digit is not always storable)
+                if (--count <= 0)
+                    digit = 0;
+                else
+                   {
+                   // remove leading digit, and bump
+                   digit = cast(int) v;
+                   v = (v - digit) * 10.0;
+                   }
+                return digit + '0';
+        }
+
+        // extract the sign
+        bool sign = signed (x);
+        if (sign)
+            x = -x;
+
+        if (x !<>= x)
+            return sign ? nan : nan[1..$];
+
+        if (x is x.infinity)
+            return sign ? inf : inf[1..$];
+
+        // assume no exponent
+        int exp = 0;
+
+        // don't scale if zero
+        if (x > 0.0)
+           {
+           // extract base10 exponent
+           exp = cast(int) log10l (x);
+
+           // round up a bit
+           auto d = decimals;
+           if (exp < 0)
+               d -= exp;
+           x += 0.5 / pow10 (d);
+
+           // extract base10 exponent
+           exp = cast(int) log10l (x);
+
+           // normalize base10 mantissa (0 < m < 10)
+           int len = exp;
+           if (exp < 0)
+               x *= pow10 (len = -exp);
+           else
+              x /= pow10 (exp);
+
+           // switch to short display if not enough space
+           if (len >= e)
+               e = 0; 
+           }
+
+        T* p = dst.ptr;
+        int count = NumType.dig;
+
+        // emit sign
+        if (sign)
+            *p++ = '-';
+
+        // are we doing +/-exp format?
+        if (e is 0)
+           {
+           assert (dst.length > decimals + 7);
+
+           // emit first digit, and decimal point
+           *p++ = toDigit (x, count);
+           if (decimals)
+              {
+              *p++ = '.';
+              if (exp < 0)
+                  count += exp;
+              }
+
+           // emit rest of mantissa
+           while (decimals-- > 0)
+                  *p++ = toDigit (x, count);
+
+           // emit exponent, if non zero
+           if (exp)
+              {
+              *p++ = 'e';
+              *p++ = (exp < 0) ? '-' : '+';
+              if (exp < 0)
+                  exp = -exp;
+
+              if (exp >= 100)
+                 {
+                 *p++ = (exp/100) + '0';
+                 exp %= 100;
+                 }
+
+              *p++ = (exp/10) + '0';
+              *p++ = (exp%10) + '0';
+              }
+           }
+        else
+           {
+           assert (dst.length >= (((exp < 0) ? 0 : exp) + decimals + 1));
+
+           // if fraction only, emit a leading zero
+           if (exp < 0)
+               *p++ = '0';
+           else
+              // emit all digits to the left of point
+              for (; exp >= 0; --exp)
+                     *p++ = toDigit (x, count);
+
+           // emit point
+           if (decimals)
+               *p++ = '.';
+
+           // emit leading fractional zeros?
+           for (++exp; exp < 0 && decimals > 0; --decimals, ++exp)
+                *p++ = '0';
+
+           // output remaining digits, if any. Trailing
+           // zeros are also returned from toDigit()
+           while (decimals-- > 0)
+                  *p++ = toDigit (x, count);
+           }
+
+        return dst [0..(p - dst.ptr)];
+}
+
+
+/******************************************************************************
+
+        Convert a formatted string of digits to a floating-point number.
+        Good for general use, but use David Gay's dtoa package if serious
+        rounding adjustments should be applied.
+
+******************************************************************************/
+
+NumType parse(T) (T[] src, uint* ate=null)
+{
+        T               c;
+        T*              p;
+        int             exp;
+        bool            sign;
+        uint            radix;
+        NumType         value = 0.0;
+
+        // remove leading space, and sign
+        c = *(p = src.ptr + Integer.trim (src, sign, radix));
+
+        // handle non-decimal representations
+        if (radix != 10)
+           {
+           long v = Integer.parse (src, radix, ate); 
+           return *cast(NumType*) &v;
+           }
+
+        // set begin and end checks
+        auto begin = p;
+        auto end = src.ptr + src.length;
+
+        // read leading digits; note that leading
+        // zeros are simply multiplied away
+        while (c >= '0' && c <= '9' && p < end)
+              {
+              value = value * 10 + (c - '0');
+              c = *++p;
+              }
+
+        // gobble up the point
+        if (c is '.' && p < end)
+            c = *++p;
+
+        // read fractional digits; note that we accumulate
+        // all digits ... very long numbers impact accuracy
+        // to a degree, but perhaps not as much as one might
+        // expect. A prior version limited the digit count,
+        // but did not show marked improvement. For maximum
+        // accuracy when reading and writing, use David Gay's
+        // dtoa package instead
+        while (c >= '0' && c <= '9' && p < end)
+              {
+              value = value * 10 + (c - '0');
+              c = *++p;
+              --exp;
+              } 
+
+        // did we get something?
+        if (value)
+           {
+           // parse base10 exponent?
+           if ((c is 'e' || c is 'E') && p < end )
+              {
+              uint eaten;
+              exp += Integer.parse (src[(++p-src.ptr) .. $], 0, &eaten);
+              p += eaten;
+              }
+
+           // adjust mantissa; note that the exponent has
+           // already been adjusted for fractional digits
+           if (exp < 0)
+               value /= pow10 (-exp);
+           else
+              value *= pow10 (exp);
+           }
+        else
+           // was it was nan instead?
+           if (p is begin)
+               if (p[0..3] == "inf")
+                   p += 3, value = value.infinity;
+               else
+                  if (p[0..3] == "nan")
+                      p += 3, value = value.nan;
+
+        // set parse length, and return value
+        if (ate)
+            *ate = p - src.ptr;
+
+        if (sign)
+            value = -value;
+        return value;
+}
+
+/******************************************************************************
+
+        Truncate trailing '0' and '.' from a string, such that 200.000 
+        becomes 200, and 20.10 becomes 20.1
+
+        Returns a potentially shorter slice of what you give it.
+
+******************************************************************************/
+
+T[] truncate(T) (T[] s)
+{
+        auto tmp = s;
+        auto i = tmp.length;
+        foreach (idx, c; tmp)
+                 if (c is '.')
+                     while (--i >= idx)
+                            if (tmp[i] != '0')
+                               {  
+                               if (tmp[i] is '.')
+                                   --i;
+                               s = tmp [0 .. i+1];
+                               while (--i >= idx)
+                                      if (tmp[i] is 'e')
+                                          return tmp;
+                               break;
+                               }
+        return s;
+}
+
+/******************************************************************************
+
+        Internal function to convert an exponent specifier to a floating
+        point value.
+
+******************************************************************************/
+
+private NumType pow10 (uint exp)
+{
+        static  NumType[] Powers = 
+                [
+                1.0e1L,
+                1.0e2L,
+                1.0e4L,
+                1.0e8L,
+                1.0e16L,
+                1.0e32L,
+                1.0e64L,
+                1.0e128L,
+                1.0e256L,
+                ];
+
+        if (exp >= 512)
+            throw new IllegalArgumentException ("Float.pow10 :: exponent too large");
+
+        NumType mult = 1.0;
+        foreach (NumType power; Powers)
+                {
+                if (exp & 1)
+                    mult *= power;
+                if ((exp >>= 1) is 0)
+                     break;
+                }
+        return mult;
+}
+
+
+/******************************************************************************
+
+******************************************************************************/
+
+debug (UnitTest)
+{
+        unittest
+        {
+                char[64] tmp;
+
+                auto f = parse ("nan");
+                assert (format(tmp, f) == "nan");
+                f = parse ("inf");
+                assert (format(tmp, f) == "inf");
+                f = parse ("-nan");
+                assert (format(tmp, f) == "-nan");
+                f = parse (" -inf");
+                assert (format(tmp, f) == "-inf");
+
+                assert (format (tmp, 3.14159, 6) == "3.141590");
+                assert (format (tmp, 3.14159, 4) == "3.1416");
+                assert (parse ("3.5") == 3.5);
+                assert (format(tmp, parse ("3.14159"), 6) == "3.141590");
+        }
+}
+
+
+debug (Float)
+{
+        import tango.io.Console;
+
+        void main() 
+        {
+                char[20] tmp;
+
+                Cout (format(tmp, 1)).newline;
+                Cout (format(tmp, 0)).newline;
+                Cout (format(tmp, 0.000001)).newline;
+
+                Cout (format(tmp, 3.14159, 6, 0)).newline;
+                Cout (format(tmp, 3e100, 6, 3)).newline;
+                Cout (format(tmp, 314159, 6)).newline;
+                Cout (format(tmp, 314159123213, 6, 15)).newline;
+                Cout (format(tmp, 3.14159, 6, 2)).newline;
+                Cout (format(tmp, 3.14159, 6, 2)).newline;
+                Cout (format(tmp, 0.00003333, 6, 2)).newline;
+                Cout (format(tmp, 0.00333333, 6, 3)).newline;
+                Cout (format(tmp, 0.03333333, 6, 2)).newline;
+                Cout.newline;
+
+                Cout (format(tmp, -3.14159, 6, 0)).newline;
+                Cout (format(tmp, -3e100, 6, 3)).newline;
+                Cout (format(tmp, -314159, 6)).newline;
+                Cout (format(tmp, -314159123213, 6, 15)).newline;
+                Cout (format(tmp, -3.14159, 6, 2)).newline;
+                Cout (format(tmp, -3.14159, 6, 2)).newline;
+                Cout (format(tmp, -0.00003333, 6, 2)).newline;
+                Cout (format(tmp, -0.00333333, 6, 3)).newline;
+                Cout (format(tmp, -0.03333333, 6, 2)).newline;
+                Cout.newline;
+
+                Cout (truncate(format(tmp, 30, 6))).newline;
+                Cout (truncate(format(tmp, 3.14159, 6, 0))).newline;
+                Cout (truncate(format(tmp, 3e100, 6, 3))).newline;
+                Cout (truncate(format(tmp, 314159, 6))).newline;
+                Cout (truncate(format(tmp, 314159123213, 6, 15))).newline;
+                Cout (truncate(format(tmp, 3.14159, 6, 2))).newline;
+                Cout (truncate(format(tmp, 3.14159, 6, 2))).newline;
+                Cout (truncate(format(tmp, 0.00003333, 6, 2))).newline;
+                Cout (truncate(format(tmp, 0.00333333, 6, 3))).newline;
+                Cout (truncate(format(tmp, 0.03333333, 6, 2))).newline;
+
+        }
+}