132
|
1 /*******************************************************************************
|
|
2
|
|
3 copyright: Copyright (c) 2005 John Chapman. All rights reserved
|
|
4
|
|
5 license: BSD style: $(LICENSE)
|
|
6
|
|
7 version: Initial release: 2005
|
|
8
|
|
9 author: John Chapman
|
|
10
|
|
11 ******************************************************************************/
|
|
12
|
|
13 module tango.text.locale.Convert;
|
|
14
|
|
15 private import tango.time.WallClock;
|
|
16
|
|
17 private import tango.core.Exception;
|
|
18
|
|
19 private import tango.text.locale.Core;
|
|
20
|
|
21 private import tango.time.chrono.Calendar;
|
|
22
|
|
23 private import Integer = tango.text.convert.Integer;
|
|
24
|
|
25 /******************************************************************************
|
|
26
|
|
27 ******************************************************************************/
|
|
28
|
|
29 private struct Result
|
|
30 {
|
|
31 private uint index;
|
|
32 private char[] target_;
|
|
33
|
|
34 /**********************************************************************
|
|
35
|
|
36 **********************************************************************/
|
|
37
|
|
38 private static Result opCall (char[] target)
|
|
39 {
|
|
40 Result result;
|
|
41
|
|
42 result.target_ = target;
|
|
43 return result;
|
|
44 }
|
|
45
|
|
46 /**********************************************************************
|
|
47
|
|
48 **********************************************************************/
|
|
49
|
|
50 private void opCatAssign (char[] rhs)
|
|
51 {
|
|
52 uint end = index + rhs.length;
|
|
53
|
|
54 target_[index .. end] = rhs;
|
|
55 index = end;
|
|
56 }
|
|
57
|
|
58 /**********************************************************************
|
|
59
|
|
60 **********************************************************************/
|
|
61
|
|
62 private void opCatAssign (char rhs)
|
|
63 {
|
|
64 target_[index++] = rhs;
|
|
65 }
|
|
66
|
|
67 /**********************************************************************
|
|
68
|
|
69 **********************************************************************/
|
|
70
|
|
71 private char[] get ()
|
|
72 {
|
|
73 return target_[0 .. index];
|
|
74 }
|
|
75
|
|
76 /**********************************************************************
|
|
77
|
|
78 **********************************************************************/
|
|
79
|
|
80 private char[] scratch ()
|
|
81 {
|
|
82 return target_;
|
|
83 }
|
|
84 }
|
|
85
|
|
86
|
|
87 /******************************************************************************
|
|
88
|
|
89 * Converts the value of this instance to its equivalent string representation using the specified _format and culture-specific formatting information.
|
|
90 * Params:
|
|
91 * format = A _format string.
|
|
92 * formatService = An IFormatService that provides culture-specific formatting information.
|
|
93 * Returns: A string representation of the value of this instance as specified by format and formatService.
|
|
94 * Remarks: See $(LINK2 datetimeformat.html, Time Formatting) for more information about date and time formatting.
|
|
95 * Examples:
|
|
96 * ---
|
|
97 * import tango.io.Print, tango.text.locale.Core, tango.time.WallClock;
|
|
98 *
|
|
99 * void main() {
|
|
100 * Culture culture = Culture.current;
|
|
101 * Time now = WallClock.now;
|
|
102 *
|
|
103 * Println("Current date and time: %s", now.toString());
|
|
104 * Println();
|
|
105 *
|
|
106 * // Format the current date and time in a number of ways.
|
|
107 * Println("Culture: %s", culture.englishName);
|
|
108 * Println();
|
|
109 *
|
|
110 * Println("Short date: %s", now.toString("d"));
|
|
111 * Println("Long date: %s", now.toString("D"));
|
|
112 * Println("Short time: %s", now.toString("t"));
|
|
113 * Println("Long time: %s", now.toString("T"));
|
|
114 * Println("General date short time: %s", now.toString("g"));
|
|
115 * Println("General date long time: %s", now.toString("G"));
|
|
116 * Println("Month: %s", now.toString("M"));
|
|
117 * Println("RFC1123: %s", now.toString("R"));
|
|
118 * Println("Sortable: %s", now.toString("s"));
|
|
119 * Println("Year: %s", now.toString("Y"));
|
|
120 * Println();
|
|
121 *
|
|
122 * // Display the same values using a different culture.
|
|
123 * culture = Culture.getCulture("fr-FR");
|
|
124 * Println("Culture: %s", culture.englishName);
|
|
125 * Println();
|
|
126 *
|
|
127 * Println("Short date: %s", now.toString("d", culture));
|
|
128 * Println("Long date: %s", now.toString("D", culture));
|
|
129 * Println("Short time: %s", now.toString("t", culture));
|
|
130 * Println("Long time: %s", now.toString("T", culture));
|
|
131 * Println("General date short time: %s", now.toString("g", culture));
|
|
132 * Println("General date long time: %s", now.toString("G", culture));
|
|
133 * Println("Month: %s", now.toString("M", culture));
|
|
134 * Println("RFC1123: %s", now.toString("R", culture));
|
|
135 * Println("Sortable: %s", now.toString("s", culture));
|
|
136 * Println("Year: %s", now.toString("Y", culture));
|
|
137 * Println();
|
|
138 * }
|
|
139 *
|
|
140 * // Produces the following output:
|
|
141 * // Current date and time: 26/05/2006 10:04:57 AM
|
|
142 * //
|
|
143 * // Culture: English (United Kingdom)
|
|
144 * //
|
|
145 * // Short date: 26/05/2006
|
|
146 * // Long date: 26 May 2006
|
|
147 * // Short time: 10:04
|
|
148 * // Long time: 10:04:57 AM
|
|
149 * // General date short time: 26/05/2006 10:04
|
|
150 * // General date long time: 26/05/2006 10:04:57 AM
|
|
151 * // Month: 26 May
|
|
152 * // RFC1123: Fri, 26 May 2006 10:04:57 GMT
|
|
153 * // Sortable: 2006-05-26T10:04:57
|
|
154 * // Year: May 2006
|
|
155 * //
|
|
156 * // Culture: French (France)
|
|
157 * //
|
|
158 * // Short date: 26/05/2006
|
|
159 * // Long date: vendredi 26 mai 2006
|
|
160 * // Short time: 10:04
|
|
161 * // Long time: 10:04:57
|
|
162 * // General date short time: 26/05/2006 10:04
|
|
163 * // General date long time: 26/05/2006 10:04:57
|
|
164 * // Month: 26 mai
|
|
165 * // RFC1123: ven., 26 mai 2006 10:04:57 GMT
|
|
166 * // Sortable: 2006-05-26T10:04:57
|
|
167 * // Year: mai 2006
|
|
168 * ---
|
|
169
|
|
170 ******************************************************************************/
|
|
171
|
|
172 public char[] formatDateTime (char[] output, Time dateTime, char[] format, IFormatService formatService = null)
|
|
173 {
|
|
174 return formatDateTime (output, dateTime, format, DateTimeFormat.getInstance(formatService));
|
|
175 }
|
|
176
|
|
177 char[] formatDateTime (char[] output, Time dateTime, char[] format, DateTimeFormat dtf)
|
|
178 {
|
|
179 /**********************************************************************
|
|
180
|
|
181 **********************************************************************/
|
|
182
|
|
183 char[] expandKnownFormat(char[] format, inout Time dateTime)
|
|
184 {
|
|
185 char[] f;
|
|
186
|
|
187 switch (format[0])
|
|
188 {
|
|
189 case 'd':
|
|
190 f = dtf.shortDatePattern;
|
|
191 break;
|
|
192 case 'D':
|
|
193 f = dtf.longDatePattern;
|
|
194 break;
|
|
195 case 'f':
|
|
196 f = dtf.longDatePattern ~ " " ~ dtf.shortTimePattern;
|
|
197 break;
|
|
198 case 'F':
|
|
199 f = dtf.fullDateTimePattern;
|
|
200 break;
|
|
201 case 'g':
|
|
202 f = dtf.generalShortTimePattern;
|
|
203 break;
|
|
204 case 'G':
|
|
205 f = dtf.generalLongTimePattern;
|
|
206 break;
|
|
207 case 'm':
|
|
208 case 'M':
|
|
209 f = dtf.monthDayPattern;
|
|
210 break;
|
|
211 case 'r':
|
|
212 case 'R':
|
|
213 f = dtf.rfc1123Pattern;
|
|
214 break;
|
|
215 case 's':
|
|
216 f = dtf.sortableDateTimePattern;
|
|
217 break;
|
|
218 case 't':
|
|
219 f = dtf.shortTimePattern;
|
|
220 break;
|
|
221 case 'T':
|
|
222 f = dtf.longTimePattern;
|
|
223 break;
|
|
224 version (Full)
|
|
225 {
|
|
226 case 'u':
|
|
227 dateTime = dateTime.toUniversalTime();
|
|
228 dtf = DateTimeFormat.invariantFormat;
|
|
229 f = dtf.universalSortableDateTimePattern;
|
|
230 break;
|
|
231 case 'U':
|
|
232 dtf = cast(DateTimeFormat) dtf.clone();
|
|
233 dateTime = dateTime.toUniversalTime();
|
|
234 if (typeid(typeof(dtf.calendar)) !is typeid(Gregorian))
|
|
235 dtf.calendar = Gregorian.generic;
|
|
236 f = dtf.fullDateTimePattern;
|
|
237 break;
|
|
238 }
|
|
239 case 'y':
|
|
240 case 'Y':
|
|
241 f = dtf.yearMonthPattern;
|
|
242 break;
|
|
243 default:
|
|
244 throw new IllegalArgumentException("Invalid date format.");
|
|
245 }
|
|
246
|
|
247 return f;
|
|
248 }
|
|
249
|
|
250 /**********************************************************************
|
|
251
|
|
252 **********************************************************************/
|
|
253
|
|
254 char[] formatCustom (inout Result result, Time dateTime, char[] format)
|
|
255 {
|
|
256
|
|
257 int parseRepeat(char[] format, int pos, char c)
|
|
258 {
|
|
259 int n = pos + 1;
|
|
260 while (n < format.length && format[n] is c)
|
|
261 n++;
|
|
262 return n - pos;
|
|
263 }
|
|
264
|
|
265 char[] formatDayOfWeek(Calendar.DayOfWeek dayOfWeek, int rpt)
|
|
266 {
|
|
267 if (rpt is 3)
|
|
268 return dtf.getAbbreviatedDayName(dayOfWeek);
|
|
269 return dtf.getDayName(dayOfWeek);
|
|
270 }
|
|
271
|
|
272 char[] formatMonth(int month, int rpt)
|
|
273 {
|
|
274 if (rpt is 3)
|
|
275 return dtf.getAbbreviatedMonthName(month);
|
|
276 return dtf.getMonthName(month);
|
|
277 }
|
|
278
|
|
279 char[] formatInt (char[] tmp, int v, int minimum)
|
|
280 {
|
|
281 auto num = Integer.format (tmp, v, Integer.Style.Unsigned);
|
|
282 if ((minimum -= num.length) > 0)
|
|
283 {
|
|
284 auto p = tmp.ptr + tmp.length - num.length;
|
|
285 while (minimum--)
|
|
286 *--p = '0';
|
|
287 num = tmp [p-tmp.ptr .. $];
|
|
288 }
|
|
289 return num;
|
|
290 }
|
|
291
|
|
292 int parseQuote(char[] format, int pos, out char[] result)
|
|
293 {
|
|
294 int start = pos;
|
|
295 char chQuote = format[pos++];
|
|
296 bool found;
|
|
297 while (pos < format.length)
|
|
298 {
|
|
299 char c = format[pos++];
|
|
300 if (c is chQuote)
|
|
301 {
|
|
302 found = true;
|
|
303 break;
|
|
304 }
|
|
305 else
|
|
306 if (c is '\\')
|
|
307 { // escaped
|
|
308 if (pos < format.length)
|
|
309 result ~= format[pos++];
|
|
310 }
|
|
311 else
|
|
312 result ~= c;
|
|
313 }
|
|
314 return pos - start;
|
|
315 }
|
|
316
|
|
317
|
|
318 Calendar calendar = dtf.calendar;
|
|
319 bool justTime = true;
|
|
320 int index, len;
|
|
321 char[10] tmp;
|
|
322
|
|
323 if (format[0] is '%')
|
|
324 {
|
|
325 // specifiers for both standard format strings and custom ones
|
|
326 const char[] commonSpecs = "dmMsty";
|
|
327 foreach (c; commonSpecs)
|
|
328 if (format[1] is c)
|
|
329 {
|
|
330 index += 1;
|
|
331 break;
|
|
332 }
|
|
333 }
|
|
334
|
|
335 while (index < format.length)
|
|
336 {
|
|
337 char c = format[index];
|
|
338 auto time = dateTime.time;
|
|
339
|
|
340 switch (c)
|
|
341 {
|
|
342 case 'd': // day
|
|
343 len = parseRepeat(format, index, c);
|
|
344 if (len <= 2)
|
|
345 {
|
|
346 int day = calendar.getDayOfMonth(dateTime);
|
|
347 result ~= formatInt (tmp, day, len);
|
|
348 }
|
|
349 else
|
|
350 result ~= formatDayOfWeek(calendar.getDayOfWeek(dateTime), len);
|
|
351 justTime = false;
|
|
352 break;
|
|
353
|
|
354 case 'M': // month
|
|
355 len = parseRepeat(format, index, c);
|
|
356 int month = calendar.getMonth(dateTime);
|
|
357 if (len <= 2)
|
|
358 result ~= formatInt (tmp, month, len);
|
|
359 else
|
|
360 result ~= formatMonth(month, len);
|
|
361 justTime = false;
|
|
362 break;
|
|
363 case 'y': // year
|
|
364 len = parseRepeat(format, index, c);
|
|
365 int year = calendar.getYear(dateTime);
|
|
366 // Two-digit years for Japanese
|
|
367 if (calendar.id is Calendar.JAPAN)
|
|
368 result ~= formatInt (tmp, year, 2);
|
|
369 else
|
|
370 {
|
|
371 if (len <= 2)
|
|
372 result ~= formatInt (tmp, year % 100, len);
|
|
373 else
|
|
374 result ~= formatInt (tmp, year, len);
|
|
375 }
|
|
376 justTime = false;
|
|
377 break;
|
|
378 case 'h': // hour (12-hour clock)
|
|
379 len = parseRepeat(format, index, c);
|
|
380 int hour = time.hours % 12;
|
|
381 if (hour is 0)
|
|
382 hour = 12;
|
|
383 result ~= formatInt (tmp, hour, len);
|
|
384 break;
|
|
385 case 'H': // hour (24-hour clock)
|
|
386 len = parseRepeat(format, index, c);
|
|
387 result ~= formatInt (tmp, time.hours, len);
|
|
388 break;
|
|
389 case 'm': // minute
|
|
390 len = parseRepeat(format, index, c);
|
|
391 result ~= formatInt (tmp, time.minutes, len);
|
|
392 break;
|
|
393 case 's': // second
|
|
394 len = parseRepeat(format, index, c);
|
|
395 result ~= formatInt (tmp, time.seconds, len);
|
|
396 break;
|
|
397 case 't': // AM/PM
|
|
398 len = parseRepeat(format, index, c);
|
|
399 if (len is 1)
|
|
400 {
|
|
401 if (time.hours < 12)
|
|
402 {
|
|
403 if (dtf.amDesignator.length != 0)
|
|
404 result ~= dtf.amDesignator[0];
|
|
405 }
|
|
406 else
|
|
407 {
|
|
408 if (dtf.pmDesignator.length != 0)
|
|
409 result ~= dtf.pmDesignator[0];
|
|
410 }
|
|
411 }
|
|
412 else
|
|
413 result ~= (time.hours < 12) ? dtf.amDesignator : dtf.pmDesignator;
|
|
414 break;
|
|
415 case 'z': // timezone offset
|
|
416 len = parseRepeat(format, index, c);
|
|
417 version (Full)
|
|
418 {
|
|
419 TimeSpan offset = (justTime && dateTime.ticks < TICKS_PER_DAY)
|
|
420 ? TimeZone.current.getUtcOffset(WallClock.now)
|
|
421 : TimeZone.current.getUtcOffset(dateTime);
|
|
422 int hours = offset.hours;
|
|
423 int minutes = offset.minutes;
|
|
424 result ~= (offset.backward) ? '-' : '+';
|
|
425 }
|
|
426 else
|
|
427 {
|
|
428 auto minutes = cast(int) (WallClock.zone.minutes);
|
|
429 if (minutes < 0)
|
|
430 minutes = -minutes, result ~= '-';
|
|
431 else
|
|
432 result ~= '+';
|
|
433 int hours = minutes / 60;
|
|
434 minutes %= 60;
|
|
435 }
|
|
436 if (len is 1)
|
|
437 result ~= formatInt (tmp, hours, 1);
|
|
438 else
|
|
439 if (len is 2)
|
|
440 result ~= formatInt (tmp, hours, 2);
|
|
441 else
|
|
442 {
|
|
443 result ~= formatInt (tmp, hours, 2);
|
|
444 result ~= ':';
|
|
445 result ~= formatInt (tmp, minutes, 2);
|
|
446 }
|
|
447 break;
|
|
448 case ':': // time separator
|
|
449 len = 1;
|
|
450 result ~= dtf.timeSeparator;
|
|
451 break;
|
|
452 case '/': // date separator
|
|
453 len = 1;
|
|
454 result ~= dtf.dateSeparator;
|
|
455 break;
|
|
456 case '\"': // string literal
|
|
457 case '\'': // char literal
|
|
458 char[] quote;
|
|
459 len = parseQuote(format, index, quote);
|
|
460 result ~= quote;
|
|
461 break;
|
|
462 default:
|
|
463 len = 1;
|
|
464 result ~= c;
|
|
465 break;
|
|
466 }
|
|
467 index += len;
|
|
468 }
|
|
469 return result.get;
|
|
470 }
|
|
471
|
|
472
|
|
473 auto result = Result (output);
|
|
474
|
|
475 if (format is null)
|
|
476 format = "G"; // Default to general format.
|
|
477
|
|
478 if (format.length is 1) // It might be one of our shortcuts.
|
|
479 format = expandKnownFormat (format, dateTime);
|
|
480
|
|
481 return formatCustom (result, dateTime, format);
|
|
482 }
|
|
483
|
|
484
|
|
485
|
|
486 /*******************************************************************************
|
|
487
|
|
488 *******************************************************************************/
|
|
489
|
|
490 private extern (C) private char* ecvt(double d, int digits, out int decpt, out bool sign);
|
|
491
|
|
492 /*******************************************************************************
|
|
493
|
|
494 *******************************************************************************/
|
|
495
|
|
496 // Must match NumberFormat.decimalPositivePattern
|
|
497 package const char[] positiveNumberFormat = "#";
|
|
498
|
|
499 // Must match NumberFormat.decimalNegativePattern
|
|
500 package const char[][] negativeNumberFormats =
|
|
501 [
|
|
502 "(#)", "-#", "- #", "#-", "# -"
|
|
503 ];
|
|
504
|
|
505 // Must match NumberFormat.currencyPositivePattern
|
|
506 package const char[][] positiveCurrencyFormats =
|
|
507 [
|
|
508 "$#", "#$", "$ #", "# $"
|
|
509 ];
|
|
510
|
|
511 // Must match NumberFormat.currencyNegativePattern
|
|
512 package const char[][] negativeCurrencyFormats =
|
|
513 [
|
|
514 "($#)", "-$#", "$-#", "$#-", "(#$)",
|
|
515 "-#$", "#-$", "#$-", "-# $", "-$ #",
|
|
516 "# $-", "$ #-", "$ -#", "#- $", "($ #)", "(# $)"
|
|
517 ];
|
|
518
|
|
519 /*******************************************************************************
|
|
520
|
|
521 *******************************************************************************/
|
|
522
|
|
523 package template charTerm (T)
|
|
524 {
|
|
525 package int charTerm(T* s)
|
|
526 {
|
|
527 int i;
|
|
528 while (*s++ != '\0')
|
|
529 i++;
|
|
530 return i;
|
|
531 }
|
|
532 }
|
|
533
|
|
534 /*******************************************************************************
|
|
535
|
|
536 *******************************************************************************/
|
|
537
|
|
538 char[] longToString (char[] buffer, long value, int digits, char[] negativeSign)
|
|
539 {
|
|
540 if (digits < 1)
|
|
541 digits = 1;
|
|
542
|
|
543 int n = buffer.length;
|
|
544 ulong uv = (value >= 0) ? value : cast(ulong) -value;
|
|
545
|
|
546 if (uv > uint.max)
|
|
547 {
|
|
548 while (--digits >= 0 || uv != 0)
|
|
549 {
|
|
550 buffer[--n] = uv % 10 + '0';
|
|
551 uv /= 10;
|
|
552 }
|
|
553 }
|
|
554 else
|
|
555 {
|
|
556 uint v = cast(uint) uv;
|
|
557 while (--digits >= 0 || v != 0)
|
|
558 {
|
|
559 buffer[--n] = v % 10 + '0';
|
|
560 v /= 10;
|
|
561 }
|
|
562 }
|
|
563
|
|
564
|
|
565 if (value < 0)
|
|
566 {
|
|
567 for (int i = negativeSign.length - 1; i >= 0; i--)
|
|
568 buffer[--n] = negativeSign[i];
|
|
569 }
|
|
570
|
|
571 return buffer[n .. $];
|
|
572 }
|
|
573
|
|
574 /*******************************************************************************
|
|
575
|
|
576 *******************************************************************************/
|
|
577
|
|
578 char[] longToHexString (char[] buffer, ulong value, int digits, char format)
|
|
579 {
|
|
580 if (digits < 1)
|
|
581 digits = 1;
|
|
582
|
|
583 int n = buffer.length;
|
|
584 while (--digits >= 0 || value != 0)
|
|
585 {
|
|
586 auto v = cast(uint) value & 0xF;
|
|
587 buffer[--n] = (v < 10) ? v + '0' : v + format - ('X' - 'A' + 10);
|
|
588 value >>= 4;
|
|
589 }
|
|
590
|
|
591 return buffer[n .. $];
|
|
592 }
|
|
593
|
|
594 /*******************************************************************************
|
|
595
|
|
596 *******************************************************************************/
|
|
597
|
|
598 char[] longToBinString (char[] buffer, ulong value, int digits)
|
|
599 {
|
|
600 if (digits < 1)
|
|
601 digits = 1;
|
|
602
|
|
603 int n = buffer.length;
|
|
604 while (--digits >= 0 || value != 0)
|
|
605 {
|
|
606 buffer[--n] = (value & 1) + '0';
|
|
607 value >>= 1;
|
|
608 }
|
|
609
|
|
610 return buffer[n .. $];
|
|
611 }
|
|
612
|
|
613 /*******************************************************************************
|
|
614
|
|
615 *******************************************************************************/
|
|
616
|
|
617 char parseFormatSpecifier (char[] format, out int length)
|
|
618 {
|
|
619 int i = -1;
|
|
620 char specifier;
|
|
621
|
|
622 if (format.length)
|
|
623 {
|
|
624 auto s = format[0];
|
|
625
|
|
626 if (s >= 'A' && s <= 'Z' || s >= 'a' && s <= 'z')
|
|
627 {
|
|
628 specifier = s;
|
|
629
|
|
630 foreach (c; format [1..$])
|
|
631 if (c >= '0' && c <= '9')
|
|
632 {
|
|
633 c -= '0';
|
|
634 if (i < 0)
|
|
635 i = c;
|
|
636 else
|
|
637 i = i * 10 + c;
|
|
638 }
|
|
639 else
|
|
640 break;
|
|
641 }
|
|
642 }
|
|
643 else
|
|
644 specifier = 'G';
|
|
645
|
|
646 length = i;
|
|
647 return specifier;
|
|
648 }
|
|
649
|
|
650 /*******************************************************************************
|
|
651
|
|
652 *******************************************************************************/
|
|
653
|
|
654 char[] formatInteger (char[] output, long value, char[] format, NumberFormat nf)
|
|
655 {
|
|
656 int length;
|
|
657 auto specifier = parseFormatSpecifier (format, length);
|
|
658
|
|
659 switch (specifier)
|
|
660 {
|
|
661 case 'g':
|
|
662 case 'G':
|
|
663 if (length > 0)
|
|
664 break;
|
|
665 // Fall through.
|
|
666
|
|
667 case 'd':
|
|
668 case 'D':
|
|
669 return longToString (output, value, length, nf.negativeSign);
|
|
670
|
|
671 case 'x':
|
|
672 case 'X':
|
|
673 return longToHexString (output, cast(ulong)value, length, specifier);
|
|
674
|
|
675 case 'b':
|
|
676 case 'B':
|
|
677 return longToBinString (output, cast(ulong)value, length);
|
|
678
|
|
679 default:
|
|
680 break;
|
|
681 }
|
|
682
|
|
683 Result result = Result (output);
|
|
684 Number number = Number (value);
|
|
685 if (specifier != char.init)
|
|
686 return toString (number, result, specifier, length, nf);
|
|
687
|
|
688 return number.toStringFormat (result, format, nf);
|
|
689 }
|
|
690
|
|
691 /*******************************************************************************
|
|
692
|
|
693 *******************************************************************************/
|
|
694
|
|
695 private enum {
|
|
696 EXP = 0x7ff,
|
|
697 NAN_FLAG = 0x80000000,
|
|
698 INFINITY_FLAG = 0x7fffffff,
|
|
699 }
|
|
700
|
|
701 char[] formatDouble (char[] output, double value, char[] format, NumberFormat nf)
|
|
702 {
|
|
703 int length;
|
|
704 int precision = 6;
|
|
705 Result result = Result (output);
|
|
706 char specifier = parseFormatSpecifier (format, length);
|
|
707
|
|
708 switch (specifier)
|
|
709 {
|
|
710 case 'r':
|
|
711 case 'R':
|
|
712 Number number = Number (value, 15);
|
|
713
|
|
714 if (number.scale == NAN_FLAG)
|
|
715 return nf.nanSymbol;
|
|
716
|
|
717 if (number.scale == INFINITY_FLAG)
|
|
718 return number.sign ? nf.negativeInfinitySymbol
|
|
719 : nf.positiveInfinitySymbol;
|
|
720
|
|
721 double d;
|
|
722 number.toDouble(d);
|
|
723 if (d == value)
|
|
724 return toString (number, result, 'G', 15, nf);
|
|
725
|
|
726 number = Number(value, 17);
|
|
727 return toString (number, result, 'G', 17, nf);
|
|
728
|
|
729 case 'g':
|
|
730 case 'G':
|
|
731 if (length > 15)
|
|
732 precision = 17;
|
|
733 // Fall through.
|
|
734
|
|
735 default:
|
|
736 break;
|
|
737 }
|
|
738
|
|
739 Number number = Number(value, precision);
|
|
740
|
|
741 if (number.scale == NAN_FLAG)
|
|
742 return nf.nanSymbol;
|
|
743
|
|
744 if (number.scale == INFINITY_FLAG)
|
|
745 return number.sign ? nf.negativeInfinitySymbol
|
|
746 : nf.positiveInfinitySymbol;
|
|
747
|
|
748 if (specifier != char.init)
|
|
749 return toString (number, result, specifier, length, nf);
|
|
750
|
|
751 return number.toStringFormat (result, format, nf);
|
|
752 }
|
|
753
|
|
754 /*******************************************************************************
|
|
755
|
|
756 *******************************************************************************/
|
|
757
|
|
758 void formatGeneral (inout Number number, inout Result target, int length, char format, NumberFormat nf)
|
|
759 {
|
|
760 int pos = number.scale;
|
|
761
|
|
762 auto p = number.digits.ptr;
|
|
763 if (pos > 0)
|
|
764 {
|
|
765 while (pos > 0)
|
|
766 {
|
|
767 target ~= (*p != '\0') ? *p++ : '0';
|
|
768 pos--;
|
|
769 }
|
|
770 }
|
|
771 else
|
|
772 target ~= '0';
|
|
773
|
|
774 if (*p != '\0')
|
|
775 {
|
|
776 target ~= nf.numberDecimalSeparator;
|
|
777 while (pos < 0)
|
|
778 {
|
|
779 target ~= '0';
|
|
780 pos++;
|
|
781 }
|
|
782
|
|
783 while (*p != '\0')
|
|
784 target ~= *p++;
|
|
785 }
|
|
786 }
|
|
787
|
|
788 /*******************************************************************************
|
|
789
|
|
790 *******************************************************************************/
|
|
791
|
|
792 void formatNumber (inout Number number, inout Result target, int length, NumberFormat nf)
|
|
793 {
|
|
794 char[] format = number.sign ? negativeNumberFormats[nf.numberNegativePattern]
|
|
795 : positiveNumberFormat;
|
|
796
|
|
797 // Parse the format.
|
|
798 foreach (c; format)
|
|
799 {
|
|
800 switch (c)
|
|
801 {
|
|
802 case '#':
|
|
803 formatFixed (number, target, length, nf.numberGroupSizes,
|
|
804 nf.numberDecimalSeparator, nf.numberGroupSeparator);
|
|
805 break;
|
|
806
|
|
807 case '-':
|
|
808 target ~= nf.negativeSign;
|
|
809 break;
|
|
810
|
|
811 default:
|
|
812 target ~= c;
|
|
813 break;
|
|
814 }
|
|
815 }
|
|
816 }
|
|
817
|
|
818 /*******************************************************************************
|
|
819
|
|
820 *******************************************************************************/
|
|
821
|
|
822 void formatCurrency (inout Number number, inout Result target, int length, NumberFormat nf)
|
|
823 {
|
|
824 char[] format = number.sign ? negativeCurrencyFormats[nf.currencyNegativePattern]
|
|
825 : positiveCurrencyFormats[nf.currencyPositivePattern];
|
|
826
|
|
827 // Parse the format.
|
|
828 foreach (c; format)
|
|
829 {
|
|
830 switch (c)
|
|
831 {
|
|
832 case '#':
|
|
833 formatFixed (number, target, length, nf.currencyGroupSizes,
|
|
834 nf.currencyDecimalSeparator, nf.currencyGroupSeparator);
|
|
835 break;
|
|
836
|
|
837 case '-':
|
|
838 target ~= nf.negativeSign;
|
|
839 break;
|
|
840
|
|
841 case '$':
|
|
842 target ~= nf.currencySymbol;
|
|
843 break;
|
|
844
|
|
845 default:
|
|
846 target ~= c;
|
|
847 break;
|
|
848 }
|
|
849 }
|
|
850 }
|
|
851
|
|
852 /*******************************************************************************
|
|
853
|
|
854 *******************************************************************************/
|
|
855
|
|
856 void formatFixed (inout Number number, inout Result target, int length,
|
|
857 int[] groupSizes, char[] decimalSeparator, char[] groupSeparator)
|
|
858 {
|
|
859 int pos = number.scale;
|
|
860 auto p = number.digits.ptr;
|
|
861
|
|
862 if (pos > 0)
|
|
863 {
|
|
864 if (groupSizes.length != 0)
|
|
865 {
|
|
866 // Calculate whether we have enough digits to format.
|
|
867 int count = groupSizes[0];
|
|
868 int index, size;
|
|
869
|
|
870 while (pos > count)
|
|
871 {
|
|
872 size = groupSizes[index];
|
|
873 if (size == 0)
|
|
874 break;
|
|
875
|
|
876 if (index < groupSizes.length - 1)
|
|
877 index++;
|
|
878
|
|
879 count += groupSizes[index];
|
|
880 }
|
|
881
|
|
882 size = (count == 0) ? 0 : groupSizes[0];
|
|
883
|
|
884 // Insert the separator according to groupSizes.
|
|
885 int end = charTerm(p);
|
|
886 int start = (pos < end) ? pos : end;
|
|
887
|
|
888
|
|
889 char[] separator = groupSeparator;
|
|
890 index = 0;
|
|
891
|
|
892 // questionable: use the back end of the output buffer to
|
|
893 // format the separators, and then copy back to start
|
|
894 char[] temp = target.scratch;
|
|
895 uint ii = temp.length;
|
|
896
|
|
897 for (int c, i = pos - 1; i >= 0; i--)
|
|
898 {
|
|
899 temp[--ii] = (i < start) ? number.digits[i] : '0';
|
|
900 if (size > 0)
|
|
901 {
|
|
902 c++;
|
|
903 if (c == size && i != 0)
|
|
904 {
|
|
905 uint iii = ii - separator.length;
|
|
906 temp[iii .. ii] = separator;
|
|
907 ii = iii;
|
|
908
|
|
909 if (index < groupSizes.length - 1)
|
|
910 size = groupSizes[++index];
|
|
911
|
|
912 c = 0;
|
|
913 }
|
|
914 }
|
|
915 }
|
|
916 target ~= temp[ii..$];
|
|
917 p += start;
|
|
918 }
|
|
919 else
|
|
920 {
|
|
921 while (pos > 0)
|
|
922 {
|
|
923 target ~= (*p != '\0') ? *p++ : '0';
|
|
924 pos--;
|
|
925 }
|
|
926 }
|
|
927 }
|
|
928 else
|
|
929 // Negative scale.
|
|
930 target ~= '0';
|
|
931
|
|
932 if (length > 0)
|
|
933 {
|
|
934 target ~= decimalSeparator;
|
|
935 while (pos < 0 && length > 0)
|
|
936 {
|
|
937 target ~= '0';
|
|
938 pos++;
|
|
939 length--;
|
|
940 }
|
|
941
|
|
942 while (length > 0)
|
|
943 {
|
|
944 target ~= (*p != '\0') ? *p++ : '0';
|
|
945 length--;
|
|
946 }
|
|
947 }
|
|
948 }
|
|
949
|
|
950 /******************************************************************************
|
|
951
|
|
952 ******************************************************************************/
|
|
953
|
|
954 char[] toString (inout Number number, inout Result result, char format, int length, NumberFormat nf)
|
|
955 {
|
|
956 switch (format)
|
|
957 {
|
|
958 case 'c':
|
|
959 case 'C':
|
|
960 // Currency
|
|
961 if (length < 0)
|
|
962 length = nf.currencyDecimalDigits;
|
|
963
|
|
964 number.round(number.scale + length);
|
|
965 formatCurrency (number, result, length, nf);
|
|
966 break;
|
|
967
|
|
968 case 'f':
|
|
969 case 'F':
|
|
970 // Fixed
|
|
971 if (length < 0)
|
|
972 length = nf.numberDecimalDigits;
|
|
973
|
|
974 number.round(number.scale + length);
|
|
975 if (number.sign)
|
|
976 result ~= nf.negativeSign;
|
|
977
|
|
978 formatFixed (number, result, length, null, nf.numberDecimalSeparator, null);
|
|
979 break;
|
|
980
|
|
981 case 'n':
|
|
982 case 'N':
|
|
983 // Number
|
|
984 if (length < 0)
|
|
985 length = nf.numberDecimalDigits;
|
|
986
|
|
987 number.round (number.scale + length);
|
|
988 formatNumber (number, result, length, nf);
|
|
989 break;
|
|
990
|
|
991 case 'g':
|
|
992 case 'G':
|
|
993 // General
|
|
994 if (length < 1)
|
|
995 length = number.precision;
|
|
996
|
|
997 number.round(length);
|
|
998 if (number.sign)
|
|
999 result ~= nf.negativeSign;
|
|
1000
|
|
1001 formatGeneral (number, result, length, (format == 'g') ? 'e' : 'E', nf);
|
|
1002 break;
|
|
1003
|
|
1004 default:
|
|
1005 return "{invalid FP format specifier '" ~ format ~ "'}";
|
|
1006 }
|
|
1007 return result.get;
|
|
1008 }
|
|
1009
|
|
1010
|
|
1011 /*******************************************************************************
|
|
1012
|
|
1013 *******************************************************************************/
|
|
1014
|
|
1015 private struct Number
|
|
1016 {
|
|
1017 int scale;
|
|
1018 bool sign;
|
|
1019 int precision;
|
|
1020 char[32] digits = void;
|
|
1021
|
|
1022 /**********************************************************************
|
|
1023
|
|
1024 **********************************************************************/
|
|
1025
|
|
1026 private static Number opCall (long value)
|
|
1027 {
|
|
1028 Number number;
|
|
1029 number.precision = 20;
|
|
1030
|
|
1031 if (value < 0)
|
|
1032 {
|
|
1033 number.sign = true;
|
|
1034 value = -value;
|
|
1035 }
|
|
1036
|
|
1037 char[20] buffer = void;
|
|
1038 int n = buffer.length;
|
|
1039
|
|
1040 while (value != 0)
|
|
1041 {
|
|
1042 buffer[--n] = value % 10 + '0';
|
|
1043 value /= 10;
|
|
1044 }
|
|
1045
|
|
1046 int end = number.scale = -(n - buffer.length);
|
|
1047 number.digits[0 .. end] = buffer[n .. n + end];
|
|
1048 number.digits[end] = '\0';
|
|
1049
|
|
1050 return number;
|
|
1051 }
|
|
1052
|
|
1053 /**********************************************************************
|
|
1054
|
|
1055 **********************************************************************/
|
|
1056
|
|
1057 private static Number opCall (double value, int precision)
|
|
1058 {
|
|
1059 Number number;
|
|
1060 number.precision = precision;
|
|
1061
|
|
1062 auto p = number.digits.ptr;
|
|
1063 long bits = *cast(long*) & value;
|
|
1064 long mant = bits & 0x000FFFFFFFFFFFFFL;
|
|
1065 int exp = cast(int)((bits >> 52) & EXP);
|
|
1066
|
|
1067 if (exp == EXP)
|
|
1068 {
|
|
1069 number.scale = (mant != 0) ? NAN_FLAG : INFINITY_FLAG;
|
|
1070 if (((bits >> 63) & 1) != 0)
|
|
1071 number.sign = true;
|
|
1072 }
|
|
1073 else
|
|
1074 {
|
|
1075 // Get the digits, decimal point and sign.
|
|
1076 char* chars = ecvt(value, number.precision, number.scale, number.sign);
|
|
1077 if (*chars != '\0')
|
|
1078 {
|
|
1079 while (*chars != '\0')
|
|
1080 *p++ = *chars++;
|
|
1081 }
|
|
1082 }
|
|
1083
|
|
1084 *p = '\0';
|
|
1085 return number;
|
|
1086 }
|
|
1087
|
|
1088 /**********************************************************************
|
|
1089
|
|
1090 **********************************************************************/
|
|
1091
|
|
1092 private bool toDouble(out double value)
|
|
1093 {
|
|
1094 const ulong[] pow10 =
|
|
1095 [
|
|
1096 0xa000000000000000UL,
|
|
1097 0xc800000000000000UL,
|
|
1098 0xfa00000000000000UL,
|
|
1099 0x9c40000000000000UL,
|
|
1100 0xc350000000000000UL,
|
|
1101 0xf424000000000000UL,
|
|
1102 0x9896800000000000UL,
|
|
1103 0xbebc200000000000UL,
|
|
1104 0xee6b280000000000UL,
|
|
1105 0x9502f90000000000UL,
|
|
1106 0xba43b74000000000UL,
|
|
1107 0xe8d4a51000000000UL,
|
|
1108 0x9184e72a00000000UL,
|
|
1109 0xb5e620f480000000UL,
|
|
1110 0xe35fa931a0000000UL,
|
|
1111 0xcccccccccccccccdUL,
|
|
1112 0xa3d70a3d70a3d70bUL,
|
|
1113 0x83126e978d4fdf3cUL,
|
|
1114 0xd1b71758e219652eUL,
|
|
1115 0xa7c5ac471b478425UL,
|
|
1116 0x8637bd05af6c69b7UL,
|
|
1117 0xd6bf94d5e57a42beUL,
|
|
1118 0xabcc77118461ceffUL,
|
|
1119 0x89705f4136b4a599UL,
|
|
1120 0xdbe6fecebdedd5c2UL,
|
|
1121 0xafebff0bcb24ab02UL,
|
|
1122 0x8cbccc096f5088cfUL,
|
|
1123 0xe12e13424bb40e18UL,
|
|
1124 0xb424dc35095cd813UL,
|
|
1125 0x901d7cf73ab0acdcUL,
|
|
1126 0x8e1bc9bf04000000UL,
|
|
1127 0x9dc5ada82b70b59eUL,
|
|
1128 0xaf298d050e4395d6UL,
|
|
1129 0xc2781f49ffcfa6d4UL,
|
|
1130 0xd7e77a8f87daf7faUL,
|
|
1131 0xefb3ab16c59b14a0UL,
|
|
1132 0x850fadc09923329cUL,
|
|
1133 0x93ba47c980e98cdeUL,
|
|
1134 0xa402b9c5a8d3a6e6UL,
|
|
1135 0xb616a12b7fe617a8UL,
|
|
1136 0xca28a291859bbf90UL,
|
|
1137 0xe070f78d39275566UL,
|
|
1138 0xf92e0c3537826140UL,
|
|
1139 0x8a5296ffe33cc92cUL,
|
|
1140 0x9991a6f3d6bf1762UL,
|
|
1141 0xaa7eebfb9df9de8aUL,
|
|
1142 0xbd49d14aa79dbc7eUL,
|
|
1143 0xd226fc195c6a2f88UL,
|
|
1144 0xe950df20247c83f8UL,
|
|
1145 0x81842f29f2cce373UL,
|
|
1146 0x8fcac257558ee4e2UL,
|
|
1147 ];
|
|
1148
|
|
1149 const uint[] pow10Exp =
|
|
1150 [
|
|
1151 4, 7, 10, 14, 17, 20, 24, 27, 30, 34,
|
|
1152 37, 40, 44, 47, 50, 54, 107, 160, 213, 266,
|
|
1153 319, 373, 426, 479, 532, 585, 638, 691, 745, 798,
|
|
1154 851, 904, 957, 1010, 1064, 1117
|
|
1155 ];
|
|
1156
|
|
1157 uint getDigits(char* p, int len)
|
|
1158 {
|
|
1159 char* end = p + len;
|
|
1160 uint r = *p - '0';
|
|
1161 p++;
|
|
1162 while (p < end)
|
|
1163 {
|
|
1164 r = 10 * r + *p - '0';
|
|
1165 p++;
|
|
1166 }
|
|
1167 return r;
|
|
1168 }
|
|
1169
|
|
1170 ulong mult64(uint val1, uint val2)
|
|
1171 {
|
|
1172 return cast(ulong)val1 * cast(ulong)val2;
|
|
1173 }
|
|
1174
|
|
1175 ulong mult64L(ulong val1, ulong val2)
|
|
1176 {
|
|
1177 ulong v = mult64(cast(uint)(val1 >> 32), cast(uint)(val2 >> 32));
|
|
1178 v += mult64(cast(uint)(val1 >> 32), cast(uint)val2) >> 32;
|
|
1179 v += mult64(cast(uint)val1, cast(uint)(val2 >> 32)) >> 32;
|
|
1180 return v;
|
|
1181 }
|
|
1182
|
|
1183 auto p = digits.ptr;
|
|
1184 int count = charTerm(p);
|
|
1185 int left = count;
|
|
1186
|
|
1187 while (*p == '0')
|
|
1188 {
|
|
1189 left--;
|
|
1190 p++;
|
|
1191 }
|
|
1192
|
|
1193 // If the digits consist of nothing but zeros...
|
|
1194 if (left == 0)
|
|
1195 {
|
|
1196 value = 0.0;
|
|
1197 return true;
|
|
1198 }
|
|
1199
|
|
1200 // Get digits, 9 at a time.
|
|
1201 int n = (left > 9) ? 9 : left;
|
|
1202 left -= n;
|
|
1203 ulong bits = getDigits(p, n);
|
|
1204 if (left > 0)
|
|
1205 {
|
|
1206 n = (left > 9) ? 9 : left;
|
|
1207 left -= n;
|
|
1208 bits = mult64(cast(uint)bits, cast(uint)(pow10[n - 1] >>> (64 - pow10Exp[n - 1])));
|
|
1209 bits += getDigits(p + 9, n);
|
|
1210 }
|
|
1211
|
|
1212 int scale = this.scale - (count - left);
|
|
1213 int s = (scale < 0) ? -scale : scale;
|
|
1214
|
|
1215 if (s >= 352)
|
|
1216 {
|
|
1217 *cast(long*)&value = (scale > 0) ? 0x7FF0000000000000 : 0;
|
|
1218 return false;
|
|
1219 }
|
|
1220
|
|
1221 // Normalise mantissa and bits.
|
|
1222 int bexp = 64;
|
|
1223 int nzero;
|
|
1224 if ((bits >> 32) != 0)
|
|
1225 nzero = 32;
|
|
1226
|
|
1227 if ((bits >> (16 + nzero)) != 0)
|
|
1228 nzero += 16;
|
|
1229
|
|
1230 if ((bits >> (8 + nzero)) != 0)
|
|
1231 nzero += 8;
|
|
1232
|
|
1233 if ((bits >> (4 + nzero)) != 0)
|
|
1234 nzero += 4;
|
|
1235
|
|
1236 if ((bits >> (2 + nzero)) != 0)
|
|
1237 nzero += 2;
|
|
1238
|
|
1239 if ((bits >> (1 + nzero)) != 0)
|
|
1240 nzero++;
|
|
1241
|
|
1242 if ((bits >> nzero) != 0)
|
|
1243 nzero++;
|
|
1244
|
|
1245 bits <<= 64 - nzero;
|
|
1246 bexp -= 64 - nzero;
|
|
1247
|
|
1248 // Get decimal exponent.
|
|
1249 if ((s & 15) != 0)
|
|
1250 {
|
|
1251 int expMult = pow10Exp[(s & 15) - 1];
|
|
1252 bexp += (scale < 0) ? ( -expMult + 1) : expMult;
|
|
1253 bits = mult64L(bits, pow10[(s & 15) + ((scale < 0) ? 15 : 0) - 1]);
|
|
1254 if ((bits & 0x8000000000000000L) == 0)
|
|
1255 {
|
|
1256 bits <<= 1;
|
|
1257 bexp--;
|
|
1258 }
|
|
1259 }
|
|
1260
|
|
1261 if ((s >> 4) != 0)
|
|
1262 {
|
|
1263 int expMult = pow10Exp[15 + ((s >> 4) - 1)];
|
|
1264 bexp += (scale < 0) ? ( -expMult + 1) : expMult;
|
|
1265 bits = mult64L(bits, pow10[30 + ((s >> 4) + ((scale < 0) ? 21 : 0) - 1)]);
|
|
1266 if ((bits & 0x8000000000000000L) == 0)
|
|
1267 {
|
|
1268 bits <<= 1;
|
|
1269 bexp--;
|
|
1270 }
|
|
1271 }
|
|
1272
|
|
1273 // Round and scale.
|
|
1274 if (cast(uint)bits & (1 << 10) != 0)
|
|
1275 {
|
|
1276 bits += (1 << 10) - 1 + (bits >>> 11) & 1;
|
|
1277 bits >>= 11;
|
|
1278 if (bits == 0)
|
|
1279 bexp++;
|
|
1280 }
|
|
1281 else
|
|
1282 bits >>= 11;
|
|
1283
|
|
1284 bexp += 1022;
|
|
1285 if (bexp <= 0)
|
|
1286 {
|
|
1287 if (bexp < -53)
|
|
1288 bits = 0;
|
|
1289 else
|
|
1290 bits >>= ( -bexp + 1);
|
|
1291 }
|
|
1292 bits = (cast(ulong)bexp << 52) + (bits & 0x000FFFFFFFFFFFFFL);
|
|
1293
|
|
1294 if (sign)
|
|
1295 bits |= 0x8000000000000000L;
|
|
1296
|
|
1297 value = *cast(double*) & bits;
|
|
1298 return true;
|
|
1299 }
|
|
1300
|
|
1301
|
|
1302
|
|
1303 /**********************************************************************
|
|
1304
|
|
1305 **********************************************************************/
|
|
1306
|
|
1307 private char[] toStringFormat (inout Result result, char[] format, NumberFormat nf)
|
|
1308 {
|
|
1309 bool hasGroups;
|
|
1310 int groupCount;
|
|
1311 int groupPos = -1, pointPos = -1;
|
|
1312 int first = int.max, last, count;
|
|
1313 bool scientific;
|
|
1314 int n;
|
|
1315 char c;
|
|
1316
|
|
1317 while (n < format.length)
|
|
1318 {
|
|
1319 c = format[n++];
|
|
1320 switch (c)
|
|
1321 {
|
|
1322 case '#':
|
|
1323 count++;
|
|
1324 break;
|
|
1325
|
|
1326 case '0':
|
|
1327 if (first == int.max)
|
|
1328 first = count;
|
|
1329 count++;
|
|
1330 last = count;
|
|
1331 break;
|
|
1332
|
|
1333 case '.':
|
|
1334 if (pointPos < 0)
|
|
1335 pointPos = count;
|
|
1336 break;
|
|
1337
|
|
1338 case ',':
|
|
1339 if (count > 0 && pointPos < 0)
|
|
1340 {
|
|
1341 if (groupPos >= 0)
|
|
1342 {
|
|
1343 if (groupPos == count)
|
|
1344 {
|
|
1345 groupCount++;
|
|
1346 break;
|
|
1347 }
|
|
1348 hasGroups = true;
|
|
1349 }
|
|
1350 groupPos = count;
|
|
1351 groupCount = 1;
|
|
1352 }
|
|
1353 break;
|
|
1354
|
|
1355 case '\'':
|
|
1356 case '\"':
|
|
1357 while (n < format.length && format[n++] != c)
|
|
1358 {}
|
|
1359 break;
|
|
1360
|
|
1361 case '\\':
|
|
1362 if (n < format.length)
|
|
1363 n++;
|
|
1364 break;
|
|
1365
|
|
1366 default:
|
|
1367 break;
|
|
1368 }
|
|
1369 }
|
|
1370
|
|
1371 if (pointPos < 0)
|
|
1372 pointPos = count;
|
|
1373
|
|
1374 int adjust;
|
|
1375 if (groupPos >= 0)
|
|
1376 {
|
|
1377 if (groupPos == pointPos)
|
|
1378 adjust -= groupCount * 3;
|
|
1379 else
|
|
1380 hasGroups = true;
|
|
1381 }
|
|
1382
|
|
1383 if (digits[0] != '\0')
|
|
1384 {
|
|
1385 scale += adjust;
|
|
1386 round(scientific ? count : scale + count - pointPos);
|
|
1387 }
|
|
1388
|
|
1389 first = (first < pointPos) ? pointPos - first : 0;
|
|
1390 last = (last > pointPos) ? pointPos - last : 0;
|
|
1391
|
|
1392 int pos = pointPos;
|
|
1393 int extra;
|
|
1394 if (!scientific)
|
|
1395 {
|
|
1396 pos = (scale > pointPos) ? scale : pointPos;
|
|
1397 extra = scale - pointPos;
|
|
1398 }
|
|
1399
|
|
1400 char[] groupSeparator = nf.numberGroupSeparator;
|
|
1401 char[] decimalSeparator = nf.numberDecimalSeparator;
|
|
1402
|
|
1403 // Work out the positions of the group separator.
|
|
1404 int[] groupPositions;
|
|
1405 int groupIndex = -1;
|
|
1406 if (hasGroups)
|
|
1407 {
|
|
1408 if (nf.numberGroupSizes.length == 0)
|
|
1409 hasGroups = false;
|
|
1410 else
|
|
1411 {
|
|
1412 int groupSizesTotal = nf.numberGroupSizes[0];
|
|
1413 int groupSize = groupSizesTotal;
|
|
1414 int digitsTotal = pos + ((extra < 0) ? extra : 0);
|
|
1415 int digitCount = (first > digitsTotal) ? first : digitsTotal;
|
|
1416
|
|
1417 int sizeIndex;
|
|
1418 while (digitCount > groupSizesTotal)
|
|
1419 {
|
|
1420 if (groupSize == 0)
|
|
1421 break;
|
|
1422
|
|
1423 groupPositions ~= groupSizesTotal;
|
|
1424 groupIndex++;
|
|
1425
|
|
1426 if (sizeIndex < nf.numberGroupSizes.length - 1)
|
|
1427 groupSize = nf.numberGroupSizes[++sizeIndex];
|
|
1428
|
|
1429 groupSizesTotal += groupSize;
|
|
1430 }
|
|
1431 }
|
|
1432 }
|
|
1433
|
|
1434 //char[] result;
|
|
1435 if (sign)
|
|
1436 result ~= nf.negativeSign;
|
|
1437
|
|
1438 auto p = digits.ptr;
|
|
1439 n = 0;
|
|
1440 bool pointWritten;
|
|
1441
|
|
1442 while (n < format.length)
|
|
1443 {
|
|
1444 c = format[n++];
|
|
1445 if (extra > 0 && (c == '#' || c == '0' || c == '.'))
|
|
1446 {
|
|
1447 while (extra > 0)
|
|
1448 {
|
|
1449 result ~= (*p != '\0') ? *p++ : '0';
|
|
1450
|
|
1451 if (hasGroups && pos > 1 && groupIndex >= 0)
|
|
1452 {
|
|
1453 if (pos == groupPositions[groupIndex] + 1)
|
|
1454 {
|
|
1455 result ~= groupSeparator;
|
|
1456 groupIndex--;
|
|
1457 }
|
|
1458 }
|
|
1459 pos--;
|
|
1460 extra--;
|
|
1461 }
|
|
1462 }
|
|
1463
|
|
1464 switch (c)
|
|
1465 {
|
|
1466 case '#':
|
|
1467 case '0':
|
|
1468 if (extra < 0)
|
|
1469 {
|
|
1470 extra++;
|
|
1471 c = (pos <= first) ? '0' : char.init;
|
|
1472 }
|
|
1473 else
|
|
1474 c = (*p != '\0') ? *p++ : pos > last ? '0' : char.init;
|
|
1475
|
|
1476 if (c != char.init)
|
|
1477 {
|
|
1478 result ~= c;
|
|
1479
|
|
1480 if (hasGroups && pos > 1 && groupIndex >= 0)
|
|
1481 {
|
|
1482 if (pos == groupPositions[groupIndex] + 1)
|
|
1483 {
|
|
1484 result ~= groupSeparator;
|
|
1485 groupIndex--;
|
|
1486 }
|
|
1487 }
|
|
1488 }
|
|
1489 pos--;
|
|
1490 break;
|
|
1491
|
|
1492 case '.':
|
|
1493 if (pos != 0 || pointWritten)
|
|
1494 break;
|
|
1495 if (last < 0 || (pointPos < count && *p != '\0'))
|
|
1496 {
|
|
1497 result ~= decimalSeparator;
|
|
1498 pointWritten = true;
|
|
1499 }
|
|
1500 break;
|
|
1501
|
|
1502 case ',':
|
|
1503 break;
|
|
1504
|
|
1505 case '\'':
|
|
1506 case '\"':
|
|
1507 if (n < format.length)
|
|
1508 n++;
|
|
1509 break;
|
|
1510
|
|
1511 case '\\':
|
|
1512 if (n < format.length)
|
|
1513 result ~= format[n++];
|
|
1514 break;
|
|
1515
|
|
1516 default:
|
|
1517 result ~= c;
|
|
1518 break;
|
|
1519 }
|
|
1520 }
|
|
1521 return result.get;
|
|
1522 }
|
|
1523
|
|
1524 /**********************************************************************
|
|
1525
|
|
1526 **********************************************************************/
|
|
1527
|
|
1528 private void round (int pos)
|
|
1529 {
|
|
1530 int index;
|
|
1531 while (index < pos && digits[index] != '\0')
|
|
1532 index++;
|
|
1533
|
|
1534 if (index == pos && digits[index] >= '5')
|
|
1535 {
|
|
1536 while (index > 0 && digits[index - 1] == '9')
|
|
1537 index--;
|
|
1538
|
|
1539 if (index > 0)
|
|
1540 digits[index - 1]++;
|
|
1541 else
|
|
1542 {
|
|
1543 scale++;
|
|
1544 digits[0] = '1';
|
|
1545 index = 1;
|
|
1546 }
|
|
1547 }
|
|
1548 else
|
|
1549 while (index > 0 && digits[index - 1] == '0')
|
|
1550 index--;
|
|
1551
|
|
1552 if (index == 0)
|
|
1553 {
|
|
1554 scale = 0;
|
|
1555 sign = false;
|
|
1556 }
|
|
1557
|
|
1558 digits[index] = '\0';
|
|
1559 }
|
|
1560 }
|