Make real2String more readable
This commit is contained in:
parent
a84c71f26d
commit
a38242d0ac
@ -14,9 +14,16 @@
|
||||
*/
|
||||
module tanya.format;
|
||||
|
||||
import tanya.container.string;
|
||||
public import tanya.format.conv;
|
||||
import tanya.math;
|
||||
import tanya.memory.op;
|
||||
import tanya.meta.metafunction;
|
||||
import tanya.meta.trait;
|
||||
|
||||
// Integer and floating point to string conversion is based on stb_sprintf
|
||||
// written by Jeff Roberts.
|
||||
|
||||
// Returns the last part of buffer with converted number.
|
||||
package(tanya) char[] integral2String(T)(T number, return ref char[21] buffer)
|
||||
if (isIntegral!T)
|
||||
@ -93,3 +100,537 @@ if (isIntegral!T)
|
||||
assert(integral2String(uint.max, buf) == "4294967295");
|
||||
assert(integral2String(int.min, buf) == "-2147483648");
|
||||
}
|
||||
|
||||
/*
|
||||
* Double-double high-precision floating point number.
|
||||
*
|
||||
* The first element is a base value corresponding to the nearest approximation
|
||||
* of the target $(D_PSYMBOL HP) value, and the second element is an offset
|
||||
* value corresponding to the difference between the target value and the base.
|
||||
* Thus, the $(D_PSYMBOL HP) value represented is the sum of the base and the
|
||||
* offset.
|
||||
*/
|
||||
private struct HP
|
||||
{
|
||||
private double base;
|
||||
private double offset = 0.0;
|
||||
|
||||
private void normalize() @nogc nothrow pure @safe
|
||||
{
|
||||
const double target = this.base + this.offset;
|
||||
this.offset -= target - this.base;
|
||||
this.base = target;
|
||||
}
|
||||
|
||||
private void multiply(ref const HP x, ref const HP y)
|
||||
@nogc nothrow pure @safe
|
||||
{
|
||||
double ahi, bhi;
|
||||
long bt;
|
||||
this.base = x.base * y.base;
|
||||
copyFp(x.base, bt);
|
||||
bt &= (~cast(ulong) 0) << 27;
|
||||
copyFp(bt, ahi);
|
||||
|
||||
double alo = x.base - ahi;
|
||||
copyFp(y.base, bt);
|
||||
bt &= (~cast(ulong) 0) << 27;
|
||||
copyFp(bt, bhi);
|
||||
|
||||
double blo = y.base - bhi;
|
||||
this.offset = ahi * bhi - this.base + ahi * blo + alo * bhi + alo * blo;
|
||||
this.offset += x.base * y.offset + x.offset * y.base;
|
||||
}
|
||||
}
|
||||
|
||||
private enum int special = 0x7000;
|
||||
|
||||
private static const ulong[20] powersOf10 = [
|
||||
1,
|
||||
10,
|
||||
100,
|
||||
1000,
|
||||
10000,
|
||||
100000,
|
||||
1000000,
|
||||
10000000,
|
||||
100000000,
|
||||
1000000000,
|
||||
10000000000UL,
|
||||
100000000000UL,
|
||||
1000000000000UL,
|
||||
10000000000000UL,
|
||||
100000000000000UL,
|
||||
1000000000000000UL,
|
||||
10000000000000000UL,
|
||||
100000000000000000UL,
|
||||
1000000000000000000UL,
|
||||
10000000000000000000UL,
|
||||
];
|
||||
|
||||
private static const char[201] digitPairs =
|
||||
"0001020304050607080910111213141516171819202122232425262728293031323334353"
|
||||
~ "6373839404142434445464748495051525354555657585960616263646566676869707172"
|
||||
~ "737475767778798081828384858687888990919293949596979899";
|
||||
|
||||
private static const HP[23] bottom = [
|
||||
HP(1e+000), HP(1e+001), HP(1e+002), HP(1e+003), HP(1e+004), HP(1e+005),
|
||||
HP(1e+006), HP(1e+007), HP(1e+008), HP(1e+009), HP(1e+010), HP(1e+011),
|
||||
HP(1e+012), HP(1e+013), HP(1e+014), HP(1e+015), HP(1e+016), HP(1e+017),
|
||||
HP(1e+018), HP(1e+019), HP(1e+020), HP(1e+021), HP(1e+022),
|
||||
];
|
||||
|
||||
private static const HP[22] negativeBottom = [
|
||||
HP(1e-001, -5.551115123125783e-018),
|
||||
HP(1e-002, -2.0816681711721684e-019),
|
||||
HP(1e-003, -2.0816681711721686e-020),
|
||||
HP(1e-004, -4.7921736023859299e-021),
|
||||
HP(1e-005, -8.1803053914031305e-022),
|
||||
HP(1e-006, 4.5251888174113741e-023),
|
||||
HP(1e-007, 4.5251888174113739e-024),
|
||||
HP(1e-008, -2.0922560830128471e-025),
|
||||
HP(1e-009, -6.2281591457779853e-026),
|
||||
HP(1e-010, -3.6432197315497743e-027),
|
||||
HP(1e-011, 6.0503030718060191e-028),
|
||||
HP(1e-012, 2.0113352370744385e-029),
|
||||
HP(1e-013, -3.0373745563400371e-030),
|
||||
HP(1e-014, 1.1806906454401013e-032),
|
||||
HP(1e-015, -7.7705399876661076e-032),
|
||||
HP(1e-016, 2.0902213275965398e-033),
|
||||
HP(1e-017, -7.1542424054621921e-034),
|
||||
HP(1e-018, -7.1542424054621926e-035),
|
||||
HP(1e-019, 2.4754073164739869e-036),
|
||||
HP(1e-020, 5.4846728545790429e-037),
|
||||
HP(1e-021, 9.2462547772103625e-038),
|
||||
HP(1e-022, -4.8596774326570872e-039),
|
||||
];
|
||||
|
||||
private static const HP[13] top = [
|
||||
HP(1e+023, 8388608),
|
||||
HP(1e+046, 6.8601809640529717e+028),
|
||||
HP(1e+069, -7.253143638152921e+052),
|
||||
HP(1e+092, -4.3377296974619174e+075),
|
||||
HP(1e+115, -1.5559416129466825e+098),
|
||||
HP(1e+138, -3.2841562489204913e+121),
|
||||
HP(1e+161, -3.7745893248228135e+144),
|
||||
HP(1e+184, -1.7356668416969134e+167),
|
||||
HP(1e+207, -3.8893577551088374e+190),
|
||||
HP(1e+230, -9.9566444326005119e+213),
|
||||
HP(1e+253, 6.3641293062232429e+236),
|
||||
HP(1e+276, -5.2069140800249813e+259),
|
||||
HP(1e+299, -5.2504760255204387e+282),
|
||||
];
|
||||
|
||||
private static const HP[13] negativeTop = [
|
||||
HP(1e-023, 3.9565301985100693e-040L),
|
||||
HP(1e-046, -2.299904345391321e-063L),
|
||||
HP(1e-069, 3.6506201437945798e-086L),
|
||||
HP(1e-092, 1.1875228833981544e-109L),
|
||||
HP(1e-115, -5.0644902316928607e-132L),
|
||||
HP(1e-138, -6.7156837247865426e-155L),
|
||||
HP(1e-161, -2.812077463003139e-178L),
|
||||
HP(1e-184, -5.7778912386589953e-201L),
|
||||
HP(1e-207, 7.4997100559334532e-224L),
|
||||
HP(1e-230, -4.6439668915134491e-247L),
|
||||
HP(1e-253, -6.3691100762962136e-270L),
|
||||
HP(1e-276, -9.436808465446358e-293L),
|
||||
HP(1e-299, 8.0970921678014997e-317L),
|
||||
];
|
||||
|
||||
/*
|
||||
* Copies double into long and back bitwise.
|
||||
*/
|
||||
private void copyFp(T, U)(ref const U src, ref T dest) @trusted
|
||||
if (T.sizeof == U.sizeof)
|
||||
{
|
||||
copy((&src)[0 .. 1], (&dest)[0 .. 1]);
|
||||
}
|
||||
|
||||
// Power can be -323 to +350.
|
||||
private HP raise2Power10(const HP value, int power)
|
||||
@nogc nothrow pure @safe
|
||||
{
|
||||
HP result;
|
||||
if ((power >= 0) && (power <= 22))
|
||||
{
|
||||
result.multiply(value, bottom[power]);
|
||||
}
|
||||
else
|
||||
{
|
||||
HP p2;
|
||||
int e = power;
|
||||
|
||||
if (power < 0)
|
||||
{
|
||||
e = -e;
|
||||
}
|
||||
int et = (e * 0x2c9) >> 14; // % 23
|
||||
if (et > 13)
|
||||
{
|
||||
et = 13;
|
||||
}
|
||||
int eb = e - (et * 23);
|
||||
|
||||
result = value;
|
||||
if (power < 0)
|
||||
{
|
||||
if (eb != 0)
|
||||
{
|
||||
--eb;
|
||||
result.multiply(value, negativeBottom[eb]);
|
||||
}
|
||||
if (et)
|
||||
{
|
||||
result.normalize();
|
||||
--et;
|
||||
p2.multiply(result, negativeTop[et]);
|
||||
result = p2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (eb != 0)
|
||||
{
|
||||
e = eb;
|
||||
if (eb > 22)
|
||||
{
|
||||
eb = 22;
|
||||
}
|
||||
e -= eb;
|
||||
result.multiply(value, bottom[eb]);
|
||||
if (e)
|
||||
{
|
||||
result.normalize();
|
||||
p2.multiply(result, bottom[e]);
|
||||
result = p2;
|
||||
}
|
||||
}
|
||||
if (et != 0)
|
||||
{
|
||||
result.normalize();
|
||||
--et;
|
||||
p2.multiply(result, top[et]);
|
||||
result = p2;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.normalize();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a float value, returns the significant bits in bits, and the position
|
||||
* of the decimal point in $(D_PARAM decimalPos). +/-Inf and NaN are specified
|
||||
* by special values returned in the $(D_PARAM decimalPos). Sing bit is set in
|
||||
* $(D_PARAM sign).
|
||||
*/
|
||||
private const(char)[] real2String(double value,
|
||||
ref char[512] buffer,
|
||||
out int exponent,
|
||||
out bool sign) @nogc nothrow pure @trusted
|
||||
{
|
||||
long bits;
|
||||
copyFp(value, bits);
|
||||
|
||||
exponent = (bits >> 52) & 0x7ff;
|
||||
sign = signBit(value);
|
||||
if (sign)
|
||||
{
|
||||
value = -value;
|
||||
}
|
||||
|
||||
if (exponent == 2047) // Is NaN or Inf?
|
||||
{
|
||||
exponent = special;
|
||||
return (bits & ((1UL << 52) - 1)) != 0 ? "NaN" : "Inf";
|
||||
}
|
||||
|
||||
if (exponent == 0) // Is zero or denormal?
|
||||
{
|
||||
if ((bits << 1) == 0) // Zero.
|
||||
{
|
||||
exponent = 1;
|
||||
buffer[0] = '0';
|
||||
return buffer[0 .. 1];
|
||||
}
|
||||
|
||||
// Find the right exponent for denormals.
|
||||
for (long cursor = 1UL << 51; (bits & cursor) == 0; cursor >>= 1)
|
||||
{
|
||||
--exponent;
|
||||
}
|
||||
}
|
||||
|
||||
// "617 / 2048" and "1233 / 4096" are estimations for the common logarithm
|
||||
// (log10) of 2. Multiplied by a binary number it tells how big the number
|
||||
// is in decimals, so it translates the binary exponent into decimal
|
||||
// format. The estimation is tweaked to hit or undershoot by no more than
|
||||
// 1 of log10 of all exponents 1..2046.
|
||||
int tens = exponent - 1023; // Bias.
|
||||
if (tens < 0)
|
||||
{
|
||||
tens = tens * 617 / 2048;
|
||||
}
|
||||
else
|
||||
{
|
||||
tens = tens * 1233 / 4096 + 1;
|
||||
}
|
||||
|
||||
// Move the significant bits into position and stick them into an int.
|
||||
HP p = raise2Power10(HP(value), 18 - tens);
|
||||
|
||||
// Get full as much precision from double-double as possible.
|
||||
bits = cast(long) p.base;
|
||||
double vh = cast(double) bits;
|
||||
double ahi = p.base - vh;
|
||||
double t = ahi - p.base;
|
||||
double alo = p.base - ahi + t - vh - t;
|
||||
bits += cast(long) (ahi + alo + p.offset);
|
||||
|
||||
// Check if we undershot (bits >= 10 ^ 19).
|
||||
if ((cast(ulong) bits) >= 1000000000000000000UL)
|
||||
{
|
||||
++tens;
|
||||
}
|
||||
|
||||
// Now do the rounding in integer land.
|
||||
enum uint fracDigits = 6;
|
||||
|
||||
uint dg = 1;
|
||||
if ((cast(ulong) bits) >= powersOf10[9])
|
||||
{
|
||||
dg = 10;
|
||||
}
|
||||
uint length;
|
||||
while ((cast(ulong) bits) >= powersOf10[dg])
|
||||
{
|
||||
++dg;
|
||||
if (dg == 20)
|
||||
{
|
||||
goto NoRound;
|
||||
}
|
||||
}
|
||||
if (fracDigits < dg)
|
||||
{
|
||||
// Add 0.5 at the right position and round.
|
||||
length = dg - fracDigits;
|
||||
if (length >= 24)
|
||||
{
|
||||
goto NoRound;
|
||||
}
|
||||
ulong r = powersOf10[length];
|
||||
bits = bits + (r / 2);
|
||||
if ((cast(ulong) bits) >= powersOf10[dg])
|
||||
{
|
||||
++tens;
|
||||
}
|
||||
bits /= r;
|
||||
}
|
||||
NoRound:
|
||||
|
||||
// Kill long trailing runs of zeros.
|
||||
if (bits)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
if (bits <= 0xffffffff)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (bits % 1000)
|
||||
{
|
||||
goto Zeroed;
|
||||
}
|
||||
bits /= 1000;
|
||||
}
|
||||
auto n = cast(uint) bits;
|
||||
while ((n % 1000) == 0)
|
||||
{
|
||||
n /= 1000;
|
||||
}
|
||||
bits = n;
|
||||
}
|
||||
Zeroed:
|
||||
|
||||
// Convert to string.
|
||||
auto result = buffer.ptr + 64;
|
||||
length = 0;
|
||||
while (true)
|
||||
{
|
||||
uint n;
|
||||
char* o = result - 8;
|
||||
// Do the conversion in chunks of U32s (avoid most 64-bit divides,
|
||||
// worth it, constant denomiators be damned).
|
||||
if (bits >= 100000000)
|
||||
{
|
||||
n = cast(uint) (bits % 100000000);
|
||||
bits /= 100000000;
|
||||
}
|
||||
else
|
||||
{
|
||||
n = cast(uint) bits;
|
||||
bits = 0;
|
||||
}
|
||||
while (n)
|
||||
{
|
||||
result -= 2;
|
||||
*cast(ushort*) result = *cast(ushort*) &digitPairs[(n % 100) * 2];
|
||||
n /= 100;
|
||||
length += 2;
|
||||
}
|
||||
if (bits == 0)
|
||||
{
|
||||
if ((length != 0) && (result[0] == '0'))
|
||||
{
|
||||
++result;
|
||||
--length;
|
||||
}
|
||||
break;
|
||||
}
|
||||
for (; result !is o; ++length, --result)
|
||||
{
|
||||
*result = '0';
|
||||
}
|
||||
}
|
||||
exponent = tens;
|
||||
return result[0 .. length];
|
||||
}
|
||||
|
||||
package(tanya) String format(string fmt, Args...)(Args args)
|
||||
{
|
||||
String ret;
|
||||
|
||||
foreach (spec; ParseFmt!(fmt, 0))
|
||||
{
|
||||
static if (is(typeof(spec) == string))
|
||||
{
|
||||
ret.insertBack(spec);
|
||||
}
|
||||
else static if (isSomeChar!(Args[0]))
|
||||
{
|
||||
ret.insertBack(args[0]);
|
||||
}
|
||||
else static if (isSomeString!(Args[0]))
|
||||
{
|
||||
if (args[0] is null)
|
||||
{
|
||||
ret.insertBack("null");
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.insertBack(args[0]);
|
||||
}
|
||||
}
|
||||
else static if (isIntegral!(Args[0]))
|
||||
{
|
||||
char[21] buffer = void;
|
||||
ret.insertBack(integral2String(args[0], buffer));
|
||||
}
|
||||
else static if (isFloatingPoint!(Args[0]))
|
||||
{
|
||||
int precision = 6;
|
||||
bool negative;
|
||||
int decimalPos;
|
||||
uint l;
|
||||
char[512] num;
|
||||
|
||||
// Read the double into a string
|
||||
const(char)[] sn = real2String(args[0], num, decimalPos, negative);
|
||||
}
|
||||
else
|
||||
{
|
||||
static assert(false, "Unable to format " ~ Args[0].stringof);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@nogc pure @system unittest
|
||||
{
|
||||
// Format without arguments.
|
||||
assert(format!""() == "");
|
||||
assert(format!"asdfqweryxcvz"() == "asdfqweryxcvz");
|
||||
|
||||
// String printing.
|
||||
assert(format!"{}"('c') == "c"); // char.
|
||||
assert(format!"{}"('с') == "с"); // wchar.
|
||||
|
||||
assert(format!"{}"("Протопи ты мне баньку по-белому!")
|
||||
== "Протопи ты мне баньку по-белому!");
|
||||
assert(format!"{}"(cast(string) null) == "null");
|
||||
|
||||
// Integer conversions.
|
||||
assert(format!"{}"(8) == "8");
|
||||
assert(format!"{}"(8U) == "8");
|
||||
assert(format!"{}"(-8) == "-8");
|
||||
assert(format!"{}"(-8L) == "-8");
|
||||
|
||||
// Floating point conversions.
|
||||
auto f = format!"{}"(1.2345);
|
||||
}
|
||||
|
||||
private struct FormatSpec
|
||||
{
|
||||
}
|
||||
|
||||
// Returns the position of `tag` in `fmt`. If `tag` can't be found, returns the
|
||||
// length of `fmt`.
|
||||
private size_t specPosition(string fmt, char tag)()
|
||||
{
|
||||
foreach (i, c; fmt)
|
||||
{
|
||||
if (c == tag)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return fmt.length;
|
||||
}
|
||||
|
||||
private template ParseFmt(string fmt, size_t pos = 0)
|
||||
{
|
||||
static if (fmt.length == 0)
|
||||
{
|
||||
alias ParseFmt = AliasSeq!();
|
||||
}
|
||||
else static if (fmt[0] == '{')
|
||||
{
|
||||
static if (fmt.length > 1 && fmt[1] == '{')
|
||||
{
|
||||
enum size_t pos = specPosition!(fmt[2 .. $], '{') + 2;
|
||||
alias ParseFmt = AliasSeq!(fmt[1 .. pos],
|
||||
ParseFmt!(fmt[pos .. $], pos));
|
||||
}
|
||||
else
|
||||
{
|
||||
enum size_t pos = specPosition!(fmt[1 .. $], '}') + 1;
|
||||
static if (pos < fmt.length)
|
||||
{
|
||||
alias ParseFmt = AliasSeq!(FormatSpec(),
|
||||
ParseFmt!(fmt[pos + 1 .. $], pos + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
static assert(false, "Enclosing '}' is missing");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
enum size_t pos = specPosition!(fmt, '{');
|
||||
alias ParseFmt = AliasSeq!(fmt[0 .. pos],
|
||||
ParseFmt!(fmt[pos .. $], pos));
|
||||
}
|
||||
}
|
||||
|
||||
@nogc nothrow pure @safe unittest
|
||||
{
|
||||
static assert(ParseFmt!"".length == 0);
|
||||
|
||||
static assert(ParseFmt!"asdf".length == 1);
|
||||
static assert(ParseFmt!"asdf"[0] == "asdf");
|
||||
|
||||
static assert(ParseFmt!"{}".length == 1);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user