Mercurial > projects > ldc
diff tango/tango/text/locale/Convert.d @ 132:1700239cab2e trunk
[svn r136] MAJOR UNSTABLE UPDATE!!!
Initial commit after moving to Tango instead of Phobos.
Lots of bugfixes...
This build is not suitable for most things.
author | lindquist |
---|---|
date | Fri, 11 Jan 2008 17:57:40 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tango/tango/text/locale/Convert.d Fri Jan 11 17:57:40 2008 +0100 @@ -0,0 +1,1560 @@ +/******************************************************************************* + + copyright: Copyright (c) 2005 John Chapman. All rights reserved + + license: BSD style: $(LICENSE) + + version: Initial release: 2005 + + author: John Chapman + +******************************************************************************/ + +module tango.text.locale.Convert; + +private import tango.time.WallClock; + +private import tango.core.Exception; + +private import tango.text.locale.Core; + +private import tango.time.chrono.Calendar; + +private import Integer = tango.text.convert.Integer; + +/****************************************************************************** + +******************************************************************************/ + +private struct Result +{ + private uint index; + private char[] target_; + + /********************************************************************** + + **********************************************************************/ + + private static Result opCall (char[] target) + { + Result result; + + result.target_ = target; + return result; + } + + /********************************************************************** + + **********************************************************************/ + + private void opCatAssign (char[] rhs) + { + uint end = index + rhs.length; + + target_[index .. end] = rhs; + index = end; + } + + /********************************************************************** + + **********************************************************************/ + + private void opCatAssign (char rhs) + { + target_[index++] = rhs; + } + + /********************************************************************** + + **********************************************************************/ + + private char[] get () + { + return target_[0 .. index]; + } + + /********************************************************************** + + **********************************************************************/ + + private char[] scratch () + { + return target_; + } +} + + +/****************************************************************************** + + * Converts the value of this instance to its equivalent string representation using the specified _format and culture-specific formatting information. + * Params: + * format = A _format string. + * formatService = An IFormatService that provides culture-specific formatting information. + * Returns: A string representation of the value of this instance as specified by format and formatService. + * Remarks: See $(LINK2 datetimeformat.html, Time Formatting) for more information about date and time formatting. + * Examples: + * --- + * import tango.io.Print, tango.text.locale.Core, tango.time.WallClock; + * + * void main() { + * Culture culture = Culture.current; + * Time now = WallClock.now; + * + * Println("Current date and time: %s", now.toString()); + * Println(); + * + * // Format the current date and time in a number of ways. + * Println("Culture: %s", culture.englishName); + * Println(); + * + * Println("Short date: %s", now.toString("d")); + * Println("Long date: %s", now.toString("D")); + * Println("Short time: %s", now.toString("t")); + * Println("Long time: %s", now.toString("T")); + * Println("General date short time: %s", now.toString("g")); + * Println("General date long time: %s", now.toString("G")); + * Println("Month: %s", now.toString("M")); + * Println("RFC1123: %s", now.toString("R")); + * Println("Sortable: %s", now.toString("s")); + * Println("Year: %s", now.toString("Y")); + * Println(); + * + * // Display the same values using a different culture. + * culture = Culture.getCulture("fr-FR"); + * Println("Culture: %s", culture.englishName); + * Println(); + * + * Println("Short date: %s", now.toString("d", culture)); + * Println("Long date: %s", now.toString("D", culture)); + * Println("Short time: %s", now.toString("t", culture)); + * Println("Long time: %s", now.toString("T", culture)); + * Println("General date short time: %s", now.toString("g", culture)); + * Println("General date long time: %s", now.toString("G", culture)); + * Println("Month: %s", now.toString("M", culture)); + * Println("RFC1123: %s", now.toString("R", culture)); + * Println("Sortable: %s", now.toString("s", culture)); + * Println("Year: %s", now.toString("Y", culture)); + * Println(); + * } + * + * // Produces the following output: + * // Current date and time: 26/05/2006 10:04:57 AM + * // + * // Culture: English (United Kingdom) + * // + * // Short date: 26/05/2006 + * // Long date: 26 May 2006 + * // Short time: 10:04 + * // Long time: 10:04:57 AM + * // General date short time: 26/05/2006 10:04 + * // General date long time: 26/05/2006 10:04:57 AM + * // Month: 26 May + * // RFC1123: Fri, 26 May 2006 10:04:57 GMT + * // Sortable: 2006-05-26T10:04:57 + * // Year: May 2006 + * // + * // Culture: French (France) + * // + * // Short date: 26/05/2006 + * // Long date: vendredi 26 mai 2006 + * // Short time: 10:04 + * // Long time: 10:04:57 + * // General date short time: 26/05/2006 10:04 + * // General date long time: 26/05/2006 10:04:57 + * // Month: 26 mai + * // RFC1123: ven., 26 mai 2006 10:04:57 GMT + * // Sortable: 2006-05-26T10:04:57 + * // Year: mai 2006 + * --- + +******************************************************************************/ + +public char[] formatDateTime (char[] output, Time dateTime, char[] format, IFormatService formatService = null) +{ + return formatDateTime (output, dateTime, format, DateTimeFormat.getInstance(formatService)); +} + +char[] formatDateTime (char[] output, Time dateTime, char[] format, DateTimeFormat dtf) +{ + /********************************************************************** + + **********************************************************************/ + + char[] expandKnownFormat(char[] format, inout Time dateTime) + { + char[] f; + + switch (format[0]) + { + case 'd': + f = dtf.shortDatePattern; + break; + case 'D': + f = dtf.longDatePattern; + break; + case 'f': + f = dtf.longDatePattern ~ " " ~ dtf.shortTimePattern; + break; + case 'F': + f = dtf.fullDateTimePattern; + break; + case 'g': + f = dtf.generalShortTimePattern; + break; + case 'G': + f = dtf.generalLongTimePattern; + break; + case 'm': + case 'M': + f = dtf.monthDayPattern; + break; + case 'r': + case 'R': + f = dtf.rfc1123Pattern; + break; + case 's': + f = dtf.sortableDateTimePattern; + break; + case 't': + f = dtf.shortTimePattern; + break; + case 'T': + f = dtf.longTimePattern; + break; +version (Full) +{ + case 'u': + dateTime = dateTime.toUniversalTime(); + dtf = DateTimeFormat.invariantFormat; + f = dtf.universalSortableDateTimePattern; + break; + case 'U': + dtf = cast(DateTimeFormat) dtf.clone(); + dateTime = dateTime.toUniversalTime(); + if (typeid(typeof(dtf.calendar)) !is typeid(Gregorian)) + dtf.calendar = Gregorian.generic; + f = dtf.fullDateTimePattern; + break; +} + case 'y': + case 'Y': + f = dtf.yearMonthPattern; + break; + default: + throw new IllegalArgumentException("Invalid date format."); + } + + return f; + } + + /********************************************************************** + + **********************************************************************/ + + char[] formatCustom (inout Result result, Time dateTime, char[] format) + { + + int parseRepeat(char[] format, int pos, char c) + { + int n = pos + 1; + while (n < format.length && format[n] is c) + n++; + return n - pos; + } + + char[] formatDayOfWeek(Calendar.DayOfWeek dayOfWeek, int rpt) + { + if (rpt is 3) + return dtf.getAbbreviatedDayName(dayOfWeek); + return dtf.getDayName(dayOfWeek); + } + + char[] formatMonth(int month, int rpt) + { + if (rpt is 3) + return dtf.getAbbreviatedMonthName(month); + return dtf.getMonthName(month); + } + + char[] formatInt (char[] tmp, int v, int minimum) + { + auto num = Integer.format (tmp, v, Integer.Style.Unsigned); + if ((minimum -= num.length) > 0) + { + auto p = tmp.ptr + tmp.length - num.length; + while (minimum--) + *--p = '0'; + num = tmp [p-tmp.ptr .. $]; + } + return num; + } + + int parseQuote(char[] format, int pos, out char[] result) + { + int start = pos; + char chQuote = format[pos++]; + bool found; + while (pos < format.length) + { + char c = format[pos++]; + if (c is chQuote) + { + found = true; + break; + } + else + if (c is '\\') + { // escaped + if (pos < format.length) + result ~= format[pos++]; + } + else + result ~= c; + } + return pos - start; + } + + + Calendar calendar = dtf.calendar; + bool justTime = true; + int index, len; + char[10] tmp; + + if (format[0] is '%') + { + // specifiers for both standard format strings and custom ones + const char[] commonSpecs = "dmMsty"; + foreach (c; commonSpecs) + if (format[1] is c) + { + index += 1; + break; + } + } + + while (index < format.length) + { + char c = format[index]; + auto time = dateTime.time; + + switch (c) + { + case 'd': // day + len = parseRepeat(format, index, c); + if (len <= 2) + { + int day = calendar.getDayOfMonth(dateTime); + result ~= formatInt (tmp, day, len); + } + else + result ~= formatDayOfWeek(calendar.getDayOfWeek(dateTime), len); + justTime = false; + break; + + case 'M': // month + len = parseRepeat(format, index, c); + int month = calendar.getMonth(dateTime); + if (len <= 2) + result ~= formatInt (tmp, month, len); + else + result ~= formatMonth(month, len); + justTime = false; + break; + case 'y': // year + len = parseRepeat(format, index, c); + int year = calendar.getYear(dateTime); + // Two-digit years for Japanese + if (calendar.id is Calendar.JAPAN) + result ~= formatInt (tmp, year, 2); + else + { + if (len <= 2) + result ~= formatInt (tmp, year % 100, len); + else + result ~= formatInt (tmp, year, len); + } + justTime = false; + break; + case 'h': // hour (12-hour clock) + len = parseRepeat(format, index, c); + int hour = time.hours % 12; + if (hour is 0) + hour = 12; + result ~= formatInt (tmp, hour, len); + break; + case 'H': // hour (24-hour clock) + len = parseRepeat(format, index, c); + result ~= formatInt (tmp, time.hours, len); + break; + case 'm': // minute + len = parseRepeat(format, index, c); + result ~= formatInt (tmp, time.minutes, len); + break; + case 's': // second + len = parseRepeat(format, index, c); + result ~= formatInt (tmp, time.seconds, len); + break; + case 't': // AM/PM + len = parseRepeat(format, index, c); + if (len is 1) + { + if (time.hours < 12) + { + if (dtf.amDesignator.length != 0) + result ~= dtf.amDesignator[0]; + } + else + { + if (dtf.pmDesignator.length != 0) + result ~= dtf.pmDesignator[0]; + } + } + else + result ~= (time.hours < 12) ? dtf.amDesignator : dtf.pmDesignator; + break; + case 'z': // timezone offset + len = parseRepeat(format, index, c); +version (Full) +{ + TimeSpan offset = (justTime && dateTime.ticks < TICKS_PER_DAY) + ? TimeZone.current.getUtcOffset(WallClock.now) + : TimeZone.current.getUtcOffset(dateTime); + int hours = offset.hours; + int minutes = offset.minutes; + result ~= (offset.backward) ? '-' : '+'; +} +else +{ + auto minutes = cast(int) (WallClock.zone.minutes); + if (minutes < 0) + minutes = -minutes, result ~= '-'; + else + result ~= '+'; + int hours = minutes / 60; + minutes %= 60; +} + if (len is 1) + result ~= formatInt (tmp, hours, 1); + else + if (len is 2) + result ~= formatInt (tmp, hours, 2); + else + { + result ~= formatInt (tmp, hours, 2); + result ~= ':'; + result ~= formatInt (tmp, minutes, 2); + } + break; + case ':': // time separator + len = 1; + result ~= dtf.timeSeparator; + break; + case '/': // date separator + len = 1; + result ~= dtf.dateSeparator; + break; + case '\"': // string literal + case '\'': // char literal + char[] quote; + len = parseQuote(format, index, quote); + result ~= quote; + break; + default: + len = 1; + result ~= c; + break; + } + index += len; + } + return result.get; + } + + + auto result = Result (output); + + if (format is null) + format = "G"; // Default to general format. + + if (format.length is 1) // It might be one of our shortcuts. + format = expandKnownFormat (format, dateTime); + + return formatCustom (result, dateTime, format); +} + + + +/******************************************************************************* + +*******************************************************************************/ + +private extern (C) private char* ecvt(double d, int digits, out int decpt, out bool sign); + +/******************************************************************************* + +*******************************************************************************/ + +// Must match NumberFormat.decimalPositivePattern +package const char[] positiveNumberFormat = "#"; + +// Must match NumberFormat.decimalNegativePattern +package const char[][] negativeNumberFormats = + [ + "(#)", "-#", "- #", "#-", "# -" + ]; + +// Must match NumberFormat.currencyPositivePattern +package const char[][] positiveCurrencyFormats = + [ + "$#", "#$", "$ #", "# $" + ]; + +// Must match NumberFormat.currencyNegativePattern +package const char[][] negativeCurrencyFormats = + [ + "($#)", "-$#", "$-#", "$#-", "(#$)", + "-#$", "#-$", "#$-", "-# $", "-$ #", + "# $-", "$ #-", "$ -#", "#- $", "($ #)", "(# $)" + ]; + +/******************************************************************************* + +*******************************************************************************/ + +package template charTerm (T) +{ + package int charTerm(T* s) + { + int i; + while (*s++ != '\0') + i++; + return i; + } +} + +/******************************************************************************* + +*******************************************************************************/ + +char[] longToString (char[] buffer, long value, int digits, char[] negativeSign) +{ + if (digits < 1) + digits = 1; + + int n = buffer.length; + ulong uv = (value >= 0) ? value : cast(ulong) -value; + + if (uv > uint.max) + { + while (--digits >= 0 || uv != 0) + { + buffer[--n] = uv % 10 + '0'; + uv /= 10; + } + } + else + { + uint v = cast(uint) uv; + while (--digits >= 0 || v != 0) + { + buffer[--n] = v % 10 + '0'; + v /= 10; + } + } + + + if (value < 0) + { + for (int i = negativeSign.length - 1; i >= 0; i--) + buffer[--n] = negativeSign[i]; + } + + return buffer[n .. $]; +} + +/******************************************************************************* + +*******************************************************************************/ + +char[] longToHexString (char[] buffer, ulong value, int digits, char format) +{ + if (digits < 1) + digits = 1; + + int n = buffer.length; + while (--digits >= 0 || value != 0) + { + auto v = cast(uint) value & 0xF; + buffer[--n] = (v < 10) ? v + '0' : v + format - ('X' - 'A' + 10); + value >>= 4; + } + + return buffer[n .. $]; +} + +/******************************************************************************* + +*******************************************************************************/ + +char[] longToBinString (char[] buffer, ulong value, int digits) +{ + if (digits < 1) + digits = 1; + + int n = buffer.length; + while (--digits >= 0 || value != 0) + { + buffer[--n] = (value & 1) + '0'; + value >>= 1; + } + + return buffer[n .. $]; +} + +/******************************************************************************* + +*******************************************************************************/ + +char parseFormatSpecifier (char[] format, out int length) +{ + int i = -1; + char specifier; + + if (format.length) + { + auto s = format[0]; + + if (s >= 'A' && s <= 'Z' || s >= 'a' && s <= 'z') + { + specifier = s; + + foreach (c; format [1..$]) + if (c >= '0' && c <= '9') + { + c -= '0'; + if (i < 0) + i = c; + else + i = i * 10 + c; + } + else + break; + } + } + else + specifier = 'G'; + + length = i; + return specifier; +} + +/******************************************************************************* + +*******************************************************************************/ + +char[] formatInteger (char[] output, long value, char[] format, NumberFormat nf) +{ + int length; + auto specifier = parseFormatSpecifier (format, length); + + switch (specifier) + { + case 'g': + case 'G': + if (length > 0) + break; + // Fall through. + + case 'd': + case 'D': + return longToString (output, value, length, nf.negativeSign); + + case 'x': + case 'X': + return longToHexString (output, cast(ulong)value, length, specifier); + + case 'b': + case 'B': + return longToBinString (output, cast(ulong)value, length); + + default: + break; + } + + Result result = Result (output); + Number number = Number (value); + if (specifier != char.init) + return toString (number, result, specifier, length, nf); + + return number.toStringFormat (result, format, nf); +} + +/******************************************************************************* + +*******************************************************************************/ + +private enum { + EXP = 0x7ff, + NAN_FLAG = 0x80000000, + INFINITY_FLAG = 0x7fffffff, + } + +char[] formatDouble (char[] output, double value, char[] format, NumberFormat nf) +{ + int length; + int precision = 6; + Result result = Result (output); + char specifier = parseFormatSpecifier (format, length); + + switch (specifier) + { + case 'r': + case 'R': + Number number = Number (value, 15); + + if (number.scale == NAN_FLAG) + return nf.nanSymbol; + + if (number.scale == INFINITY_FLAG) + return number.sign ? nf.negativeInfinitySymbol + : nf.positiveInfinitySymbol; + + double d; + number.toDouble(d); + if (d == value) + return toString (number, result, 'G', 15, nf); + + number = Number(value, 17); + return toString (number, result, 'G', 17, nf); + + case 'g': + case 'G': + if (length > 15) + precision = 17; + // Fall through. + + default: + break; + } + + Number number = Number(value, precision); + + if (number.scale == NAN_FLAG) + return nf.nanSymbol; + + if (number.scale == INFINITY_FLAG) + return number.sign ? nf.negativeInfinitySymbol + : nf.positiveInfinitySymbol; + + if (specifier != char.init) + return toString (number, result, specifier, length, nf); + + return number.toStringFormat (result, format, nf); +} + +/******************************************************************************* + +*******************************************************************************/ + +void formatGeneral (inout Number number, inout Result target, int length, char format, NumberFormat nf) +{ + int pos = number.scale; + + auto p = number.digits.ptr; + if (pos > 0) + { + while (pos > 0) + { + target ~= (*p != '\0') ? *p++ : '0'; + pos--; + } + } + else + target ~= '0'; + + if (*p != '\0') + { + target ~= nf.numberDecimalSeparator; + while (pos < 0) + { + target ~= '0'; + pos++; + } + + while (*p != '\0') + target ~= *p++; + } +} + +/******************************************************************************* + +*******************************************************************************/ + +void formatNumber (inout Number number, inout Result target, int length, NumberFormat nf) +{ + char[] format = number.sign ? negativeNumberFormats[nf.numberNegativePattern] + : positiveNumberFormat; + + // Parse the format. + foreach (c; format) + { + switch (c) + { + case '#': + formatFixed (number, target, length, nf.numberGroupSizes, + nf.numberDecimalSeparator, nf.numberGroupSeparator); + break; + + case '-': + target ~= nf.negativeSign; + break; + + default: + target ~= c; + break; + } + } +} + +/******************************************************************************* + +*******************************************************************************/ + +void formatCurrency (inout Number number, inout Result target, int length, NumberFormat nf) +{ + char[] format = number.sign ? negativeCurrencyFormats[nf.currencyNegativePattern] + : positiveCurrencyFormats[nf.currencyPositivePattern]; + + // Parse the format. + foreach (c; format) + { + switch (c) + { + case '#': + formatFixed (number, target, length, nf.currencyGroupSizes, + nf.currencyDecimalSeparator, nf.currencyGroupSeparator); + break; + + case '-': + target ~= nf.negativeSign; + break; + + case '$': + target ~= nf.currencySymbol; + break; + + default: + target ~= c; + break; + } + } +} + +/******************************************************************************* + +*******************************************************************************/ + +void formatFixed (inout Number number, inout Result target, int length, + int[] groupSizes, char[] decimalSeparator, char[] groupSeparator) +{ + int pos = number.scale; + auto p = number.digits.ptr; + + if (pos > 0) + { + if (groupSizes.length != 0) + { + // Calculate whether we have enough digits to format. + int count = groupSizes[0]; + int index, size; + + while (pos > count) + { + size = groupSizes[index]; + if (size == 0) + break; + + if (index < groupSizes.length - 1) + index++; + + count += groupSizes[index]; + } + + size = (count == 0) ? 0 : groupSizes[0]; + + // Insert the separator according to groupSizes. + int end = charTerm(p); + int start = (pos < end) ? pos : end; + + + char[] separator = groupSeparator; + index = 0; + + // questionable: use the back end of the output buffer to + // format the separators, and then copy back to start + char[] temp = target.scratch; + uint ii = temp.length; + + for (int c, i = pos - 1; i >= 0; i--) + { + temp[--ii] = (i < start) ? number.digits[i] : '0'; + if (size > 0) + { + c++; + if (c == size && i != 0) + { + uint iii = ii - separator.length; + temp[iii .. ii] = separator; + ii = iii; + + if (index < groupSizes.length - 1) + size = groupSizes[++index]; + + c = 0; + } + } + } + target ~= temp[ii..$]; + p += start; + } + else + { + while (pos > 0) + { + target ~= (*p != '\0') ? *p++ : '0'; + pos--; + } + } + } + else + // Negative scale. + target ~= '0'; + + if (length > 0) + { + target ~= decimalSeparator; + while (pos < 0 && length > 0) + { + target ~= '0'; + pos++; + length--; + } + + while (length > 0) + { + target ~= (*p != '\0') ? *p++ : '0'; + length--; + } + } +} + +/****************************************************************************** + +******************************************************************************/ + +char[] toString (inout Number number, inout Result result, char format, int length, NumberFormat nf) +{ + switch (format) + { + case 'c': + case 'C': + // Currency + if (length < 0) + length = nf.currencyDecimalDigits; + + number.round(number.scale + length); + formatCurrency (number, result, length, nf); + break; + + case 'f': + case 'F': + // Fixed + if (length < 0) + length = nf.numberDecimalDigits; + + number.round(number.scale + length); + if (number.sign) + result ~= nf.negativeSign; + + formatFixed (number, result, length, null, nf.numberDecimalSeparator, null); + break; + + case 'n': + case 'N': + // Number + if (length < 0) + length = nf.numberDecimalDigits; + + number.round (number.scale + length); + formatNumber (number, result, length, nf); + break; + + case 'g': + case 'G': + // General + if (length < 1) + length = number.precision; + + number.round(length); + if (number.sign) + result ~= nf.negativeSign; + + formatGeneral (number, result, length, (format == 'g') ? 'e' : 'E', nf); + break; + + default: + return "{invalid FP format specifier '" ~ format ~ "'}"; + } + return result.get; +} + + +/******************************************************************************* + +*******************************************************************************/ + +private struct Number +{ + int scale; + bool sign; + int precision; + char[32] digits = void; + + /********************************************************************** + + **********************************************************************/ + + private static Number opCall (long value) + { + Number number; + number.precision = 20; + + if (value < 0) + { + number.sign = true; + value = -value; + } + + char[20] buffer = void; + int n = buffer.length; + + while (value != 0) + { + buffer[--n] = value % 10 + '0'; + value /= 10; + } + + int end = number.scale = -(n - buffer.length); + number.digits[0 .. end] = buffer[n .. n + end]; + number.digits[end] = '\0'; + + return number; + } + + /********************************************************************** + + **********************************************************************/ + + private static Number opCall (double value, int precision) + { + Number number; + number.precision = precision; + + auto p = number.digits.ptr; + long bits = *cast(long*) & value; + long mant = bits & 0x000FFFFFFFFFFFFFL; + int exp = cast(int)((bits >> 52) & EXP); + + if (exp == EXP) + { + number.scale = (mant != 0) ? NAN_FLAG : INFINITY_FLAG; + if (((bits >> 63) & 1) != 0) + number.sign = true; + } + else + { + // Get the digits, decimal point and sign. + char* chars = ecvt(value, number.precision, number.scale, number.sign); + if (*chars != '\0') + { + while (*chars != '\0') + *p++ = *chars++; + } + } + + *p = '\0'; + return number; + } + + /********************************************************************** + + **********************************************************************/ + + private bool toDouble(out double value) + { + const ulong[] pow10 = + [ + 0xa000000000000000UL, + 0xc800000000000000UL, + 0xfa00000000000000UL, + 0x9c40000000000000UL, + 0xc350000000000000UL, + 0xf424000000000000UL, + 0x9896800000000000UL, + 0xbebc200000000000UL, + 0xee6b280000000000UL, + 0x9502f90000000000UL, + 0xba43b74000000000UL, + 0xe8d4a51000000000UL, + 0x9184e72a00000000UL, + 0xb5e620f480000000UL, + 0xe35fa931a0000000UL, + 0xcccccccccccccccdUL, + 0xa3d70a3d70a3d70bUL, + 0x83126e978d4fdf3cUL, + 0xd1b71758e219652eUL, + 0xa7c5ac471b478425UL, + 0x8637bd05af6c69b7UL, + 0xd6bf94d5e57a42beUL, + 0xabcc77118461ceffUL, + 0x89705f4136b4a599UL, + 0xdbe6fecebdedd5c2UL, + 0xafebff0bcb24ab02UL, + 0x8cbccc096f5088cfUL, + 0xe12e13424bb40e18UL, + 0xb424dc35095cd813UL, + 0x901d7cf73ab0acdcUL, + 0x8e1bc9bf04000000UL, + 0x9dc5ada82b70b59eUL, + 0xaf298d050e4395d6UL, + 0xc2781f49ffcfa6d4UL, + 0xd7e77a8f87daf7faUL, + 0xefb3ab16c59b14a0UL, + 0x850fadc09923329cUL, + 0x93ba47c980e98cdeUL, + 0xa402b9c5a8d3a6e6UL, + 0xb616a12b7fe617a8UL, + 0xca28a291859bbf90UL, + 0xe070f78d39275566UL, + 0xf92e0c3537826140UL, + 0x8a5296ffe33cc92cUL, + 0x9991a6f3d6bf1762UL, + 0xaa7eebfb9df9de8aUL, + 0xbd49d14aa79dbc7eUL, + 0xd226fc195c6a2f88UL, + 0xe950df20247c83f8UL, + 0x81842f29f2cce373UL, + 0x8fcac257558ee4e2UL, + ]; + + const uint[] pow10Exp = + [ + 4, 7, 10, 14, 17, 20, 24, 27, 30, 34, + 37, 40, 44, 47, 50, 54, 107, 160, 213, 266, + 319, 373, 426, 479, 532, 585, 638, 691, 745, 798, + 851, 904, 957, 1010, 1064, 1117 + ]; + + uint getDigits(char* p, int len) + { + char* end = p + len; + uint r = *p - '0'; + p++; + while (p < end) + { + r = 10 * r + *p - '0'; + p++; + } + return r; + } + + ulong mult64(uint val1, uint val2) + { + return cast(ulong)val1 * cast(ulong)val2; + } + + ulong mult64L(ulong val1, ulong val2) + { + ulong v = mult64(cast(uint)(val1 >> 32), cast(uint)(val2 >> 32)); + v += mult64(cast(uint)(val1 >> 32), cast(uint)val2) >> 32; + v += mult64(cast(uint)val1, cast(uint)(val2 >> 32)) >> 32; + return v; + } + + auto p = digits.ptr; + int count = charTerm(p); + int left = count; + + while (*p == '0') + { + left--; + p++; + } + + // If the digits consist of nothing but zeros... + if (left == 0) + { + value = 0.0; + return true; + } + + // Get digits, 9 at a time. + int n = (left > 9) ? 9 : left; + left -= n; + ulong bits = getDigits(p, n); + if (left > 0) + { + n = (left > 9) ? 9 : left; + left -= n; + bits = mult64(cast(uint)bits, cast(uint)(pow10[n - 1] >>> (64 - pow10Exp[n - 1]))); + bits += getDigits(p + 9, n); + } + + int scale = this.scale - (count - left); + int s = (scale < 0) ? -scale : scale; + + if (s >= 352) + { + *cast(long*)&value = (scale > 0) ? 0x7FF0000000000000 : 0; + return false; + } + + // Normalise mantissa and bits. + int bexp = 64; + int nzero; + if ((bits >> 32) != 0) + nzero = 32; + + if ((bits >> (16 + nzero)) != 0) + nzero += 16; + + if ((bits >> (8 + nzero)) != 0) + nzero += 8; + + if ((bits >> (4 + nzero)) != 0) + nzero += 4; + + if ((bits >> (2 + nzero)) != 0) + nzero += 2; + + if ((bits >> (1 + nzero)) != 0) + nzero++; + + if ((bits >> nzero) != 0) + nzero++; + + bits <<= 64 - nzero; + bexp -= 64 - nzero; + + // Get decimal exponent. + if ((s & 15) != 0) + { + int expMult = pow10Exp[(s & 15) - 1]; + bexp += (scale < 0) ? ( -expMult + 1) : expMult; + bits = mult64L(bits, pow10[(s & 15) + ((scale < 0) ? 15 : 0) - 1]); + if ((bits & 0x8000000000000000L) == 0) + { + bits <<= 1; + bexp--; + } + } + + if ((s >> 4) != 0) + { + int expMult = pow10Exp[15 + ((s >> 4) - 1)]; + bexp += (scale < 0) ? ( -expMult + 1) : expMult; + bits = mult64L(bits, pow10[30 + ((s >> 4) + ((scale < 0) ? 21 : 0) - 1)]); + if ((bits & 0x8000000000000000L) == 0) + { + bits <<= 1; + bexp--; + } + } + + // Round and scale. + if (cast(uint)bits & (1 << 10) != 0) + { + bits += (1 << 10) - 1 + (bits >>> 11) & 1; + bits >>= 11; + if (bits == 0) + bexp++; + } + else + bits >>= 11; + + bexp += 1022; + if (bexp <= 0) + { + if (bexp < -53) + bits = 0; + else + bits >>= ( -bexp + 1); + } + bits = (cast(ulong)bexp << 52) + (bits & 0x000FFFFFFFFFFFFFL); + + if (sign) + bits |= 0x8000000000000000L; + + value = *cast(double*) & bits; + return true; + } + + + + /********************************************************************** + + **********************************************************************/ + + private char[] toStringFormat (inout Result result, char[] format, NumberFormat nf) + { + bool hasGroups; + int groupCount; + int groupPos = -1, pointPos = -1; + int first = int.max, last, count; + bool scientific; + int n; + char c; + + while (n < format.length) + { + c = format[n++]; + switch (c) + { + case '#': + count++; + break; + + case '0': + if (first == int.max) + first = count; + count++; + last = count; + break; + + case '.': + if (pointPos < 0) + pointPos = count; + break; + + case ',': + if (count > 0 && pointPos < 0) + { + if (groupPos >= 0) + { + if (groupPos == count) + { + groupCount++; + break; + } + hasGroups = true; + } + groupPos = count; + groupCount = 1; + } + break; + + case '\'': + case '\"': + while (n < format.length && format[n++] != c) + {} + break; + + case '\\': + if (n < format.length) + n++; + break; + + default: + break; + } + } + + if (pointPos < 0) + pointPos = count; + + int adjust; + if (groupPos >= 0) + { + if (groupPos == pointPos) + adjust -= groupCount * 3; + else + hasGroups = true; + } + + if (digits[0] != '\0') + { + scale += adjust; + round(scientific ? count : scale + count - pointPos); + } + + first = (first < pointPos) ? pointPos - first : 0; + last = (last > pointPos) ? pointPos - last : 0; + + int pos = pointPos; + int extra; + if (!scientific) + { + pos = (scale > pointPos) ? scale : pointPos; + extra = scale - pointPos; + } + + char[] groupSeparator = nf.numberGroupSeparator; + char[] decimalSeparator = nf.numberDecimalSeparator; + + // Work out the positions of the group separator. + int[] groupPositions; + int groupIndex = -1; + if (hasGroups) + { + if (nf.numberGroupSizes.length == 0) + hasGroups = false; + else + { + int groupSizesTotal = nf.numberGroupSizes[0]; + int groupSize = groupSizesTotal; + int digitsTotal = pos + ((extra < 0) ? extra : 0); + int digitCount = (first > digitsTotal) ? first : digitsTotal; + + int sizeIndex; + while (digitCount > groupSizesTotal) + { + if (groupSize == 0) + break; + + groupPositions ~= groupSizesTotal; + groupIndex++; + + if (sizeIndex < nf.numberGroupSizes.length - 1) + groupSize = nf.numberGroupSizes[++sizeIndex]; + + groupSizesTotal += groupSize; + } + } + } + + //char[] result; + if (sign) + result ~= nf.negativeSign; + + auto p = digits.ptr; + n = 0; + bool pointWritten; + + while (n < format.length) + { + c = format[n++]; + if (extra > 0 && (c == '#' || c == '0' || c == '.')) + { + while (extra > 0) + { + result ~= (*p != '\0') ? *p++ : '0'; + + if (hasGroups && pos > 1 && groupIndex >= 0) + { + if (pos == groupPositions[groupIndex] + 1) + { + result ~= groupSeparator; + groupIndex--; + } + } + pos--; + extra--; + } + } + + switch (c) + { + case '#': + case '0': + if (extra < 0) + { + extra++; + c = (pos <= first) ? '0' : char.init; + } + else + c = (*p != '\0') ? *p++ : pos > last ? '0' : char.init; + + if (c != char.init) + { + result ~= c; + + if (hasGroups && pos > 1 && groupIndex >= 0) + { + if (pos == groupPositions[groupIndex] + 1) + { + result ~= groupSeparator; + groupIndex--; + } + } + } + pos--; + break; + + case '.': + if (pos != 0 || pointWritten) + break; + if (last < 0 || (pointPos < count && *p != '\0')) + { + result ~= decimalSeparator; + pointWritten = true; + } + break; + + case ',': + break; + + case '\'': + case '\"': + if (n < format.length) + n++; + break; + + case '\\': + if (n < format.length) + result ~= format[n++]; + break; + + default: + result ~= c; + break; + } + } + return result.get; + } + + /********************************************************************** + + **********************************************************************/ + + private void round (int pos) + { + int index; + while (index < pos && digits[index] != '\0') + index++; + + if (index == pos && digits[index] >= '5') + { + while (index > 0 && digits[index - 1] == '9') + index--; + + if (index > 0) + digits[index - 1]++; + else + { + scale++; + digits[0] = '1'; + index = 1; + } + } + else + while (index > 0 && digits[index - 1] == '0') + index--; + + if (index == 0) + { + scale = 0; + sign = false; + } + + digits[index] = '\0'; + } +}