123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944 |
|
/*******************************************************************************
copyright: Copyright (c) 2004 Kris Bell. All rights reserved
license: BSD style: $(LICENSE)
version: Nov 2005: Initial release
Jan 2010: added internal ecvt()
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;
/******************************************************************************
select an internal version
******************************************************************************/
version = float_internal;
private alias real NumType;
/******************************************************************************
optional math functions
******************************************************************************/
private extern (C)
{
double log10 (double x);
double ceil (double num);
double modf (double num, double *i);
double pow (double base, double exp);
real log10l (real x);
real ceill (real num);
real modfl (real num, real *i);
real powl (real base, real exp);
int printf (char*, ...);
version (float_lib)
{
version (Windows)
{
alias ecvt econvert;
alias ecvt fconvert;
char* ecvt (double d, int digits, int* decpt, int* sign);
char* fcvt (double d, int digits, int* decpt, int* sign);
}
else
{
alias ecvtl econvert;
alias ecvtl fconvert;
char* ecvtl (real d, int digits, int* decpt, int* sign);
char* fcvtl (real d, int digits, int* decpt, int* sign);
}
}
}
/******************************************************************************
Constants
******************************************************************************/
private enum
{
Pad = 0, // default trailing decimal zero
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) (const(T[]) src)
{
size_t len;
auto x = parse (src, &len);
if (len < src.length || len == 0)
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, cast(int)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, cast(int)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, cast(int)decimals, e).dup;
}
/******************************************************************************
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;
int i = tmp.length;
foreach (int idx, T 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;
}
/******************************************************************************
Extract a sign-bit
******************************************************************************/
private bool negative (NumType x)
{
static if (NumType.sizeof is 4)
return ((*cast(uint *)&x) & 0x8000_0000) != 0;
else
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;
}
}
/******************************************************************************
Convert a floating-point number to a string.
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. Parameter pad will
append trailing '0' decimals when set ~ otherwise trailing '0's
will be elided
******************************************************************************/
T[] format(T) (T[] dst, NumType x, int decimals=Dec, int e=Exp, bool pad=Pad)
{
char* end,
str;
int exp,
sign,
mode=5;
char[32] buf = void;
// test exponent to determine mode
exp = (x is 0) ? 1 : cast(int) log10l (x < 0 ? -x : x);
if (exp <= -e || exp >= e)
mode = 2, ++decimals;
version (float_internal)
str = convertl (buf.ptr, x, decimals, &exp, &sign, mode is 5);
version (float_dtoa)
str = dtoa (x, mode, decimals, &exp, &sign, &end);
version (float_lib)
{
if (mode is 5)
str = fconvert (x, decimals, &exp, &sign);
else
str = econvert (x, decimals, &exp, &sign);
}
auto p = dst.ptr;
if (sign)
*p++ = '-';
if (exp is 9999)
while (*str)
*p++ = *str++;
else
{
if (mode is 2)
{
--exp;
*p++ = *str++;
if (*str || pad)
{
auto d = p;
*p++ = '.';
while (*str)
*p++ = *str++;
if (pad)
while (p-d < decimals)
*p++ = '0';
}
*p++ = 'e';
if (exp < 0)
*p++ = '-', exp = -exp;
else
*p++ = '+';
if (exp >= 1000)
{
*p++ = cast(T)((exp/1000) + '0');
exp %= 1000;
}
if (exp >= 100)
{
*p++ = cast(char)(exp / 100 + '0');
exp %= 100;
}
*p++ = cast(char)(exp / 10 + '0');
*p++ = cast(char)(exp % 10 + '0');
}
else
{
if (exp <= 0)
*p++ = '0';
else
for (; exp > 0; --exp)
*p++ = (*str) ? *str++ : '0';
if (*str || pad)
{
*p++ = '.';
auto d = p;
for (; exp < 0; ++exp)
*p++ = '0';
while (*str)
*p++ = *str++;
if (pad)
while (p-d < decimals)
*p++ = '0';
}
}
}
// stuff a C terminator in there too ...
*p = 0;
return dst[0..(p - dst.ptr)];
}
/******************************************************************************
ecvt() and fcvt() for 80bit FP, which DMD does not include. Based
upon the following:
Copyright (c) 2009 Ian Piumarta
All rights reserved.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the 'Software'), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, provided that the
above copyright notice(s) and this permission notice appear in all
copies of the Software.
******************************************************************************/
version (float_internal)
{
private char *convertl (char* buf, real value, int ndigit, int *decpt, int *sign, int fflag)
{
if ((*sign = negative(value)) != 0)
value = -value;
*decpt = 9999;
if (value !<>= value)
return cast(char*)"nan\0";
if (value is value.infinity)
return cast(char*)"inf\0";
int exp10 = (value is 0) ? !fflag : cast(int) ceill(log10l(value));
if (exp10 < -4931)
exp10 = -4931;
value *= powl (10.0, -exp10);
if (value)
{
while (value < 0.1) { value *= 10; --exp10; }
while (value >= 1.0) { value /= 10; ++exp10; }
}
assert(value is 0 || (0.1 <= value && value < 1.0));
//auto zero = pad ? int.max : 1;
auto zero = 1;
if (fflag)
{
// if (! pad)
zero = exp10;
if (ndigit + exp10 < 0)
{
*decpt= -ndigit;
return cast(char*)"\0";
}
ndigit += exp10;
}
*decpt = exp10;
int ptr = 1;
if (ndigit > real.dig)
ndigit = real.dig;
//printf ("< flag %d, digits %d, exp10 %d, decpt %d\n", fflag, ndigit, exp10, *decpt);
while (ptr <= ndigit)
{
real i = void;
value = modfl (value * 10, &i);
buf [ptr++]= cast(char)('0' + cast(int) i);
}
if (value >= 0.5)
while (--ptr && ++buf[ptr] > '9')
buf[ptr] = (ptr > zero) ? '\0' : '0';
else
for (auto i=ptr; i && --i > zero && buf[i] is '0';)
buf[i] = '\0';
if (ptr)
{
buf [ndigit + 1] = '\0';
return buf + 1;
}
if (fflag)
{
++ndigit;
}
buf[0]= '1';
++*decpt;
buf[ndigit]= '\0';
return buf;
}
}
/******************************************************************************
David Gay's extended conversions between string and floating-point
numeric representations. Use these where you need extended accuracy
for convertions.
Note that this class requires the attendent file dtoa.c be compiled
and linked to the application
******************************************************************************/
version (float_dtoa)
{
private extern(C)
{
// these should be linked in via dtoa.c
double strtod (const(char*) s00, const(char*)* se);
char* dtoa (double d, int mode, int ndigits, int* decpt, int* sign, char** rve);
}
/**********************************************************************
Convert a formatted string of digits to a floating-
point number.
**********************************************************************/
NumType parse (const(char[]) src, size_t* ate=null)
{
const(char)* end;
auto value = strtod (src.ptr, &end);
assert (end <= src.ptr + src.length);
if (ate)
*ate = end - src.ptr;
return value;
}
/**********************************************************************
Convert a formatted string of digits to a floating-
point number.
**********************************************************************/
NumType parse (const(wchar[]) src, size_t* ate=null)
{
// cheesy hack to avoid pre-parsing :: max digits == 100
char[100] tmp = void;
auto p = tmp.ptr;
auto e = p + tmp.length;
foreach (c; src)
if (p < e && (c & 0x80) is 0)
*p++ = c;
else
break;
return parse (tmp[0..p-tmp.ptr], ate);
}
/**********************************************************************
Convert a formatted string of digits to a floating-
point number.
**********************************************************************/
NumType parse (const(dchar[]) src, size_t* ate=null)
{
// cheesy hack to avoid pre-parsing :: max digits == 100
char[100] tmp = void;
auto p = tmp.ptr;
auto e = p + tmp.length;
foreach (c; src)
if (p < e && (c & 0x80) is 0)
*p++ = c;
else
break;
return parse (tmp[0..p-tmp.ptr], ate);
}
}
else
{
private import Integer = tango.text.convert.Integer;
/******************************************************************************
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) (const(T[]) src, size_t* ate=null)
{
T c;
const(T)* p;
int exp;
bool sign;
uint radix;
NumType value = 0.0;
static bool match (const(T)* aa, const(T[]) bb)
{
foreach (b; bb)
{
auto a = cast(T)*aa++;
if (a >= 'A' && a <= 'Z')
a += 'a' - 'A';
if (a != b)
return false;
}
return true;
}
// remove leading space, and sign
p = src.ptr + Integer.trim (src, sign, radix);
// bail out if the string is empty
if (src.length is 0 || p > &src[$-1])
return NumType.nan;
c = *p;
// 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 (p > begin)
{
// parse base10 exponent?
if ((c is 'e' || c is 'E') && p < end )
{
size_t 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
if (end - p >= 3)
switch (*p)
{
case 'I': case 'i':
if (match (p+1, "nf"))
{
value = value.infinity;
p += 3;
if (end - p >= 5 && match (p, "inity"))
p += 5;
}
break;
case 'N': case 'n':
if (match (p+1, "an"))
{
value = value.nan;
p += 3;
}
break;
default:
break;
}
// set parse length, and return value
if (ate)
*ate = p - src.ptr;
if (sign)
value = -value;
return value;
}
/******************************************************************************
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,
1.0e512L,
1.0e1024L,
1.0e2048L,
1.0e4096L,
1.0e8192L,
];
if (exp >= 16384)
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;
}
}
version (float_old)
{
/******************************************************************************
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, bool pad=Pad)
{return format!(T)(dst, x, decimals, e, pad);}
T[] format(T) (T[] dst, NumType x, uint decimals=Dec, int e=Exp, bool pad=Pad)
{
enum immutable(T)[] inf = "-inf";
enum immutable(T)[] nan = "-nan";
// strip digits from the left of a normalized base-10 number
static int toDigit (ref NumType v, ref 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 = negative (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;
int abs = 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);
// normalize base10 mantissa (0 < m < 10)
abs = exp = cast(int) log10l (x);
if (exp > 0)
x /= pow10 (exp);
else
abs = -exp;
// switch to exponent display as necessary
if (abs >= 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);
if (exp < 0)
x *= pow10 (abs+1);
// emit first digit, and decimal point
*p++ = cast(T) toDigit (x, count);
if (decimals)
{
*p++ = '.';
// emit rest of mantissa
while (decimals-- > 0)
*p++ = cast(T) toDigit (x, count);
if (pad is false)
{
while (*(p-1) is '0')
--p;
if (*(p-1) is '.')
--p;
}
}
// emit exponent, if non zero
if (abs)
{
*p++ = 'e';
*p++ = (exp < 0) ? '-' : '+';
if (abs >= 1000)
{
*p++ = cast(T)((abs/1000) + '0');
abs %= 1000;
*p++ = cast(T)((abs/100) + '0');
abs %= 100;
}
else
if (abs >= 100)
{
*p++ = cast(T)((abs/100) + '0');
abs %= 100;
}
*p++ = cast(T)((abs/10) + '0');
*p++ = cast(T)((abs%10) + '0');
}
}
else
{
assert (dst.length >= (((exp < 0) ? 0 : exp) + decimals + 1));
// if fraction only, emit a leading zero
if (exp < 0)
{
x *= pow10 (abs);
*p++ = '0';
}
else
// emit all digits to the left of point
for (; exp >= 0; --exp)
*p++ = cast(T )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++ = cast(T) toDigit (x, count);
if (pad is false)
{
while (*(p-1) is '0')
--p;
if (*(p-1) is '.')
--p;
}
}
}
return dst [0..(p - dst.ptr)];
}
}
/******************************************************************************
******************************************************************************/
debug (UnitTest)
{
import tango.io.Console;
unittest
{
char[164] 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.14159");
assert (format (tmp, 3.14159, 4) == "3.1416");
assert (parse ("3.5".dup) == 3.5);
assert (format(tmp, parse ("3.14159".dup), 6) == "3.14159");
assert (format(tmp, 0.09999, 2, 0, true) == "1.00e-01");
}
}
debug (Float)
{
import tango.io.Console;
void main()
{
char[500] tmp;
/+
Cout (format(tmp, NumType.max)).newline;
Cout (format(tmp, -NumType.nan)).newline;
Cout (format(tmp, -NumType.infinity)).newline;
Cout (format(tmp, toFloat("nan"w))).newline;
Cout (format(tmp, toFloat("-nan"d))).newline;
Cout (format(tmp, toFloat("inf"))).newline;
Cout (format(tmp, toFloat("-inf"))).newline;
+/
Cout (format(tmp, toFloat ("0.000000e+00"))).newline;
Cout (format(tmp, toFloat("0x8000000000000000"))).newline;
Cout (format(tmp, 1)).newline;
Cout (format(tmp, -0)).newline;
Cout (format(tmp, 0.000001)).newline.newline;
Cout (format(tmp, 3.14159, 6, 0)).newline;
Cout (format(tmp, 3.0e10, 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, 3, 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, 2, 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, -0.9999999, 7, 3)).newline;
Cout (format(tmp, -3.0e100, 6, 3)).newline;
Cout ((format(tmp, 1.0, 6))).newline;
Cout ((format(tmp, 30, 6))).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.0, 3, 15))).newline;
Cout ((format(tmp, 3.14159, 6, 2))).newline;
Cout ((format(tmp, 3.14159, 4, 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 (format(tmp, NumType.min, 6)).newline;
Cout (format(tmp, -1)).newline;
Cout (format(tmp, toFloat(format(tmp, -1)))).newline;
Cout.newline;
}
}
|