comparison 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
comparison
equal deleted inserted replaced
131:5825d48b27d1 132:1700239cab2e
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 }