1758 lines
53 KiB
D
1758 lines
53 KiB
D
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/**
|
|
* This package contains formatting and conversion functions.
|
|
*
|
|
* Copyright: Eugene Wissner 2017.
|
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
|
* Mozilla Public License, v. 2.0).
|
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
|
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/format/package.d,
|
|
* tanya/format/package.d)
|
|
*/
|
|
module tanya.format;
|
|
|
|
import core.stdc.stdarg;
|
|
public import tanya.format.conv;
|
|
import tanya.memory.op;
|
|
import tanya.range.array;
|
|
|
|
private enum special = 0x7000;
|
|
private enum char comma = ',';
|
|
private enum char period = '.';
|
|
|
|
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] digitpair =
|
|
"0001020304050607080910111213141516171819202122232425262728293031323334353"
|
|
~ "6373839404142434445464748495051525354555657585960616263646566676869707172"
|
|
~ "737475767778798081828384858687888990919293949596979899";
|
|
|
|
private static const double[23] bottom = [
|
|
1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008,
|
|
1e+009, 1e+010, 1e+011, 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017,
|
|
1e+018, 1e+019, 1e+020, 1e+021, 1e+022,
|
|
];
|
|
|
|
private static const double[22] negativeBottom = [
|
|
1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009,
|
|
1e-010, 1e-011, 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018,
|
|
1e-019, 1e-020, 1e-021, 1e-022,
|
|
];
|
|
|
|
private static const double[13] top = [
|
|
1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207,
|
|
1e+230, 1e+253, 1e+276, 1e+299,
|
|
];
|
|
|
|
private static const double[13] negativeTop = [
|
|
1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207,
|
|
1e-230, 1e-253, 1e-276, 1e-299,
|
|
];
|
|
|
|
private static const double[13] topError = [
|
|
8388608, 6.8601809640529717e+028, -7.253143638152921e+052,
|
|
-4.3377296974619174e+075, -1.5559416129466825e+098,
|
|
-3.2841562489204913e+121, -3.7745893248228135e+144,
|
|
-1.7356668416969134e+167, -3.8893577551088374e+190,
|
|
-9.9566444326005119e+213, 6.3641293062232429e+236,
|
|
-5.2069140800249813e+259, -5.2504760255204387e+282,
|
|
];
|
|
|
|
private static const double[22] negativeBottomError = [
|
|
-5.551115123125783e-018, -2.0816681711721684e-019,
|
|
-2.0816681711721686e-020, -4.7921736023859299e-021,
|
|
-8.1803053914031305e-022, 4.5251888174113741e-023,
|
|
4.5251888174113739e-024, -2.0922560830128471e-025,
|
|
-6.2281591457779853e-026, -3.6432197315497743e-027,
|
|
6.0503030718060191e-028, 2.0113352370744385e-029, -3.0373745563400371e-030,
|
|
1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033,
|
|
-7.1542424054621921e-034, -7.1542424054621926e-035,
|
|
2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038,
|
|
-4.8596774326570872e-039,
|
|
];
|
|
|
|
private static const double[13] negativeTopError = [
|
|
3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086,
|
|
1.1875228833981544e-109, -5.0644902316928607e-132,
|
|
-6.7156837247865426e-155, -2.812077463003139e-178,
|
|
-5.7778912386589953e-201, 7.4997100559334532e-224,
|
|
-4.6439668915134491e-247, -6.3691100762962136e-270,
|
|
-9.436808465446358e-293, // 8.0970921678014997e-317,
|
|
];
|
|
|
|
private enum ulong tenTo19th = 1000000000000000000UL;
|
|
|
|
private void ddmultlo(A, B, C, D, E, F)(ref A oh,
|
|
ref B ol,
|
|
ref C xh,
|
|
ref D xl,
|
|
ref E yh,
|
|
ref F yl)
|
|
{
|
|
ol = ol + (xh * yl + xl * yh);
|
|
}
|
|
|
|
private void ddmultlos(A, B, C, D)(ref A oh, ref B ol, ref C xh, ref D yl)
|
|
{
|
|
ol = ol + (xh * yl);
|
|
}
|
|
|
|
private void ddrenorm(T, U)(ref T oh, ref U ol)
|
|
{
|
|
double s;
|
|
s = oh + ol;
|
|
ol = ol - (s - oh);
|
|
oh = s;
|
|
}
|
|
|
|
// Power can be -323 to +350.
|
|
private void raise2Power10(double* ohi,
|
|
double* olo,
|
|
double d,
|
|
int power) pure nothrow @nogc
|
|
{
|
|
double ph, pl;
|
|
if ((power >= 0) && (power <= 22))
|
|
{
|
|
ddmulthi(ph, pl, d, bottom[power]);
|
|
}
|
|
else
|
|
{
|
|
int e, et, eb;
|
|
double p2h, p2l;
|
|
|
|
e = power;
|
|
if (power < 0)
|
|
{
|
|
e = -e;
|
|
}
|
|
et = (e * 0x2c9) >> 14; /* %23 */
|
|
if (et > 13)
|
|
{
|
|
et = 13;
|
|
}
|
|
eb = e - (et * 23);
|
|
|
|
ph = d;
|
|
pl = 0.0;
|
|
if (power < 0)
|
|
{
|
|
if (eb)
|
|
{
|
|
--eb;
|
|
ddmulthi(ph, pl, d, negativeBottom[eb]);
|
|
ddmultlos(ph, pl, d, negativeBottomError[eb]);
|
|
}
|
|
if (et)
|
|
{
|
|
ddrenorm(ph, pl);
|
|
--et;
|
|
ddmulthi(p2h, p2l, ph, negativeTop[et]);
|
|
ddmultlo(p2h,
|
|
p2l,
|
|
ph,
|
|
pl,
|
|
negativeTop[et],
|
|
negativeTopError[et]);
|
|
ph = p2h;
|
|
pl = p2l;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (eb)
|
|
{
|
|
e = eb;
|
|
if (eb > 22)
|
|
{
|
|
eb = 22;
|
|
}
|
|
e -= eb;
|
|
ddmulthi(ph, pl, d, bottom[eb]);
|
|
if (e)
|
|
{
|
|
ddrenorm(ph, pl);
|
|
ddmulthi(p2h, p2l, ph, bottom[e]);
|
|
ddmultlos(p2h, p2l, bottom[e], pl);
|
|
ph = p2h;
|
|
pl = p2l;
|
|
}
|
|
}
|
|
if (et)
|
|
{
|
|
ddrenorm(ph, pl);
|
|
--et;
|
|
ddmulthi(p2h, p2l, ph, top[et]);
|
|
ddmultlo(p2h, p2l, ph, pl, top[et], topError[et]);
|
|
ph = p2h;
|
|
pl = p2l;
|
|
}
|
|
}
|
|
}
|
|
ddrenorm(ph, pl);
|
|
*ohi = ph;
|
|
*olo = pl;
|
|
}
|
|
|
|
/*
|
|
* Given a float value, returns the significant bits in bits, and the position
|
|
* of the decimal point in decimalPos. +/-Inf and NaN are specified by special
|
|
* values returned in the decimalPos parameter.
|
|
* fracDigits is absolute normally, but if you want from first significant
|
|
* digits (got %g and %e), or in 0x80000000
|
|
*/
|
|
private int real2String(ref const(char)* start,
|
|
ref uint len,
|
|
char* out_,
|
|
out int decimalPos,
|
|
double value,
|
|
uint fracDigits) pure nothrow @nogc
|
|
{
|
|
long bits = 0;
|
|
int e, tens;
|
|
|
|
double d = value;
|
|
copyFp(bits, d);
|
|
auto exponent = cast(int) ((bits >> 52) & 2047);
|
|
auto ng = cast(int) (bits >> 63);
|
|
if (ng)
|
|
{
|
|
d = -d;
|
|
}
|
|
|
|
if (exponent == 2047) // Is NaN or inf?
|
|
{
|
|
start = (bits & (((cast(ulong) 1) << 52) - 1)) ? "NaN" : "Inf";
|
|
decimalPos = special;
|
|
len = 3;
|
|
return ng;
|
|
}
|
|
|
|
if (exponent == 0) // Is zero or denormal?
|
|
{
|
|
if ((bits << 1) == 0) // Do zero.
|
|
{
|
|
decimalPos = 1;
|
|
start = out_;
|
|
out_[0] = '0';
|
|
len = 1;
|
|
return ng;
|
|
}
|
|
// Find the right exponent for denormals.
|
|
{
|
|
long v = (cast(ulong) 1) << 51;
|
|
while ((bits & v) == 0)
|
|
{
|
|
--exponent;
|
|
v >>= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find the decimal exponent as well as the decimal bits of the value.
|
|
{
|
|
double ph, pl;
|
|
|
|
// log10 estimate - very specifically tweaked to hit or undershoot by
|
|
// no more than 1 of log10 of all exponents 1..2046.
|
|
tens = exponent - 1023;
|
|
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.
|
|
raise2Power10(&ph, &pl, d, 18 - tens);
|
|
|
|
// Get full as much precision from double-double as possible.
|
|
bits = cast(long) ph;
|
|
double vh = cast(double) bits;
|
|
double ahi = (ph - vh);
|
|
double t = (ahi - ph);
|
|
double alo = (ph - (ahi - t)) - (vh + t);
|
|
bits += cast(long) (ahi + alo + pl);
|
|
|
|
// Check if we undershot.
|
|
if ((cast(ulong) bits) >= tenTo19th)
|
|
{
|
|
++tens;
|
|
}
|
|
}
|
|
|
|
// Now do the rounding in integer land.
|
|
if (fracDigits & 0x80000000)
|
|
{
|
|
fracDigits = (fracDigits & 0x7ffffff) + 1;
|
|
}
|
|
else
|
|
{
|
|
fracDigits = tens + fracDigits;
|
|
}
|
|
if ((fracDigits < 24))
|
|
{
|
|
uint dg = 1;
|
|
if (cast(ulong) bits >= powersOf10[9])
|
|
{
|
|
dg = 10;
|
|
}
|
|
while (cast(ulong) bits >= powersOf10[dg])
|
|
{
|
|
++dg;
|
|
if (dg == 20)
|
|
{
|
|
goto noround;
|
|
}
|
|
}
|
|
if (fracDigits < dg)
|
|
{
|
|
// Add 0.5 at the right position and round.
|
|
e = dg - fracDigits;
|
|
if (cast(uint) e >= 24)
|
|
{
|
|
goto noround;
|
|
}
|
|
ulong r = powersOf10[e];
|
|
bits = bits + (r / 2);
|
|
if (cast(ulong) bits >= powersOf10[dg])
|
|
{
|
|
++tens;
|
|
}
|
|
bits /= r;
|
|
}
|
|
noround:
|
|
}
|
|
|
|
// Kill long trailing runs of zeros.
|
|
if (bits)
|
|
{
|
|
uint n;
|
|
for (;;)
|
|
{
|
|
if (bits <= 0xffffffff)
|
|
{
|
|
break;
|
|
}
|
|
if (bits % 1000)
|
|
{
|
|
goto donez;
|
|
}
|
|
bits /= 1000;
|
|
}
|
|
n = cast(uint) bits;
|
|
while ((n % 1000) == 0)
|
|
{
|
|
n /= 1000;
|
|
}
|
|
bits = n;
|
|
donez:
|
|
}
|
|
|
|
// Convert to string.
|
|
out_ += 64;
|
|
e = 0;
|
|
for (;;)
|
|
{
|
|
uint n;
|
|
char *o = out_ - 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)
|
|
{
|
|
out_ -= 2;
|
|
*cast(ushort*) out_ = *cast(ushort*) &digitpair[(n % 100) * 2];
|
|
n /= 100;
|
|
e += 2;
|
|
}
|
|
if (bits == 0)
|
|
{
|
|
if ((e) && (out_[0] == '0'))
|
|
{
|
|
++out_;
|
|
--e;
|
|
}
|
|
break;
|
|
}
|
|
while (out_ != o)
|
|
{
|
|
*--out_ = '0';
|
|
++e;
|
|
}
|
|
}
|
|
|
|
decimalPos = tens;
|
|
start = out_;
|
|
len = e;
|
|
return ng;
|
|
}
|
|
|
|
private void leadSign(uint fl, char* sign)
|
|
pure nothrow @nogc
|
|
{
|
|
sign[0] = 0;
|
|
if (fl & Modifier.negative)
|
|
{
|
|
sign[0] = 1;
|
|
sign[1] = '-';
|
|
}
|
|
else if (fl & Modifier.leadingSpace)
|
|
{
|
|
sign[0] = 1;
|
|
sign[1] = ' ';
|
|
}
|
|
else if (fl & Modifier.leadingPlus)
|
|
{
|
|
sign[0] = 1;
|
|
sign[1] = '+';
|
|
}
|
|
}
|
|
|
|
private enum Modifier : uint
|
|
{
|
|
leftJust = 1,
|
|
leadingPlus = 2,
|
|
leadingSpace = 4,
|
|
leading0x = 8,
|
|
leadingZero = 16,
|
|
intMax = 32,
|
|
tripletComma = 64,
|
|
negative = 128,
|
|
halfWidth = 512,
|
|
}
|
|
|
|
// Copies d to bits w/ strict aliasing (this compiles to nothing on /Ox).
|
|
private void copyFp(T, U)(ref T dest, ref U src)
|
|
{
|
|
for (int count = 0; count < 8; ++count)
|
|
{
|
|
(cast(char*) &dest)[count] = (cast(char*) &src)[count];
|
|
}
|
|
}
|
|
|
|
private void ddmulthi(ref double oh,
|
|
ref double ol,
|
|
ref double xh,
|
|
const ref double yh) pure nothrow @nogc
|
|
{
|
|
double ahi, bhi;
|
|
long bt;
|
|
oh = xh * yh;
|
|
copyFp(bt, xh);
|
|
bt &= ((~cast(ulong) 0) << 27);
|
|
copyFp(ahi, bt);
|
|
double alo = xh - ahi;
|
|
copyFp(bt, yh);
|
|
bt &= ((~cast(ulong) 0) << 27);
|
|
copyFp(bhi, bt);
|
|
double blo = yh - bhi;
|
|
ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo;
|
|
}
|
|
|
|
/*
|
|
* Get float info.
|
|
*
|
|
* Returns: Sign bit.
|
|
*/
|
|
private int real2Parts(long* bits, out int exponent, const double value)
|
|
pure nothrow @nogc
|
|
{
|
|
long b;
|
|
|
|
// Load value and round at the fracDigits.
|
|
double d = value;
|
|
|
|
copyFp(b, d);
|
|
|
|
*bits = b & (((cast(ulong) 1) << 52) - 1);
|
|
// 1023 is the exponent bias, calculated as 2^(k - 1) - 1, where k is the
|
|
// number of bits used to represent the exponent, 11 bit for double.
|
|
exponent = cast(int) (((b >> 52) & 0x7ff) - 1023);
|
|
|
|
return cast(int) (b >> 63);
|
|
}
|
|
|
|
private char[] vsprintf(string fmt)(return char[] buf, va_list va)
|
|
pure nothrow @nogc
|
|
{
|
|
static const string hex = "0123456789abcdefxp";
|
|
static const string hexu = "0123456789ABCDEFXP";
|
|
char* bf = buf.ptr;
|
|
string f = fmt;
|
|
int tlen = 0;
|
|
|
|
FmtLoop: while (true)
|
|
{
|
|
// Fast copy everything up to the next % (or end of string).
|
|
while ((cast(size_t) f.ptr) & 3)
|
|
{
|
|
schk:
|
|
if (f.length == 0)
|
|
{
|
|
break FmtLoop;
|
|
}
|
|
if (f[0] == '%')
|
|
{
|
|
goto scandd;
|
|
}
|
|
|
|
*bf++ = f[0];
|
|
f.popFront();
|
|
}
|
|
while (true)
|
|
{
|
|
// Check if the next 4 bytes contain %(0x25) or end of string.
|
|
// Using the 'hasless' trick:
|
|
// https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord
|
|
uint v = *cast(uint*) f;
|
|
uint c = (~v) & 0x80808080;
|
|
if ((((v ^ 0x25252525) - 0x01010101) & c) || f.length <= 3)
|
|
{
|
|
goto schk;
|
|
}
|
|
|
|
*cast(uint*) bf = v;
|
|
bf += 4;
|
|
f = f[4 .. $];
|
|
}
|
|
scandd:
|
|
|
|
f.popFront();
|
|
|
|
// Ok, we have a percent, read the modifiers first.
|
|
int fw = 0;
|
|
int precision = -1;
|
|
int tz = 0;
|
|
uint fl = 0;
|
|
|
|
// Flags.
|
|
for (;;)
|
|
{
|
|
switch (f[0])
|
|
{
|
|
// If we have left justify.
|
|
case '-':
|
|
fl |= Modifier.leftJust;
|
|
f.popFront();
|
|
continue;
|
|
// If we have leading plus.
|
|
case '+':
|
|
fl |= Modifier.leadingPlus;
|
|
f.popFront();
|
|
continue;
|
|
// If we have leading space.
|
|
case ' ':
|
|
fl |= Modifier.leadingSpace;
|
|
f.popFront();
|
|
continue;
|
|
// If we have leading 0x.
|
|
case '#':
|
|
fl |= Modifier.leading0x;
|
|
f.popFront();
|
|
continue;
|
|
// If we have thousand commas.
|
|
case '\'':
|
|
fl |= Modifier.tripletComma;
|
|
f.popFront();
|
|
continue;
|
|
// If we have leading zero.
|
|
case '0':
|
|
fl |= Modifier.leadingZero;
|
|
f.popFront();
|
|
goto flags_done;
|
|
default:
|
|
goto flags_done;
|
|
}
|
|
}
|
|
flags_done:
|
|
|
|
// Get the field width.
|
|
if (f[0] == '*')
|
|
{
|
|
fw = va_arg!uint(va);
|
|
f.popFront();
|
|
}
|
|
else
|
|
{
|
|
while ((f[0] >= '0') && (f[0] <= '9'))
|
|
{
|
|
fw = fw * 10 + f[0] - '0';
|
|
f.popFront();
|
|
}
|
|
}
|
|
// Get the precision.
|
|
if (f[0] == '.')
|
|
{
|
|
f.popFront();
|
|
if (f[0] == '*')
|
|
{
|
|
precision = va_arg!uint(va);
|
|
f.popFront();
|
|
}
|
|
else
|
|
{
|
|
precision = 0;
|
|
while ((f[0] >= '0') && (f[0] <= '9'))
|
|
{
|
|
precision = precision * 10 + f[0] - '0';
|
|
f.popFront();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle integer size overrides.
|
|
switch (f[0])
|
|
{
|
|
// are we halfwidth?
|
|
case 'h':
|
|
fl |= Modifier.halfWidth;
|
|
f.popFront();
|
|
break;
|
|
// are we 64-bit (unix style)
|
|
case 'l':
|
|
f.popFront();
|
|
if (f[0] == 'l')
|
|
{
|
|
fl |= Modifier.intMax;
|
|
f.popFront();
|
|
}
|
|
break;
|
|
// are we 64-bit on intmax? (c99)
|
|
case 'j':
|
|
fl |= Modifier.intMax;
|
|
f.popFront();
|
|
break;
|
|
// are we 64-bit on size_t or ptrdiff_t? (c99)
|
|
case 'z':
|
|
case 't':
|
|
static if (size_t.sizeof == 8)
|
|
{
|
|
fl |= Modifier.intMax;
|
|
}
|
|
f.popFront();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Handle each replacement.
|
|
enum NUMSZ = 512; // Big enough for e308 (with commas) or e-307.
|
|
char[NUMSZ] num;
|
|
char[8] lead;
|
|
char[8] tail;
|
|
char *s;
|
|
const(char)* h;
|
|
uint l, n, cs;
|
|
ulong n64;
|
|
|
|
double fv;
|
|
|
|
int decimalPos;
|
|
const(char)* sn;
|
|
|
|
switch (f[0])
|
|
{
|
|
case 's':
|
|
// Get the string.
|
|
s = va_arg!(char[])(va).ptr;
|
|
if (s is null)
|
|
{
|
|
s = cast(char*) "null";
|
|
}
|
|
// Get the length.
|
|
sn = s;
|
|
for (;;)
|
|
{
|
|
if (((cast(size_t) sn) & 3) == 0)
|
|
{
|
|
break;
|
|
}
|
|
lchk:
|
|
if (sn[0] == 0)
|
|
{
|
|
goto ld;
|
|
}
|
|
++sn;
|
|
}
|
|
n = 0xffffffff;
|
|
if (precision >= 0)
|
|
{
|
|
n = cast(uint) (sn - s);
|
|
if (n >= cast(uint) precision)
|
|
{
|
|
goto ld;
|
|
}
|
|
n = (cast(uint) (precision - n)) >> 2;
|
|
}
|
|
while (n)
|
|
{
|
|
uint v = *cast(uint*) sn;
|
|
if ((v - 0x01010101) & (~v) & 0x80808080UL)
|
|
{
|
|
goto lchk;
|
|
}
|
|
sn += 4;
|
|
--n;
|
|
}
|
|
goto lchk;
|
|
ld:
|
|
|
|
l = cast(uint) (sn - s);
|
|
// Clamp to precision.
|
|
if (l > cast(uint) precision)
|
|
{
|
|
l = precision;
|
|
}
|
|
lead[0] = 0;
|
|
tail[0] = 0;
|
|
precision = 0;
|
|
decimalPos = 0;
|
|
cs = 0;
|
|
// Copy the string in.
|
|
goto scopy;
|
|
|
|
case 'c': // Char.
|
|
// Get the character.
|
|
s = num.ptr + NUMSZ - 1;
|
|
*s = cast(char) va_arg!int(va);
|
|
l = 1;
|
|
lead[0] = 0;
|
|
tail[0] = 0;
|
|
precision = 0;
|
|
decimalPos = 0;
|
|
cs = 0;
|
|
goto scopy;
|
|
|
|
case 'n': // Weird write-bytes specifier.
|
|
{
|
|
int* d = va_arg!(int*)(va);
|
|
*d = tlen + cast(int) (bf - buf.ptr);
|
|
}
|
|
break;
|
|
|
|
case 'A': // Hex float.
|
|
case 'a': // Hex float.
|
|
h = (f[0] == 'A') ? hexu.ptr : hex.ptr;
|
|
fv = va_arg!double(va);
|
|
if (precision == -1)
|
|
{
|
|
precision = 6; // Default is 6.
|
|
}
|
|
// Read the double into a string.
|
|
if (real2Parts(cast(long*) &n64, decimalPos, fv))
|
|
{
|
|
fl |= Modifier.negative;
|
|
}
|
|
s = num.ptr + 64;
|
|
|
|
leadSign(fl, lead.ptr);
|
|
|
|
if (decimalPos == -1023)
|
|
{
|
|
decimalPos = n64 ? -1022 : 0;
|
|
}
|
|
else
|
|
{
|
|
n64 |= (cast(ulong) 1) << 52;
|
|
}
|
|
n64 <<= (64 - 56);
|
|
if (precision < 15)
|
|
{
|
|
n64 += (((cast(ulong) 8) << 56) >> (precision * 4));
|
|
}
|
|
// add leading chars
|
|
|
|
lead[1 + lead[0]] = '0';
|
|
lead[2 + lead[0]] = 'x';
|
|
lead[0] += 2;
|
|
|
|
*s++ = h[(n64 >> 60) & 15];
|
|
n64 <<= 4;
|
|
if (precision)
|
|
{
|
|
*s++ = period;
|
|
}
|
|
sn = s;
|
|
|
|
// Print the bits.
|
|
n = precision;
|
|
if (n > 13)
|
|
{
|
|
n = 13;
|
|
}
|
|
if (precision > cast(int) n)
|
|
{
|
|
tz = precision - n;
|
|
}
|
|
precision = 0;
|
|
while (n--)
|
|
{
|
|
*s++ = h[(n64 >> 60) & 15];
|
|
n64 <<= 4;
|
|
}
|
|
|
|
// Print the exponent.
|
|
tail[1] = h[17];
|
|
if (decimalPos < 0)
|
|
{
|
|
tail[2] = '-';
|
|
decimalPos = -decimalPos;
|
|
}
|
|
else
|
|
{
|
|
tail[2] = '+';
|
|
}
|
|
n = (decimalPos>= 1000) ? 6 : ((decimalPos >= 100) ? 5 : ((decimalPos >= 10) ? 4 : 3));
|
|
tail[0] = cast(char) n;
|
|
for (;;)
|
|
{
|
|
tail[n] = '0' + decimalPos % 10;
|
|
if (n <= 3)
|
|
{
|
|
break;
|
|
}
|
|
--n;
|
|
decimalPos /= 10;
|
|
}
|
|
|
|
decimalPos = cast(int) (s - sn);
|
|
l = cast(int) (s - (num.ptr + 64));
|
|
s = num.ptr + 64;
|
|
cs = 1 + (3 << 24);
|
|
goto scopy;
|
|
|
|
case 'G': // Float.
|
|
case 'g': // Float.
|
|
h = (f[0] == 'G') ? hexu.ptr : hex.ptr;
|
|
fv = va_arg!double(va);
|
|
if (precision == -1)
|
|
{
|
|
precision = 6;
|
|
}
|
|
else if (precision == 0)
|
|
{
|
|
precision = 1; // Default is 6.
|
|
}
|
|
// Read the double into a string.
|
|
if (real2String(sn,
|
|
l,
|
|
num.ptr,
|
|
decimalPos,
|
|
fv,
|
|
(precision - 1) | 0x80000000))
|
|
{
|
|
fl |= Modifier.negative;
|
|
}
|
|
|
|
// Clamp the precision and delete extra zeros after clamp.
|
|
n = precision;
|
|
if (l > cast(uint) precision)
|
|
{
|
|
l = precision;
|
|
}
|
|
while ((l > 1) && (precision) && (sn[l - 1] == '0'))
|
|
{
|
|
--precision;
|
|
--l;
|
|
}
|
|
|
|
// Should we use %e.
|
|
if ((decimalPos <= -4) || (decimalPos > cast(int) n))
|
|
{
|
|
if (precision > cast(int) l)
|
|
{
|
|
precision = l - 1;
|
|
}
|
|
else if (precision)
|
|
{
|
|
// When using %e, there is one digit before the decimal.
|
|
--precision;
|
|
}
|
|
goto doexpfromg;
|
|
}
|
|
// This is the insane action to get the pr to match %g sematics
|
|
// for %f
|
|
if (decimalPos > 0)
|
|
{
|
|
precision = (decimalPos < cast(int) l) ? l - decimalPos : 0;
|
|
}
|
|
else
|
|
{
|
|
if (precision > cast(int) l)
|
|
{
|
|
precision = -decimalPos + l;
|
|
}
|
|
else
|
|
{
|
|
precision = -decimalPos + precision;
|
|
}
|
|
}
|
|
goto dofloatfromg;
|
|
|
|
case 'E': // Float.
|
|
case 'e': // Float.
|
|
h = (f[0] == 'E') ? hexu.ptr : hex.ptr;
|
|
fv = va_arg!double(va);
|
|
if (precision == -1)
|
|
{
|
|
precision = 6; // Default is 6.
|
|
}
|
|
// read the double into a string
|
|
if (real2String(sn,
|
|
l,
|
|
num.ptr,
|
|
decimalPos,
|
|
fv,
|
|
precision | 0x80000000))
|
|
{
|
|
fl |= Modifier.negative;
|
|
}
|
|
doexpfromg:
|
|
tail[0] = 0;
|
|
leadSign(fl, lead.ptr);
|
|
if (decimalPos == special)
|
|
{
|
|
s = cast(char*) sn;
|
|
cs = 0;
|
|
precision = 0;
|
|
goto scopy;
|
|
}
|
|
s = num.ptr + 64;
|
|
// Handle leading chars.
|
|
*s++ = sn[0];
|
|
|
|
if (precision)
|
|
{
|
|
*s++ = period;
|
|
}
|
|
|
|
// Handle after decimal.
|
|
if ((l - 1) > cast(uint) precision)
|
|
{
|
|
l = precision + 1;
|
|
}
|
|
for (n = 1; n < l; n++)
|
|
{
|
|
*s++ = sn[n];
|
|
}
|
|
// Trailing zeros.
|
|
tz = precision - (l - 1);
|
|
precision = 0;
|
|
// Dump the exponent.
|
|
tail[1] = h[0xe];
|
|
decimalPos -= 1;
|
|
if (decimalPos < 0)
|
|
{
|
|
tail[2] = '-';
|
|
decimalPos = -decimalPos;
|
|
}
|
|
else
|
|
{
|
|
tail[2] = '+';
|
|
}
|
|
|
|
n = (decimalPos >= 100) ? 5 : 4;
|
|
|
|
tail[0] = cast(char) n;
|
|
for (;;)
|
|
{
|
|
tail[n] = '0' + decimalPos % 10;
|
|
if (n <= 3)
|
|
{
|
|
break;
|
|
}
|
|
--n;
|
|
decimalPos /= 10;
|
|
}
|
|
cs = 1 + (3 << 24); // How many tens.
|
|
goto flt_lead;
|
|
|
|
case 'f': // Float.
|
|
fv = va_arg!double(va);
|
|
doafloat:
|
|
if (precision == -1)
|
|
{
|
|
precision = 6; // Default is 6.
|
|
}
|
|
// Read the double into a string.
|
|
if (real2String(sn, l, num.ptr, decimalPos, fv, precision))
|
|
{
|
|
fl |= Modifier.negative;
|
|
}
|
|
dofloatfromg:
|
|
tail[0] = 0;
|
|
leadSign(fl, lead.ptr);
|
|
if (decimalPos == special)
|
|
{
|
|
s = cast(char*) sn;
|
|
cs = 0;
|
|
precision = 0;
|
|
goto scopy;
|
|
}
|
|
s = num.ptr + 64;
|
|
|
|
// Handle the three decimal varieties.
|
|
if (decimalPos <= 0)
|
|
{
|
|
// Handle 0.000*000xxxx.
|
|
*s++ = '0';
|
|
if (precision)
|
|
{
|
|
*s++ = period;
|
|
}
|
|
n = -decimalPos;
|
|
if (cast(int) n > precision)
|
|
{
|
|
n = precision;
|
|
}
|
|
int i = n;
|
|
while (i)
|
|
{
|
|
if (((cast(size_t) s) & 3) == 0)
|
|
{
|
|
break;
|
|
}
|
|
*s++ = '0';
|
|
--i;
|
|
}
|
|
while (i >= 4)
|
|
{
|
|
*cast(uint*) s = 0x30303030;
|
|
s += 4;
|
|
i -= 4;
|
|
}
|
|
while (i)
|
|
{
|
|
*s++ = '0';
|
|
--i;
|
|
}
|
|
if (cast(int) (l + n) > precision)
|
|
{
|
|
l = precision - n;
|
|
}
|
|
i = l;
|
|
while (i)
|
|
{
|
|
*s++ = *sn++;
|
|
--i;
|
|
}
|
|
tz = precision - (n + l);
|
|
// How many tens did we write (for commas below).
|
|
cs = 1 + (3 << 24);
|
|
}
|
|
else
|
|
{
|
|
cs = (fl & Modifier.tripletComma) ? ((600 - cast(uint) decimalPos) % 3) : 0;
|
|
if (cast(uint) decimalPos>= l)
|
|
{
|
|
// Handle xxxx000*000.0.
|
|
n = 0;
|
|
for (;;)
|
|
{
|
|
if ((fl & Modifier.tripletComma) && (++cs == 4))
|
|
{
|
|
cs = 0;
|
|
*s++ = comma;
|
|
}
|
|
else
|
|
{
|
|
*s++ = sn[n];
|
|
++n;
|
|
if (n >= l)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (n < cast(uint) decimalPos)
|
|
{
|
|
n = decimalPos - n;
|
|
if ((fl & Modifier.tripletComma) == 0)
|
|
{
|
|
while (n)
|
|
{
|
|
if (((cast(size_t) s) & 3) == 0)
|
|
{
|
|
break;
|
|
}
|
|
*s++ = '0';
|
|
--n;
|
|
}
|
|
while (n >= 4)
|
|
{
|
|
*cast(uint*) s = 0x30303030;
|
|
s += 4;
|
|
n -= 4;
|
|
}
|
|
}
|
|
while (n)
|
|
{
|
|
if ((fl & Modifier.tripletComma) && (++cs == 4))
|
|
{
|
|
cs = 0;
|
|
*s++ = comma;
|
|
}
|
|
else
|
|
{
|
|
*s++ = '0';
|
|
--n;
|
|
}
|
|
}
|
|
}
|
|
// cs is how many tens.
|
|
cs = cast(int) (s - (num.ptr + 64)) + (3 << 24);
|
|
if (precision)
|
|
{
|
|
*s++ = period;
|
|
tz = precision;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Handle xxxxx.xxxx000*000.
|
|
n = 0;
|
|
for (;;)
|
|
{
|
|
if ((fl & Modifier.tripletComma) && (++cs == 4))
|
|
{
|
|
cs = 0;
|
|
*s++ = comma;
|
|
}
|
|
else
|
|
{
|
|
*s++ = sn[n];
|
|
++n;
|
|
if (n >= cast(uint) decimalPos)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// cs is how many tens.
|
|
cs = cast(int) (s - (num.ptr + 64)) + (3 << 24);
|
|
if (precision)
|
|
{
|
|
*s++ = period;
|
|
}
|
|
if ((l - decimalPos) > cast(uint) precision)
|
|
{
|
|
l = precision + decimalPos;
|
|
}
|
|
while (n < l)
|
|
{
|
|
*s++ = sn[n];
|
|
++n;
|
|
}
|
|
tz = precision - (l - decimalPos);
|
|
}
|
|
}
|
|
precision = 0;
|
|
|
|
flt_lead:
|
|
// Get the length that we copied.
|
|
l = cast(uint) (s - (num.ptr + 64));
|
|
s = num.ptr + 64;
|
|
goto scopy;
|
|
|
|
case 'B': // Upper binary.
|
|
case 'b': // Lower binary.
|
|
h = (f[0] == 'B') ? hexu.ptr : hex.ptr;
|
|
lead[0] = 0;
|
|
if (fl & Modifier.leading0x)
|
|
{
|
|
lead[0] = 2;
|
|
lead[1] = '0';
|
|
lead[2] = h[0xb];
|
|
}
|
|
l = (8 << 4) | (1 << 8);
|
|
{
|
|
goto radixnum;
|
|
}
|
|
|
|
case 'o': // octal
|
|
h = hexu.ptr;
|
|
lead[0] = 0;
|
|
if (fl & Modifier.leading0x)
|
|
{
|
|
lead[0] = 1;
|
|
lead[1] = '0';
|
|
}
|
|
l = (3 << 4) | (3 << 8);
|
|
goto radixnum;
|
|
|
|
case 'p': // pointer
|
|
static if (size_t.sizeof == 8)
|
|
{
|
|
fl |= Modifier.intMax;
|
|
}
|
|
precision = (void*).sizeof * 2;
|
|
// 'p' only prints the pointer with zeros.
|
|
fl &= ~Modifier.leadingZero;
|
|
goto case;
|
|
case 'X': // Upper hex.
|
|
case 'x': // Lower hex.
|
|
h = (f[0] == 'X') ? hexu.ptr : hex.ptr;
|
|
l = (4 << 4) | (4 << 8);
|
|
lead[0] = 0;
|
|
if (fl & Modifier.leading0x)
|
|
{
|
|
lead[0] = 2;
|
|
lead[1] = '0';
|
|
lead[2] = h[16];
|
|
}
|
|
radixnum:
|
|
// Get the number.
|
|
if (fl & Modifier.intMax)
|
|
{
|
|
n64 = va_arg!ulong(va);
|
|
}
|
|
else
|
|
{
|
|
n64 = va_arg!uint(va);
|
|
}
|
|
|
|
s = num.ptr + NUMSZ;
|
|
decimalPos = 0;
|
|
// Clear tail, and clear leading if value is zero.
|
|
tail[0] = 0;
|
|
if (n64 == 0)
|
|
{
|
|
lead[0] = 0;
|
|
if (precision == 0)
|
|
{
|
|
l = 0;
|
|
cs = (((l >> 4) & 15)) << 24;
|
|
goto scopy;
|
|
}
|
|
}
|
|
// Convert to string.
|
|
for (;;)
|
|
{
|
|
*--s = h[cast(size_t) (n64 & ((1 << (l >> 8)) - 1))];
|
|
n64 >>= (l >> 8);
|
|
if (!((n64) || (cast(int) ((num.ptr + NUMSZ) - s) < precision)))
|
|
{
|
|
break;
|
|
}
|
|
if (fl & Modifier.tripletComma)
|
|
{
|
|
++l;
|
|
if ((l & 15) == ((l >> 4) & 15))
|
|
{
|
|
l &= ~15;
|
|
*--s = comma;
|
|
}
|
|
}
|
|
}
|
|
// Get the tens and the comma position.
|
|
cs = cast(uint) ((num.ptr + NUMSZ) - s) + ((((l >> 4) & 15)) << 24);
|
|
// Get the length that we copied.
|
|
l = cast(uint)((num.ptr + NUMSZ) - s);
|
|
// Copy it.
|
|
goto scopy;
|
|
|
|
case 'u': // Unsigned.
|
|
case 'i':
|
|
case 'd': // Integer.
|
|
// Get the integer and abs it.
|
|
if (fl & Modifier.intMax)
|
|
{
|
|
long i64 = va_arg!long(va);
|
|
n64 = cast(ulong) i64;
|
|
if ((f[0] != 'u') && (i64 < 0))
|
|
{
|
|
n64 = cast(ulong) -i64;
|
|
fl |= Modifier.negative;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int i = va_arg!int(va);
|
|
n64 = cast(uint) i;
|
|
if ((f[0] != 'u') && (i < 0))
|
|
{
|
|
n64 = cast(uint) -i;
|
|
fl |= Modifier.negative;
|
|
}
|
|
}
|
|
|
|
// Convert to string.
|
|
s = num.ptr + NUMSZ;
|
|
l = 0;
|
|
|
|
for (;;)
|
|
{
|
|
// Do in 32-bit chunks (avoid lots of 64-bit divides even
|
|
// with constant denominators).
|
|
char *o = s - 8;
|
|
if (n64 >= 100000000)
|
|
{
|
|
n = cast(uint) (n64 % 100000000);
|
|
n64 /= 100000000;
|
|
}
|
|
else
|
|
{
|
|
n = cast(uint) n64;
|
|
n64 = 0;
|
|
}
|
|
if ((fl & Modifier.tripletComma) == 0)
|
|
{
|
|
while (n)
|
|
{
|
|
s -= 2;
|
|
*cast(ushort*) s = *cast(ushort*) &digitpair[(n % 100) * 2];
|
|
n /= 100;
|
|
}
|
|
}
|
|
while (n)
|
|
{
|
|
if ((fl & Modifier.tripletComma) && (l++ == 3))
|
|
{
|
|
l = 0;
|
|
*--s = comma;
|
|
--o;
|
|
}
|
|
else
|
|
{
|
|
*--s = cast(char) (n % 10) + '0';
|
|
n /= 10;
|
|
}
|
|
}
|
|
if (n64 == 0)
|
|
{
|
|
if ((s[0] == '0') && (s != (num.ptr + NUMSZ)))
|
|
{
|
|
++s;
|
|
}
|
|
break;
|
|
}
|
|
while (s != o)
|
|
{
|
|
if ((fl & Modifier.tripletComma) && (l++ == 3))
|
|
{
|
|
l = 0;
|
|
*--s = comma;
|
|
--o;
|
|
}
|
|
else
|
|
{
|
|
*--s = '0';
|
|
}
|
|
}
|
|
}
|
|
|
|
tail[0] = 0;
|
|
leadSign(fl, lead.ptr);
|
|
|
|
// Get the length that we copied.
|
|
l = cast(uint) ((num.ptr + NUMSZ) - s);
|
|
if (l == 0)
|
|
{
|
|
*--s = '0';
|
|
l = 1;
|
|
}
|
|
cs = l + (3 << 24);
|
|
if (precision < 0)
|
|
{
|
|
precision = 0;
|
|
}
|
|
|
|
scopy:
|
|
// Get fw=leading/trailing space, precision=leading zeros.
|
|
if (precision < cast(int) l)
|
|
{
|
|
precision = l;
|
|
}
|
|
n = precision + lead[0] + tail[0] + tz;
|
|
if (fw < cast(int) n)
|
|
{
|
|
fw = n;
|
|
}
|
|
fw -= n;
|
|
precision -= l;
|
|
|
|
// Handle right justify and leading zeros.
|
|
if ((fl & Modifier.leftJust) == 0)
|
|
{
|
|
// If leading zeros, everything is in precision.
|
|
if (fl & Modifier.leadingZero)
|
|
{
|
|
precision = (fw > precision) ? fw : precision;
|
|
fw = 0;
|
|
}
|
|
else
|
|
{
|
|
fl &= ~Modifier.tripletComma; // if no leading zeros, then no commas
|
|
}
|
|
}
|
|
|
|
// Copy the spaces and/or zeros.
|
|
if (fw + precision)
|
|
{
|
|
int i;
|
|
|
|
// copy leading spaces (or when doing %8.4d stuff)
|
|
if ((fl & Modifier.leftJust) == 0)
|
|
{
|
|
while (fw > 0)
|
|
{
|
|
i = fw;
|
|
fw -= i;
|
|
while (i)
|
|
{
|
|
if (((cast(size_t) bf) & 3) == 0)
|
|
{
|
|
break;
|
|
}
|
|
*bf++ = ' ';
|
|
--i;
|
|
}
|
|
while (i >= 4)
|
|
{
|
|
*cast(uint*) bf = 0x20202020;
|
|
bf += 4;
|
|
i -= 4;
|
|
}
|
|
while (i)
|
|
{
|
|
*bf++ = ' ';
|
|
--i;
|
|
}
|
|
}
|
|
}
|
|
|
|
// copy leader
|
|
sn = lead.ptr + 1;
|
|
while (lead[0])
|
|
{
|
|
i = lead[0];
|
|
lead[0] -= cast(char) i;
|
|
while (i)
|
|
{
|
|
*bf++ = *sn++;
|
|
--i;
|
|
}
|
|
}
|
|
|
|
// Copy leading zeros.
|
|
uint c = cs >> 24;
|
|
cs &= 0xffffff;
|
|
if (fl & Modifier.tripletComma)
|
|
{
|
|
cs = cast(uint) (c - ((precision + cs) % (c + 1)));
|
|
}
|
|
else
|
|
{
|
|
cs = 0;
|
|
}
|
|
while (precision > 0)
|
|
{
|
|
i = precision;
|
|
precision -= i;
|
|
if ((fl & Modifier.tripletComma) == 0)
|
|
{
|
|
while (i)
|
|
{
|
|
if (((cast(size_t) bf) & 3) == 0)
|
|
{
|
|
break;
|
|
}
|
|
*bf++ = '0';
|
|
--i;
|
|
}
|
|
while (i >= 4)
|
|
{
|
|
*cast(uint*) bf = 0x30303030;
|
|
bf += 4;
|
|
i -= 4;
|
|
}
|
|
}
|
|
while (i)
|
|
{
|
|
if ((fl & Modifier.tripletComma) && (cs++ == c))
|
|
{
|
|
cs = 0;
|
|
*bf++ = comma;
|
|
}
|
|
else
|
|
{
|
|
*bf++ = '0';
|
|
}
|
|
--i;
|
|
}
|
|
}
|
|
}
|
|
|
|
// copy leader if there is still one
|
|
sn = lead.ptr + 1;
|
|
while (lead[0])
|
|
{
|
|
int i = lead[0];
|
|
lead[0] -= cast(char) i;
|
|
while (i)
|
|
{
|
|
*bf++ = *sn++;
|
|
--i;
|
|
}
|
|
}
|
|
|
|
// Copy the string.
|
|
n = l;
|
|
while (n)
|
|
{
|
|
int i = n;
|
|
n -= i;
|
|
|
|
while (i)
|
|
{
|
|
*bf++ = *s++;
|
|
--i;
|
|
}
|
|
}
|
|
|
|
// Copy trailing zeros.
|
|
while (tz)
|
|
{
|
|
int i = tz;
|
|
tz -= i;
|
|
while (i)
|
|
{
|
|
if (((cast(size_t) bf) & 3) == 0)
|
|
{
|
|
break;
|
|
}
|
|
*bf++ = '0';
|
|
--i;
|
|
}
|
|
while (i >= 4)
|
|
{
|
|
*cast(uint*) bf = 0x30303030;
|
|
bf += 4;
|
|
i -= 4;
|
|
}
|
|
while (i)
|
|
{
|
|
*bf++ = '0';
|
|
--i;
|
|
}
|
|
}
|
|
|
|
// copy tail if there is one
|
|
sn = tail.ptr + 1;
|
|
while (tail[0])
|
|
{
|
|
int i = tail[0];
|
|
tail[0] -= cast(char) i;
|
|
while (i)
|
|
{
|
|
*bf++ = *sn++;
|
|
--i;
|
|
}
|
|
}
|
|
|
|
// handle the left justify
|
|
if (fl & Modifier.leftJust && fw > 0)
|
|
{
|
|
while (fw)
|
|
{
|
|
int i = fw;
|
|
fw -= i;
|
|
while (i)
|
|
{
|
|
if (((cast(size_t) bf) & 3) == 0)
|
|
{
|
|
break;
|
|
}
|
|
*bf++ = ' ';
|
|
--i;
|
|
}
|
|
while (i >= 4)
|
|
{
|
|
*cast(uint*) bf = 0x20202020;
|
|
bf += 4;
|
|
i -= 4;
|
|
}
|
|
while (i--)
|
|
{
|
|
*bf++ = ' ';
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default: // Unknown, just copy code.
|
|
s = num.ptr + NUMSZ - 1;
|
|
*s = f[0];
|
|
l = 1;
|
|
fw = precision = fl = 0;
|
|
lead[0] = 0;
|
|
tail[0] = 0;
|
|
precision = 0;
|
|
decimalPos = 0;
|
|
cs = 0;
|
|
goto scopy;
|
|
}
|
|
f.popFront();
|
|
}
|
|
|
|
*bf = 0;
|
|
return buf[0 .. tlen + cast(int) (bf - buf.ptr)];
|
|
}
|
|
|
|
package(tanya) char[] format(string fmt)(return char[] buf, ...)
|
|
nothrow
|
|
{
|
|
va_list va;
|
|
va_start(va, buf);
|
|
auto result = vsprintf!fmt(buf, va);
|
|
va_end(va);
|
|
return result;
|
|
}
|
|
|
|
private nothrow unittest
|
|
{
|
|
char[318] buffer;
|
|
|
|
// Format without arguments.
|
|
assert(format!""(buffer) == "");
|
|
assert(format!"asdfqweryxcvz"(buffer) == "asdfqweryxcvz");
|
|
|
|
// Modifiers.
|
|
assert(format!"%-5g"(buffer, 8.5) == "8.5 ");
|
|
assert(format!"%05g"(buffer, 8.6) == "008.6");
|
|
assert(format!"% 5g"(buffer, 8.6) == " 8.6");
|
|
assert(format!"%+d"(buffer, 8) == "+8");
|
|
assert(format!"%#x"(buffer, 20) == "0x14");
|
|
assert(format!"%#o"(buffer, 8) == "010");
|
|
assert(format!"%#b"(buffer, 3) == "0b11");
|
|
assert(format!"%'d"(buffer, 1000) == "1,000");
|
|
assert(format!"%*d"(buffer, 5, 1) == " 1");
|
|
assert(format!"%.1f"(buffer, 10.25) == "10.3");
|
|
assert(format!"%.*f"(buffer, 1, 10.25) == "10.3");
|
|
assert(format!"%-9i"(buffer, 1) == "1 ");
|
|
assert(format!"%'07.3g"(buffer, 0.01) == ",000.01");
|
|
|
|
// Integer size.
|
|
assert(format!"%hd"(buffer, 10) == "10");
|
|
assert(format!"%ld"(buffer, 10) == "10");
|
|
assert(format!"%lld"(buffer, 10L) == "10");
|
|
assert(format!"%jd"(buffer, 10L) == "10");
|
|
assert(format!"%zd"(buffer, 10) == "10");
|
|
assert(format!"%td"(buffer, 10) == "10");
|
|
|
|
// String printing.
|
|
assert(format!"%s"(buffer, "Some weired string") == "Some weired string");
|
|
assert(format!"%s"(buffer, cast(string) null) == "null");
|
|
assert(format!"%.4s"(buffer, "Some weired string") == "Some");
|
|
assert(format!"%c"(buffer, 'c') == "c");
|
|
|
|
// Integer conversions.
|
|
assert(format!"%d"(buffer, 8) == "8");
|
|
assert(format!"%i"(buffer, 8) == "8");
|
|
assert(format!"%i"(buffer, -8) == "-8");
|
|
assert(format!"%lli"(buffer, -8L) == "-8");
|
|
assert(format!"%u"(buffer, 8) == "8");
|
|
assert(format!"%o"(buffer, 8) == "10");
|
|
assert(format!"%b"(buffer, 3) == "11");
|
|
assert(format!"%B"(buffer, 3) == "11");
|
|
assert(format!"%.0x"(buffer, 0) == "");
|
|
assert(format!"%'x"(buffer, 11111111) == "a9,8ac7");
|
|
assert(format!"%d"(buffer, 100000001) == "100000001");
|
|
assert(format!"%.12d"(buffer, 99999999L) == "000099999999");
|
|
assert(format!"%'d"(buffer, 100000001) == "100,000,001");
|
|
|
|
// Count of bytes written so far.
|
|
{
|
|
int written;
|
|
format!"asd%nf"(buffer, &written);
|
|
assert(written == 3);
|
|
}
|
|
|
|
// Floating point conversions.
|
|
assert(format!"%g"(buffer, 0.1234) == "0.1234");
|
|
assert(format!"%G"(buffer, 0.3) == "0.3");
|
|
assert(format!"%g"(buffer, 0.333333333333) == "0.333333");
|
|
assert(format!"%g"(buffer, 38234.1234) == "38234.1");
|
|
assert(format!"%g"(buffer, -0.3) == "-0.3");
|
|
assert(format!"%f"(buffer, -0.3) == "-0.300000");
|
|
assert(format!"%g"(buffer, 0.000000000000000006) == "6e-18");
|
|
assert(format!"%g"(buffer, 0.0) == "0");
|
|
assert(format!"%f"(buffer, 0.0) == "0.000000");
|
|
assert(format!"%e"(buffer, 0.3) == "3.000000e-01");
|
|
assert(format!"%E"(buffer, 0.3) == "3.000000E-01");
|
|
assert(format!"%e"(buffer, -0.3) == "-3.000000e-01");
|
|
assert(format!"%a"(buffer, 3.5) == "0x1.c00000p+1");
|
|
assert(format!"%A"(buffer, 3.5) == "0x1.C00000P+1");
|
|
assert(format!"%a"(buffer, -3.5) == "-0x1.c00000p+1");
|
|
assert(format!"%.14a"(buffer, -3.5) == "-0x1.c0000000000000p+1");
|
|
assert(format!"%a"(buffer, 0.0) == "0x0.000000p+0");
|
|
assert(format!"%a"(buffer, 1e-300) == "0x1.56e1fcp-997");
|
|
assert(format!"%f"(buffer, double.init) == "NaN");
|
|
assert(format!"%f"(buffer, double.infinity) == "Inf");
|
|
assert(format!"%.0g"(buffer, 0.0) == "0");
|
|
assert(format!"%f"(buffer, 0.000000000000000000000000003) == "0.000000");
|
|
assert(format!"%g"(buffer, 0.23432e304) == "2.3432e+303");
|
|
assert(format!"%f"(buffer, -0.23432e8) == "-23432000.000000");
|
|
assert(format!"%e"(buffer, double.init) == "NaN");
|
|
assert(format!"%f"(buffer, 1e-307) == "0.000000");
|
|
assert(format!"%f"(buffer, 1e+8) == "100000000.000000");
|
|
assert(format!"%'05g"(buffer, 111234.1) == "111,234");
|
|
assert(format!"%.2g"(buffer, double.init) == "Na");
|
|
assert(format!"%.1e"(buffer, 0.999) == "1.0e+00");
|
|
assert(format!"%.0f"(buffer, 0.999) == "1");
|
|
assert(format!"%.9f"(buffer, 1e-307) == "0.000000000");
|
|
assert(format!"%g"(buffer, 0x1p-16382L)); // "6.95336e-310"
|
|
assert(format!"%'f"(buffer, 1e+3) == "1,000.000000");
|
|
assert(format!"%'g"(buffer, 38234.1234) == "38,234.1");
|
|
|
|
// Pointer conversions.
|
|
{
|
|
auto p = format!"%p"(buffer, cast(void*) 1);
|
|
assert(p[$ - 1] == '1');
|
|
foreach (e; p[0 .. $ - 1])
|
|
{
|
|
assert(e == '0');
|
|
}
|
|
}
|
|
|
|
// Unknown specifier.
|
|
assert(format!"%k"(buffer) == "k");
|
|
assert(format!"%%k"(buffer) == "%k");
|
|
}
|