view 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 source

/*******************************************************************************

        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;

        }
}