Mercurial > projects > ldc
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; + + } +}