12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565 |
|
/*******************************************************************************
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 size_t index;
private char[] target_;
/**********************************************************************
**********************************************************************/
private static Result opCall (char[] target)
{
Result result;
result.target_ = target;
return result;
}
/**********************************************************************
**********************************************************************/
private void opOpAssign(immutable(char)[] s : "~") (const(char)[] rhs)
{
auto end = index + rhs.length;
target_[index .. end] = rhs;
index = end;
}
/**********************************************************************
**********************************************************************/
private void opOpAssign(immutable(char)[] s : "~") (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, const(char)[] format, IFormatService formatService = null)
{
return formatDateTime (output, dateTime, format, DateTimeFormat.getInstance(formatService));
}
char[] formatDateTime (char[] output, Time dateTime, const(char)[] format, DateTimeFormat dtf)
{
/**********************************************************************
**********************************************************************/
const(char)[] expandKnownFormat(const(char)[] format, ref Time dateTime)
{
const(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 (ref Result result, Time dateTime, const(char)[] format)
{
int parseRepeat(const(char)[] format, int pos, char c)
{
int n = pos + 1;
while (n < format.length && format[n] is c)
n++;
return n - pos;
}
const(char)[] formatDayOfWeek(Calendar.DayOfWeek dayOfWeek, int rpt)
{
if (rpt is 3)
return dtf.getAbbreviatedDayName(dayOfWeek);
return dtf.getDayName(dayOfWeek);
}
const(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, "u");
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(const(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
enum immutable(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) char* ecvt(double d, int digits, out int decpt, out bool sign);
/*******************************************************************************
*******************************************************************************/
// Must match NumberFormat.decimalPositivePattern
package enum immutable(char)[] positiveNumberFormat = "#";
// Must match NumberFormat.decimalNegativePattern
package enum immutable(char)[][] negativeNumberFormats =
[
"(#)", "-#", "- #", "#-", "# -"
];
// Must match NumberFormat.currencyPositivePattern
package enum immutable(char)[][] positiveCurrencyFormats =
[
"$#", "#$", "$ #", "# $"
];
// Must match NumberFormat.currencyNegativePattern
package enum immutable(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, const(char)[] negativeSign)
{
if (digits < 1)
digits = 1;
auto n = buffer.length;
ulong uv = (value >= 0) ? value : cast(ulong) -value;
if (uv > uint.max)
{
while (--digits >= 0 || uv != 0)
{
buffer[--n] = cast(char)(uv % 10 + '0');
uv /= 10;
}
}
else
{
uint v = cast(uint) uv;
while (--digits >= 0 || v != 0)
{
buffer[--n] = cast(char)(v % 10 + '0');
v /= 10;
}
}
if (value < 0)
{
for (size_t i = negativeSign.length; i > 0;)
buffer[--n] = negativeSign[--i];
}
return buffer[n .. $];
}
/*******************************************************************************
*******************************************************************************/
char[] longToHexString (char[] buffer, ulong value, int digits, char format)
{
if (digits < 1)
digits = 1;
auto n = buffer.length;
while (--digits >= 0 || value != 0)
{
auto v = cast(uint) value & 0xF;
buffer[--n] = cast(char)((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;
auto n = buffer.length;
while (--digits >= 0 || value != 0)
{
buffer[--n] = cast(char)((value & 1) + '0');
value >>= 1;
}
return buffer[n .. $];
}
/*******************************************************************************
*******************************************************************************/
char parseFormatSpecifier (const(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 (char 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, const(char)[] format, NumberFormat nf)
{
int length;
auto specifier = parseFormatSpecifier (format, length);
switch (specifier)
{
case 'g':
case 'G':
if (length > 0)
break;
// Fall through.
goto case;
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, const(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)
// Bad dup?
return nf.nanSymbol.dup;
if (number.scale == INFINITY_FLAG)
// Bad dup?
return number.sign ? nf.negativeInfinitySymbol.dup
: nf.positiveInfinitySymbol.dup;
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.
goto default;
default:
break;
}
Number number = Number(value, precision);
if (number.scale == NAN_FLAG)
// Bad dup?
return nf.nanSymbol.dup;
if (number.scale == INFINITY_FLAG)
// Bad dup?
return number.sign ? nf.negativeInfinitySymbol.dup
: nf.positiveInfinitySymbol.dup;
if (specifier != char.init)
return toString (number, result, specifier, length, nf);
return number.toStringFormat (result, format, nf);
}
/*******************************************************************************
*******************************************************************************/
void formatGeneral (ref Number number, ref 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 (ref Number number, ref Result target, int length, NumberFormat nf)
{
const(char)[] format = number.sign ? negativeNumberFormats[nf.numberNegativePattern]
: positiveNumberFormat;
// Parse the format.
foreach (char 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 (ref Number number, ref Result target, int length, NumberFormat nf)
{
const(char)[] format = number.sign ? negativeCurrencyFormats[nf.currencyNegativePattern]
: positiveCurrencyFormats[nf.currencyPositivePattern];
// Parse the format.
foreach (char 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 (ref Number number, ref Result target, int length,
const(int)[] groupSizes, const(char)[] decimalSeparator, const(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;
const(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();
auto 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)
{
size_t 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 (ref Number number, ref 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 '".dup ~ format ~ "'}".dup;
}
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] = cast(char)(value % 10 + '0');
value /= 10;
}
int end = number.scale = -(n - cast(int)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)
{
enum 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,
];
enum 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 (ref Result result, const(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;
}
const(char)[] groupSeparator = nf.numberGroupSeparator;
const(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';
}
}
|