Mercurial > projects > ldc
view 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 source
/******************************************************************************* 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'; } }