diff --git a/druntime/mak/COPY b/druntime/mak/COPY index 30a69609ee36..344c61b0ff18 100644 --- a/druntime/mak/COPY +++ b/druntime/mak/COPY @@ -38,6 +38,8 @@ COPY=\ $(IMPDIR)\core\internal\destruction.d \ $(IMPDIR)\core\internal\entrypoint.d \ $(IMPDIR)\core\internal\execinfo.d \ + $(IMPDIR)\core\internal\ftot\decimal.d \ + $(IMPDIR)\core\internal\ftot\package.d \ $(IMPDIR)\core\internal\hash.d \ $(IMPDIR)\core\internal\moving.d \ $(IMPDIR)\core\internal\newaa.d \ diff --git a/druntime/mak/SRCS b/druntime/mak/SRCS index 1f2afa2515be..9597b28fff86 100644 --- a/druntime/mak/SRCS +++ b/druntime/mak/SRCS @@ -35,6 +35,8 @@ SRCS=\ src\core\internal\destruction.d \ src\core\internal\entrypoint.d \ src\core\internal\execinfo.d \ + src\core\internal\ftot\decimal.d \ + src\core\internal\ftot\package.d \ src\core\internal\hash.d \ src\core\internal\moving.d \ src\core\internal\newaa.d \ diff --git a/druntime/src/core/internal/ftot/decimal.d b/druntime/src/core/internal/ftot/decimal.d new file mode 100644 index 000000000000..28d869d87fce --- /dev/null +++ b/druntime/src/core/internal/ftot/decimal.d @@ -0,0 +1,362 @@ +/// +module core.internal.ftot.decimal; + +pure: +nothrow: +@nogc: +@safe: + +package: + +mixin template Estimation(BigitT) +if(is(BigitT == ushort) || is(BigitT == uint)) +{ + // Set decimal exponent. The most effective is to use + // the largest power of 10 that is less than T.max. + static if(BigitT.sizeof == 4) + { + enum decimalExp = 9; // 10^9 - largest decimal number less than 32-bit value + enum bigitBitWidth = 29; // 2^29 - largest binary number less than 10^9 + alias UL = ulong; // Twice longer than BigitT + } + else static if(BigitT.sizeof == 2) + { + enum decimalExp = 4; // 10^4 - largest decimal number less than 16-bit value + enum bigitBitWidth = 13; // 2^13 - largest binary number less than 10^4 + alias UL = uint; + } +} + +mixin template BigitsArraySizeCalculation(BigitT, F) +if(__traits(isFloating, F)) +{ + enum isNotReal = !is(F == real) || + ( + real.mant_dig == double.mant_dig + && real.min_10_exp == double.min_10_exp + && real.max_10_exp == double.max_10_exp + ); + + mixin Estimation!BigitT; + + enum intPartBigitsNum = F.mant_dig / bigitBitWidth + (F.mant_dig % bigitBitWidth == 0 ? 0 : 1); + + static if(is(F == float) || (UL.sizeof == 8 && isNotReal)) + { + /// A type that is guaranteed to fit a integral part of floating T (except "real") + alias GF = UL; + } + else + { + static if(isNotReal) + static assert(intPartBigitsNum == 5); + + alias GF = ulong; + } + + private + { + enum maxLeftShifts = (F.max_10_exp + decimalExp-1) / decimalExp; + enum maxRightShifts = (-F.min_10_exp + decimalExp-1) / decimalExp; + } + + enum initialDigits = 1 /* digit before dot */ + F.max_10_exp + (-F.min_10_exp) + F.dig; + enum maxShifts = maxLeftShifts < maxRightShifts ? maxRightShifts : maxLeftShifts; + + enum bigitsArrLength = (initialDigits + decimalExp-1) / decimalExp + maxShifts; +} + +/** + * A fixed-point decimal number. + * + * Implements a fixed-point decimal number using a base-10^9 positional system. + * It represents large numbers by breaking them into "bigits" (blocks), + * where each block is a 9-digit decimal integer stored in a integer type. + * + * Params: + * T = unsigned integer of size 16 or 32 + * maxLen = bigits array size + */ +struct Decimal(T, ushort maxLen) +if(is(T == ushort) || is(T == uint)) +{ + private T[maxLen] bigitsArr = void; + T[] bigits; + + mixin Estimation!T; + + static assert(decimalExp <= bigitBitWidth); + + private enum maxLeftShift = bigitBitWidth; + enum T bigitBound = 10 ^^ decimalExp; + + ushort numBigits; + short fractionStart; + + void shiftFewBitsLeft(in int n) + in(n > 0) + in(n <= maxLeftShift) + { + enum byte bigitIdx = 0; + + // Will number of blocks increase after the shifting? + // If so, reserves space for a new block + const ubyte offset = bigits[bigitIdx] >= (bigitBound >> n) ? 1 : 0; + T carry; + + foreach_reverse(i; bigitIdx .. numBigits) + { + UL bigit = bigits[i]; + bigit = (bigit << n) + carry; + + if(bigit < bigitBound) + carry = 0; + else + { + carry = assumeSafeCastT(bigit / bigitBound); + bigit = bigit % bigitBound; + } + + bigits[i + offset] = assumeSafeCastT(bigit); + } + + if(offset != 0) + { + bigits[bigitIdx] = carry; + numBigits++; + } + } + + void shiftFewBitsRight(in int n) + in(n > 0) + in(n <= decimalExp) + { + const T mask = assumeSafeCastT((1 << n) - 1); + T borrow; + int offset; + + // Number was here and moved completely to the right? + if((bigits[0] >> n) == 0 && bigits[0] != 0) + { + offset = 1; + numBigits--; + fractionStart--; + + borrow = assumeSafeCastT(UL(bigits[0]) * bigitBound >> n); + } + + foreach(i; 0 .. numBigits) + { + const UL bigit = bigits[i + offset]; + + bigits[i] = assumeSafeCastT(borrow + (bigit >> n)); + borrow = assumeSafeCastT((bigit & mask) * bigitBound >> n); + } + + if(borrow != 0) + { + bigits[numBigits] = borrow; + numBigits++; + } + } + + void massiveLeftShift(int n) + in(n > 0) + { + enum bitsPerIteration = maxLeftShift; + + do + { + const bits = n < bitsPerIteration ? n : bitsPerIteration; + shiftFewBitsLeft(bits); + n -= bits; + } + while(n > 0); + } + + void massiveRightShift(int n) + in(n > 0) + { + enum bitsPerIteration = decimalExp; + + do + { + const bits = n < bitsPerIteration ? n : bitsPerIteration; + shiftFewBitsRight(bits); + n -= bits; + } + while(n > 0); + } + + this(F)(F d) + if(__traits(isFloating, F)) + { + import core.stdc.math; + + mixin BigitsArraySizeCalculation!(T, F); + static assert(maxLen <= bigitsArrLength); + + enum numBits = d.mant_dig; + int exp; // in fact, exponent value always fits into a short type + + static if(is(F == float)) + const integralPart = frexpf(d, &exp) * (1U << numBits); + else static if(is(F == double) || isNotReal) + const integralPart = frexp(cast(double) d, &exp) * (1UL << numBits); + else static if(is(F == real)) + auto mant = frexpl(d, &exp).fabsl; + + exp -= numBits; + + byte intPartIdx = intPartBigitsNum; + + void addIntPartAsInteger(GF v) + { + while(true) + { + assert(intPartIdx > 0); + intPartIdx--; + + const lessSig = v % bigitBound; + v /= bigitBound; + + bigitsArr[intPartIdx] = lessSig; + numBigits++; + + if(v == 0) + break; + } + } + + static if(isNotReal) + { + auto v = cast(GF) (integralPart < 0 ? -integralPart : integralPart); + addIntPartAsInteger(v); + } + else + {{ + // Fetch unsigned values from a big mantiss + + version(assert) + { + //TODO: implement our own scalbnl() using core.internal.convert to achieve pure ctor + import core.stdc.errno; + import core.stdc.fenv; + + errno = 0; + () @trusted { feclearexcept(FE_ALL_EXCEPT); }(); + } + + short shift = F.mant_dig; + + while(true) + { + if(shift == 0) + { + const word = cast(GF) mant; + addIntPartAsInteger(word); + break; + } + + const scaled = scalbnl(mant, shift); + () @trusted { + assert(fetestexcept(FE_OVERFLOW|FE_UNDERFLOW) == 0); + }(); + + const word = cast(GF) scaled; + addIntPartAsInteger(word); + + // Remove fetched bits + mant -= scalbnl(cast(real) word, -shift); + () @trusted { + assert(fetestexcept(FE_OVERFLOW|FE_UNDERFLOW) == 0); + }(); + + if(mant < mant.epsilon) + break; + + shift -= bigitBitWidth; + + // In case incomplete last word when .mant_dig isn't multiple of bigitBitWidth + static if(F.mant_dig % bigitBitWidth != 0) + if(shift < 0) + shift = 0; + } + }} + + assert(intPartIdx >= 0); + + // Skip leading zero bigits + () @trusted { + bigits = bigitsArr[intPartIdx .. $]; + }(); + + if(exp >= 0) + { + massiveLeftShift(exp); + fractionStart = numBigits; + } + else + { + const integralBigitsAcquired = intPartBigitsNum - intPartIdx; + fractionStart = integralBigitsAcquired; + + massiveRightShift(-exp); + } + + // Assigning again for better boundary control + version(D_NoBoundsChecks){} else + bigits = bigits[0 .. numBigits + 1]; + } + + private static T assumeSafeCastT(V)(V val, size_t line = __LINE__) + if(__traits(isIntegral, V)) + { + assert(val >= 0); + assert(val <= T.max); + + return cast(T) val; + } +} + + +unittest +{ + double val = 9999999.0; + auto d = Decimal!(uint, 20)(val); + d.bigitsArr = 0; // resets bigits storage + + d.bigits[0] = 1; + + d.shiftFewBitsLeft(1); + assert(d.bigits[0] == 2); + + d.shiftFewBitsLeft(d.maxLeftShift); + assert(d.bigits[0] == 1); +} + +unittest +{ + double val = 123.456; + auto d = Decimal!(uint, 16)(val); + d.numBigits = 2; + + const initial = d.bigits; + + // Shift right to 32 bits: + d.shiftFewBitsRight(1); + d.shiftFewBitsRight(2); + d.shiftFewBitsRight(5); + d.shiftFewBitsRight(8); + d.shiftFewBitsRight(8); + d.shiftFewBitsRight(8); + + // Shift left to the initial state: + d.shiftFewBitsLeft(1); + d.shiftFewBitsLeft(2); + d.shiftFewBitsLeft(5); + d.shiftFewBitsLeft(8); + d.shiftFewBitsLeft(16); + + assert(initial[0 .. 2] == d.bigits[0 .. 2]); +} diff --git a/druntime/src/core/internal/ftot/package.d b/druntime/src/core/internal/ftot/package.d new file mode 100644 index 000000000000..a741ea2da3f8 --- /dev/null +++ b/druntime/src/core/internal/ftot/package.d @@ -0,0 +1,509 @@ +/// Floating to text conversion +module core.internal.ftot; + +import core.internal.ftot.decimal; +import core.internal.string: unsignedToTempString, unsignedToTempStringTrailingZerosHandling; + +nothrow: +@safe: + +unittest +{ + char[100] buf = '|'; + // Just in case of debugging using stdc: + buf[$-1] = '\0'; + + void testIt(T)(T v, string expected, ushort precision = 6) + { + char[] res; + + res = dtoa_puff!(true, true, uint)(buf, v, precision, false); + assert(res == expected); + + res = dtoa_puff!(true, true, ushort)(buf, v, precision, false); + assert(res == expected); + } + + testIt(-12.345f, "-1.2345e+01"); + testIt(-12.345f, "-1.2345e+01", 5); + testIt(2.0, "2.0e+00"); + testIt(-1.23456789101112, "-1.23457e+00"); + testIt(0.0000123456789, "1.23457e-05"); +} + +/** +Converts an floating value to a string of characters. + +Can be used when compiling with -betterC. Does not allocate memory. + +Params: + value = the floating value to convert + buf = the pre-allocated buffer used to store the result + precision = required count of significant digits + enableTrailingZeroes = add zeros as end filler + +Returns: + The floating value as a string of characters +*/ +char[] floatingToTempString(bool expForm, bool stdcCompat, T)(T value, return scope char[] buf, in ushort precision, bool enableTrailingZeroes) @nogc +if(__traits(isFloating, T)) +{ + static if(size_t.sizeof == 4) + alias BigitType = ushort; + else + alias BigitType = uint; + + version(assert) + { + const bufLen = maxOutputBufSize!(BigitType, T, expForm) + (expForm ? precision : 0); + assert(buf.length >= bufLen); + } + + char[] slice = dtoa_puff!(expForm, stdcCompat, BigitType)(buf, value, precision, enableTrailingZeroes); + + return slice; +} + +unittest +{ + char[21] buf; + auto r = floatingToTempString!(true, true)(-123.456789, buf, 6, false); + + assert(r == "-1.23457e+02"); +} + +version(linux) + version = ComparisonWithLibc; +else version(Windows) + version = ComparisonWithLibc; + +unittest +{ + static void testIt(T)(T val, string phobos_std, string phobos_exp, in size_t line) + { + char[10] lineBuf; + const testOnLine = ` (test on line `~unsignedToTempString(line, lineBuf)~`)`; + + const precision = 7; + + { + void cmp(alias BigitType, bool expForm)() + { + enum bufLen = maxOutputBufSize!(BigitType, T, expForm) + (expForm ? precision : 0); + // For convenient debugging with printf(): + char[bufLen] buf = 'x'; + buf[$-1] = '\0'; + + const shouldBe = expForm ? phobos_exp : phobos_std; + + if(shouldBe == "disabled") + return; + + const r = dtoa_puff!(expForm, false, BigitType)(buf, val, precision, false); + + assert(r == shouldBe, `"`~r~`" but expected "`~shouldBe~`" (as implemented in Phobos, expForm=`~(expForm ? "true" : "false")~`)`~testOnLine); + } + + cmp!(ushort, false); + cmp!(ushort, true); + cmp!(uint, false); + cmp!(uint, true); + } + + version(ComparisonWithLibc) + { + static string fmtStr(bool expForm) + { + static if(!is(T==real)) + return expForm ? "%e" : "%f"; + else + return expForm ? "%Le" : "%Lf"; + } + + static string getLibcText(T val, bool expForm) @trusted + { + import core.stdc.stdio: snprintf; + + const fmt = fmtStr(expForm); + + char[5000] buf; + auto len = snprintf(buf.ptr, buf.length, &fmt[0], val); + assert(len > 0); + assert(len <= buf.length); + + return buf[0 .. len].idup; + } + + void libcCmp(alias BigitType, bool expForm)() + { + /* + The Microsoft C++ compiler treats long double the same + as double, so the C runtime also doesn't have support + for floating point numbers larger than double. + */ + version(Windows) + static if(is(T == real)) + return; + + const stdcText = getLibcText(val, expForm); + + enum bufLen = maxOutputBufSize!(BigitType, T, expForm) + (expForm ? precision : 0); + char[bufLen] buf = 'x'; + buf[$-1] = '\0'; + + const asLibc = dtoa_puff!(expForm, true, BigitType)(buf, val, precision, true); + + assert(asLibc == stdcText, `"`~asLibc~`" but stdc snprintf("`~fmtStr(expForm)~`") returns: "`~stdcText~`"`~testOnLine); + } + + libcCmp!(ushort, false); + libcCmp!(ushort, true); + libcCmp!(uint, false); + libcCmp!(uint, true); + } + } + + // Test on all posible types + static void onAll(T, ARGS...)(T val, ARGS args, in size_t line = __LINE__) + { + static if(T.dig <= float.dig) + testIt(float(val), args, line); + + static if(T.dig <= double.dig) + testIt(double(val), args, line); + + static if(T.dig <= real.dig) + testIt(real(val), args, line); + } + + onAll(1.0f, "1", "1e+00"); + onAll(0.0f, "0", "0e+00"); + onAll(float.nan, "nan", "nan"); + onAll(float.infinity, "inf", "inf"); + onAll(-float.infinity, "-inf", "-inf"); + onAll(-123.45678f, "-123.456779", "-1.234568e+02"); + onAll(1e3f, "1000", "1e+03"); + onAll(0.001f, "0.001", "1e-03"); + onAll(-1.0e-8f, "-0", "-1e-08"); + onAll(-3.75733371660706733e+254, "-375733371660706733350021703837468260693310962815288025942112652778731979228624690746182101684187633634076761835259805303946874579012812170352826319278230807411175518153951956765533871792968837984915446671623313976187557599924035579899807743068455080820736", "-3.757334e+254"); + onAll(-3.75733371660706733e-254, "-0", "-3.757334e-254"); + onAll(float.max, "340282346638528859811704183484516925440" /* well-known maximum 32 bit IEEE-754 floating value */, "3.402823e+38"); + onAll(-float.max, "-340282346638528859811704183484516925440" /* ditto */, "-3.402823e+38"); + onAll(double.max, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368", "1.797693e+308"); + onAll(-double.max, "-179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368", "-1.797693e+308"); + + // Comparison with a predefined string is disabled because it would have to be a huge string + onAll(real.max, "disabled", "disabled"); + onAll(-real.max, "disabled", "disabled"); + + // Very small values + onAll(float.min_normal, "0", "1.175494e-38"); + onAll(double.min_normal, "0", "2.225074e-308"); + onAll(real.min_normal, "0", "disabled" /* platform-dependent */); +} + +/// Returns: immutable portion of needed resulting buffer size. +/// Does not adds variable precision part for scientific notation. +template maxOutputBufSize(BigitType, F, bool expForm) +if(__traits(isFloating, F)) +{ + static assert(F.max_10_exp <= 9999 && -F.min_10_exp <= 9999); + + static if(F.max_10_exp > 999 && -F.min_10_exp > 999) + enum expLen = 4; + else static if(F.max_10_exp > 99 && -F.min_10_exp > 99) + enum expLen = 3; + else + enum expLen = 2; + + mixin BigitsArraySizeCalculation!(BigitType, F); + + static if(expForm) + { + // -0.12345e-123 + enum maxOutputBufSize = 1 /* sign */ + (decimalExp-1) /* possible leading zeros */ + 1 /* dot */ + 2 /* e with sign */ + expLen; + } + else + { + enum totalDigits = initialDigits + maxShifts * decimalExp; + + // -1234567890.1234567 + enum maxOutputBufSize = 1 /* sign */ + 1 /* dot */ + totalDigits; + } +} + +char[] dtoa_puff(bool expForm, bool stdcCompat, T, F)(return scope char[] buf, F val, ushort precision, in bool enableTrailingZeros) +{ + static char[] setRet(return scope char[] buf, string s) + { + auto ret = buf[0 .. s.length]; + ret[] = s[0 .. $]; + return ret; + } + + // Special cases + if(val != val) + return setRet(buf, "nan"); + else if(val > F.max) + return setRet(buf, "inf"); + else if(val < -F.max) + return setRet(buf, "-inf"); + + mixin BigitsArraySizeCalculation!(T, F); + const d = Decimal!(T, bigitsArrLength)(val); + + uint bigitIndex; + + void blockOut(bool enableLeadingZeros)() + { + ref const bigit = d.bigits[bigitIndex]; + + const end = count + d.decimalExp; + auto block = buf[count .. end]; + + const digitsLen = to_chars_reverse(block, bigit); + assert(digitsLen <= d.decimalExp); + + static if(enableLeadingZeros) + { + block[0 .. $ - digitsLen] = '0'; + digitsCount += block.length; + } + else + digitsCount += digitsLen; + + count = end; + bigitIndex++; + } + + // First bigit output + size_t digitsCount; + size_t count = 1; // possible minus sign + size_t firstDigitIdx; + + if(!expForm && d.fractionStart <= 0) + { + // At first need to add leading zeros + auto zNum = -d.fractionStart * d.decimalExp; + if(zNum > precision) + zNum = precision; + + firstDigitIdx = count; + const end = count + zNum + 1; + buf[count .. end] = '0'; + count = end; + + digitsCount += zNum; + + blockOut!true; + } + else + { + blockOut!false; + firstDigitIdx = count - digitsCount; + } + + size_t startIdx = firstDigitIdx; + if(val < 0) + buf[--startIdx] = '-'; + + static if(!expForm) + { + size_t dotPlace = d.fractionStart <= 0 ? 1 : digitsCount; + } + else + { + const dotPlace = 1; + const remainedIntDigits = (d.fractionStart - bigitIndex) * d.decimalExp; + int exp = cast(int)digitsCount - dotPlace + remainedIntDigits; + } + + assert(dotPlace > 0); + + // Remaining bigits output + while(bigitIndex < d.numBigits) + { + static if(expForm) + const currPrecision = digitsCount; + else + { + const onIntegerPart = (bigitIndex < d.fractionStart); + const currPrecision = onIntegerPart ? 0 : digitsCount - dotPlace; + } + + if(currPrecision > precision) + break; + + blockOut!true; + + static if(!expForm) + { + if(onIntegerPart && d.fractionStart > 0) + dotPlace = digitsCount; + } + } + + static if(!expForm) + { + // Precision value is taken into account only after dot + precision += dotPlace - 1; + } + + if(digitsCount > precision) + { + auto toRound = buf[firstDigitIdx .. count]; + count += precision - toRound.length; + auto deltaExp = round(toRound, precision, d.bigits[bigitIndex .. $]); + + static if(expForm) + exp += deltaExp; + + digitsCount = precision; + } + + // Add decimal dot + const dotIdx= firstDigitIdx + dotPlace; + + if(stdcCompat || dotIdx != count) + { + count++; + + //TODO: char-by-char shift is too slow + //1. Split integral and fractional parts to completely avoid such shifts for output by write() + //2. Implement automatic choice of shifting direction to avoid too much iterations during formatting + foreach_reverse(i; dotIdx .. count) + buf[i] = buf[i-1]; + + buf[dotIdx] = '.'; + } + + if(enableTrailingZeros) + { + for(; digitsCount < precision; digitsCount++) + buf[count++] = '0'; + } + else + { + static if(stdcCompat) + { + // Special case for adding 0 after dot if no more digits after dot + if(dotIdx == count - 1) + buf[count++] = '0'; + } + + // Remove trailing zeros + for(; count > dotIdx; count--) + { + const c = buf[count-1]; + + static if(stdcCompat) + { + if(buf[count-2] == '.') + break; + } + else + { + if(c == '.') + { + count--; + break; + } + } + + if(c != '0') + break; + } + } + + static if(expForm) + exponentOutput!stdcCompat(buf, count, exp); + + return buf[startIdx .. count]; +} + +private auto round(T)(return scope char[] buf, in uint precision, in T[] notProcessedBigits) pure +{ + // Checks: exactly 0.5 or a little higher? + bool hasNonzero() + { + foreach(i, ref c; buf[precision + 1 .. $]) + if(c != '0') + return true; + + foreach(i, ref b; notProcessedBigits) + if(b != 0) + return true; + + return false; + }; + + int expDelta; + const digit = buf[precision]; + + if(digit > '5' || (digit == '5' && ((buf[precision - 1] % 2) == 1 || hasNonzero))) + { + int i = precision - 1; + + while(i >= 0 && buf[i] == '9') + buf[i--] = '0'; + + if(i >= 0) + buf[i]++; + else + { + buf[0] = '1'; + expDelta++; + } + } + + return expDelta; +} + +private void exponentOutput(bool stdcCompat)(char[] buf, ref size_t count, int exp) pure +in(exp <= 9999) +in(exp >= -9999) +{ + buf[count++] = 'e'; + + version(all) + { + if(exp >= 0) + buf[count] = '+'; + else + { + buf[count] = '-'; + exp = -exp; + } + + count++; + + if(exp > 99) + { + auto digits = unsignedToTempString(exp); + + const end = count + digits.length; + buf[count .. end] = digits; + count = end; + + return; + } + + const tens = exp / 10; + buf[count++] = cast(char)('0' + tens); + buf[count++] = cast(char)('0' + exp % 10); + } +} + +/// Same as C++ std::to_chars but outputs from right boundary +private size_t to_chars_reverse(T)(scope char[] buf, T val, bool noTrailingZeros = false) pure +if(__traits(isUnsigned, T)) +{ + char[] digits; + + if(noTrailingZeros) + digits = unsignedToTempStringTrailingZerosHandling!(10, false, false)(val, buf); + else + digits = unsignedToTempStringTrailingZerosHandling!(10, false, true)(val, buf); + + return digits.length; +} diff --git a/druntime/src/core/internal/string.d b/druntime/src/core/internal/string.d index c9151a9ef9d1..100467e5da69 100644 --- a/druntime/src/core/internal/string.d +++ b/druntime/src/core/internal/string.d @@ -33,6 +33,12 @@ Returns: */ T[] unsignedToTempString(uint radix = 10, bool upperCase = false, T, U)(in U value, return scope T[] buf) if(__traits(isUnsigned, U)) +{ + return unsignedToTempStringTrailingZerosHandling!(radix, upperCase, true)(value, buf); +} + +package T[] unsignedToTempStringTrailingZerosHandling(uint radix = 10, bool upperCase = false, bool withTrailingZeros, T, U)(in U value, return scope T[] buf) +if(__traits(isUnsigned, U)) { // Process oversized unsigned on 32 bit CPU? static if (size_t.sizeof == 4 && U.sizeof > 4) @@ -41,11 +47,11 @@ if(__traits(isUnsigned, U)) { // use faster 32 bit arithmetic uint val = cast(uint) value; - return toTempStringImpl!(radix, upperCase)(val, buf); + return toTempStringImpl!(radix, upperCase, withTrailingZeros)(val, buf); } } - return toTempStringImpl!(radix, upperCase)(cast()value, buf); + return toTempStringImpl!(radix, upperCase, withTrailingZeros)(cast()value, buf); } ///ditto @@ -56,13 +62,14 @@ if(!__traits(isUnsigned, V)) return unsignedToTempString!(radix, upperCase)(cast(ulong)value, buf); } -private T[] toTempStringImpl(uint radix, bool upperCase, V, T)(V value, ref scope T[] buf) +private T[] toTempStringImpl(uint radix, bool upperCase, bool withTrailingZeros, V, T)(V value, ref scope T[] buf) if (radix >= 2 && radix <= 36 && __traits(isUnsigned, V) && (is(T == char) || is(T == wchar) || is(T == dchar))) { enum baseChar = upperCase ? 'A' : 'a'; size_t i = buf.length; + static if(!withTrailingZeros) bool trailingPassed; do { @@ -77,7 +84,15 @@ if (radix >= 2 && radix <= 36 && x = cast(uint)(value % radix); value /= radix; } - buf[--i] = cast(char)((radix <= 10 || x < 10) ? x + '0' : x - 10 + baseChar); + auto c = cast(char)((radix <= 10 || x < 10) ? x + '0' : x - 10 + baseChar); + static if(!withTrailingZeros) + { + if(!trailingPassed && c == '0') + continue; + else + trailingPassed = true; + } + buf[--i] = c; } while (value); return buf[i .. $]; } @@ -140,6 +155,14 @@ unittest assert(!is(typeof(100.unsignedToTempString!1(buf)))); assert(!is(typeof(100.unsignedToTempString!0(buf) == ""))); assert(!is(typeof(100.unsignedToTempString!37(buf) == ""))); + + // ignore trailing zeros + assert(1u.unsignedToTempStringTrailingZerosHandling!(10, false, false)(buf) == "1"); + assert(0u.unsignedToTempStringTrailingZerosHandling!(10, false, false)(buf) == ""); + assert(1230u.unsignedToTempStringTrailingZerosHandling!(10, false, false)(buf) == "123"); + assert(12004500u.unsignedToTempStringTrailingZerosHandling!(10, false, false)(buf) == "120045"); + assert(0x12ab00u.unsignedToTempStringTrailingZerosHandling!(16, false, false)(buf) == "12ab"); + assert(0x120abu.unsignedToTempStringTrailingZerosHandling!(16, false, false)(buf) == "120ab"); } alias SignedStringBuf = char[65];