2 // System.FloatingPointFormatter.cs
5 // Pedro Martinez Juliá <yoros@wanadoo.es>
7 // Copyright (C) 2003 Pedro Martíez Juliá <yoros@wanadoo.es>
12 using System.Collections;
13 using System.Globalization;
18 internal class FloatingPointFormatter {
24 public int dec_len_min;
30 public FloatingPointFormatter
31 (double p, double p10, int dec_len, int dec_len_min,
32 double p2, double p102, int dec_len2, int dec_len_min2) {
36 format1.dec_len = dec_len;
37 format1.dec_len_min = dec_len_min;
41 format2.dec_len = dec_len2;
42 format2.dec_len_min = dec_len_min2;
45 public string GetStringFrom
46 (string format, NumberFormatInfo nfi, double value) {
48 if (format == null || format == "") {
52 nfi = NumberFormatInfo.CurrentInfo;
54 if (Double.IsNaN(value)) {
57 if (Double.IsNegativeInfinity(value)) {
58 return nfi.NegativeInfinitySymbol;
60 if (Double.IsPositiveInfinity(value)) {
61 return nfi.PositiveInfinitySymbol;
66 if (!ParseFormat(format, out specifier, out precision)) {
68 return FormatCustom (format1, value, nfi, format);
71 string msg = "An exception was thrown but the " +
72 "application will continue working right.\n" +
73 "Please mail to \"yoros@wanadoo.es\" with the " +
74 "subject \"FORMAT_EXCEPTION\" and the content: " +
75 "Format: ->" + format + "<-; Value: ->" + value +
77 Console.Error.Write (msg);
78 return FormatGeneral (format1, value, nfi, -1);
82 Format formatData = format1;//(precision > format1.dec_len+1) ? format2 : format1;
86 return FormatCurrency (formatData, value, nfi, precision);
88 throw new FormatException(Locale.GetText(
89 "The specified format is invalid") + ": " + format);
91 formatData = (precision > format1.dec_len) ? format2 : format1;
92 return FormatExponential (formatData, value, nfi, precision, format[0]);
94 return FormatFixedPoint (formatData, value, nfi, precision);
96 return FormatGeneral (formatData, value, nfi, precision);
98 return FormatNumber (formatData, value, nfi, precision);
100 return FormatPercent (formatData, value, nfi, precision);
102 return FormatReversible (formatData, value, nfi, precision);
104 throw new FormatException(Locale.GetText(
105 "The specified format is invalid") + ": " + format);
107 throw new FormatException(Locale.GetText(
108 "The specified format is invalid") + ": " + format);
112 private bool ParseFormat (string format,
113 out char specifier, out int precision) {
115 precision = format2.dec_len;
117 // FIXME: Math.Round is used and the max is 15.
122 switch (format.Length) {
124 specifier = Char.ToUpper(format[0]);
128 if (Char.IsLetter(format[0]) && Char.IsDigit(format[1])) {
129 specifier = Char.ToUpper(format[0]);
130 precision = Convert.ToInt32(format[1] - '0');
135 if (Char.IsLetter(format[0]) && Char.IsDigit(format[1])
136 && Char.IsDigit(format[2])) {
137 specifier = Char.ToUpper(format[0]);
138 precision = Convert.ToInt32(format.Substring(1, 2));
146 private void Normalize (Format formatData, double value, int precision,
147 out long mantissa, out int exponent) {
151 Double.IsInfinity(value) ||
152 Double.IsNaN(value)) {
155 value = Math.Abs(value);
156 if (precision <= (formatData.dec_len) && precision >= 0) {
157 value = Math.Round (value, precision);
161 Double.IsInfinity(value) ||
162 Double.IsNaN(value)) {
166 if (value >= formatData.p10) {
167 while (value >= formatData.p10) {
172 else if (value < formatData.p) {
173 while (value < formatData.p) {
178 mantissa = (long) Math.Round(value);
181 private string FormatCurrency (Format formatData, double value,
182 NumberFormatInfo nfi, int precision) {
184 precision = (precision >= 0) ? precision : nfi.CurrencyDecimalDigits;
185 string numb = FormatNumberInternal (formatData, value, nfi, precision);
187 switch (nfi.CurrencyNegativePattern) {
189 return "(" + nfi.CurrencySymbol + numb + ")";
191 return nfi.NegativeSign + nfi.CurrencySymbol + numb;
193 return nfi.CurrencySymbol + nfi.NegativeSign + numb;
195 return nfi.CurrencySymbol + numb + nfi.NegativeSign;
197 return "(" + numb + nfi.CurrencySymbol + ")";
199 return nfi.NegativeSign + numb + nfi.CurrencySymbol;
201 return numb + nfi.NegativeSign + nfi.CurrencySymbol;
203 return numb + nfi.CurrencySymbol + nfi.NegativeSign;
205 return nfi.NegativeSign + numb + " " + nfi.CurrencySymbol;
207 return nfi.NegativeSign + nfi.CurrencySymbol + " " + numb;
209 return numb + " " + nfi.CurrencySymbol + nfi.NegativeSign;
211 return nfi.CurrencySymbol + " " + numb + nfi.NegativeSign;
213 return nfi.CurrencySymbol + " " + nfi.NegativeSign + numb;
215 return numb + nfi.NegativeSign + " " + nfi.CurrencySymbol;
217 return "(" + nfi.CurrencySymbol + " " + numb + ")";
219 return "(" + numb + " " + nfi.CurrencySymbol + ")";
221 throw new ArgumentException(Locale.GetText(
222 "Invalid CurrencyNegativePattern"));
226 switch (nfi.CurrencyPositivePattern) {
228 return nfi.CurrencySymbol + numb ;
230 return numb + nfi.CurrencySymbol;
232 return nfi.CurrencySymbol + " " + numb;
234 return numb + " " + nfi.CurrencySymbol;
236 throw new ArgumentException(Locale.GetText(
237 "invalid CurrencyPositivePattern"));
242 private string FormatExponential (Format formatData, double value, NumberFormatInfo nfi,
243 int precision, char exp_char) {
244 StringBuilder sb = new StringBuilder();
245 precision = (precision >= 0) ? precision : 6;
246 int decimals = precision;
249 Normalize (formatData, value, precision, out mantissa, out exponent);
250 if (formatData.dec_len > precision) {
251 double aux = mantissa;
252 for (int i = 0; i < formatData.dec_len - precision; i++) {
255 mantissa = (long) Math.Round(aux);
256 for (int i = 0; i < formatData.dec_len - precision; i++) {
260 bool not_null = false;
261 if (mantissa != 0.0) {
262 for (int i = 0; i < formatData.dec_len || mantissa >= 10; i++) {
263 if ((not_null == false) && ((mantissa % 10) != 0)) {
267 sb.Insert(0, (char)('0' + (mantissa % 10)));
275 sb = new StringBuilder();
276 sb.Append((char)('0' + (mantissa % 10)));
279 while (precision > 0) {
283 if (sb.Length == 0) {
286 sb.Insert (0, (char)('0' + (mantissa % 10)) +
287 nfi.NumberDecimalSeparator);
290 sb.Append(exp_char + nfi.PositiveSign);
293 sb.Append(exp_char + nfi.NegativeSign);
295 sb.Append(Math.Abs(exponent).ToString("000"));
297 sb.Insert(0, nfi.NegativeSign);
299 return sb.ToString();
302 private string FormatFixedPoint (Format formatData, double value,
303 NumberFormatInfo nfi, int precision) {
304 StringBuilder sb = new StringBuilder();
305 precision = (precision >= 0) ? precision : nfi.NumberDecimalDigits;
306 int decimals = precision;
309 Normalize (formatData, value, precision, out mantissa, out exponent);
311 while (decimals > 0) {
317 int decimal_limit = -(decimals + 1);
318 while (exponent < 0) {
319 if (exponent > decimal_limit) {
320 sb.Insert(0, (char)('0' + (mantissa % 10)));
327 sb.Append ('0', decimals);
331 if (precision != 0) {
332 sb.Insert(0, nfi.NumberDecimalSeparator);
338 while (exponent > 0) {
342 while (mantissa != 0) {
343 sb.Insert(0, (char)('0' + (mantissa % 10)));
348 sb.Insert(0, nfi.NegativeSign);
350 return sb.ToString();
353 private string FormatGeneral (Format formatData, double value,
354 NumberFormatInfo nfi, int precision) {
355 StringBuilder sb = new StringBuilder();
360 precision = (precision > 0) ?
361 precision : formatData.dec_len+1;
365 Normalize (formatData, value, precision, out mantissa, out exponent);
367 double dmant = mantissa;
368 for (int i = 0; i < formatData.dec_len - precision + 1; i++) {
371 mantissa = (long) Math.Round (dmant);
372 for (int i = 0; i < formatData.dec_len - precision + 1; i++) {
377 /* Calculate the exponent we would get using the scientific notation */
378 int snExponent = exponent;
379 long snMantissa = mantissa;
381 while (snMantissa >= 10) {
386 if (snExponent > -5 && snExponent < precision) {
387 bool not_null = false;
388 while (exponent < 0) {
389 if ((not_null == false) && ((mantissa % 10) != 0)) {
393 sb.Insert(0, (char)('0' + (mantissa % 10)));
398 if (sb.Length != 0) {
399 sb.Insert(0, nfi.NumberDecimalSeparator);
405 while (mantissa > 0) {
406 sb.Insert(0, (char)('0' + (mantissa % 10)));
412 bool not_null = false;
413 while (mantissa >= 10) {
414 if ((not_null == false) && ((mantissa % 10) != 0)) {
418 sb.Insert (0, (char)('0' + (mantissa % 10)));
424 sb.Insert(0, nfi.NumberDecimalSeparator);
426 sb.Insert(0, (char)('0' + (mantissa % 10)) );
429 sb.Append("E" + nfi.PositiveSign);
432 sb.Append("E" + nfi.NegativeSign);
434 sb.Append(Math.Abs(exponent).ToString("00"));
437 sb.Insert(0, nfi.NegativeSign);
440 return sb.ToString();
443 private string FormatNumber (Format formatData, double value, NumberFormatInfo nfi, int precision) {
445 precision = (precision >= 0) ? precision : nfi.NumberDecimalDigits;
446 string numb = FormatNumberInternal (formatData, value, nfi, precision);
448 switch (nfi.NumberNegativePattern) {
450 return "(" + numb + ")";
452 return nfi.NegativeSign + numb;
454 return nfi.NegativeSign + " " + numb;
456 return numb + nfi.NegativeSign;
458 return numb + " " + nfi.NegativeSign;
460 throw new ArgumentException(Locale.GetText(
461 "Invalid NumberNegativePattern"));
467 private string FormatPercent (Format formatData, double value, NumberFormatInfo nfi,
470 precision = (precision >= 0) ? precision : nfi.PercentDecimalDigits;
471 string numb = FormatNumberInternal (formatData, value*100, nfi, precision);
473 switch (nfi.PercentNegativePattern) {
475 return nfi.NegativeSign + numb + " " + nfi.PercentSymbol;
477 return nfi.NegativeSign + numb + nfi.PercentSymbol;
479 return nfi.NegativeSign + nfi.PercentSymbol + numb;
481 throw new ArgumentException(Locale.GetText(
482 "Invalid PercentNegativePattern"));
486 switch (nfi.PercentPositivePattern) {
488 return numb + " " + nfi.PercentSymbol;
490 return numb + nfi.PercentSymbol;
492 return nfi.PercentSymbol + numb;
494 throw new ArgumentException(Locale.GetText(
495 "invalid PercehtPositivePattern"));
500 private string FormatNumberInternal (Format formatData, double value, NumberFormatInfo nfi, int precision)
502 StringBuilder sb = new StringBuilder();
503 int decimals = precision;
506 Normalize (formatData, value, precision, out mantissa, out exponent);
508 while (decimals > 0) {
514 int decimal_limit = -(decimals + 1);
515 while (exponent < 0) {
516 if (exponent > decimal_limit) {
517 sb.Insert(0, (char)('0' + (mantissa % 10)));
524 sb.Append ('0', decimals);
528 if (precision != 0) {
529 sb.Insert(0, nfi.NumberDecimalSeparator);
537 int groupSize = nfi.NumberGroupSizes[0];
538 if (groupSize == 0) groupSize = int.MaxValue;
540 while (exponent > 0 || mantissa != 0) {
542 if (groupPos == groupSize) {
543 sb.Insert (0, nfi.NumberGroupSeparator);
545 if (groupIndex < nfi.NumberGroupSizes.Length - 1) {
547 groupSize = nfi.NumberGroupSizes[groupIndex];
548 if (groupSize == 0) groupSize = int.MaxValue;
557 sb.Insert(0, (char)('0' + (mantissa % 10)));
564 return sb.ToString();
569 private string FormatReversible (Format formatData, double value, NumberFormatInfo nfi,
571 return FormatGeneral (formatData, value, nfi, precision);
574 private string FormatCustom (Format formatData, double value,
575 NumberFormatInfo nfi, string format) {
576 int first_semicolon, last_semicolon;
577 first_semicolon = format.IndexOf(';');
578 last_semicolon = format.LastIndexOf(';');
579 if (first_semicolon == last_semicolon) {
580 if (first_semicolon == -1) {
582 string result = FormatCustomParser (formatData, value, nfi, format);
586 if (result.Length > 0) {
587 result = "-" + result;
591 return FormatCustomParser (formatData, value, nfi, format);
595 return FormatCustomParser
596 (formatData, value, nfi, format.Substring(first_semicolon + 1));
598 return FormatCustomParser (formatData, value, nfi,
599 format.Substring(0, first_semicolon - 1));
602 return FormatCustomParser (formatData, value, nfi,
603 format.Substring(0, first_semicolon - 1));
605 else if (value < 0.0) {
606 return FormatCustomParser (formatData, value, nfi,
607 format.Substring (first_semicolon + 1,
608 last_semicolon - first_semicolon));
610 return FormatCustomParser (formatData, value, nfi,
611 format.Substring(last_semicolon + 1));
614 private struct Flags {
615 public int NumberOfColons;
616 public bool Groupping;
620 public int FirstFormatPos;
621 public int IntegralLength;
622 public int DecimalLength;
623 public int ExponentialLength;
626 private Flags AnalizeFormat (string format) {
627 Flags f = new Flags();
628 f.NumberOfColons = 0;
633 f.FirstFormatPos = -1;
634 int aux = 0, i = 0, count = 0;
635 foreach (char c in format) {
642 if (f.FirstFormatPos < 0) {
643 f.FirstFormatPos = i;
653 f.IntegralLength = count;
656 f.NumberOfColons = aux;
665 f.DecimalLength = count;
673 f.NumberOfColons = aux;
675 if (f.DecimalLength > 0) {
676 f.ExponentialLength = count;
679 f.DecimalLength = count;
684 private string FormatCustomParser (Format formatData, double value,
685 NumberFormatInfo nfi, string format) {
688 Flags f = AnalizeFormat(format);
689 if (f.FirstFormatPos < 0) {
692 if (((f.Percent) || (f.NumberOfColons > 0)) && (f.ExpPos < 0)) {
693 int len = f.DecimalLength;
699 if (f.NumberOfColons > 0) {
700 len -= (3 * f.NumberOfColons);
701 exp -= 3 * f.NumberOfColons;
706 value = Math.Round(value, len);
707 Normalize (formatData, value, 15, out mantissa, out exponent);
711 value = Math.Round(value, f.DecimalLength);
712 Normalize (formatData, value, 15, out mantissa, out exponent);
714 StringBuilder sb = new StringBuilder();
716 StringBuilder sb_decimal = new StringBuilder();
717 while (mantissa > 0) {
718 sb_decimal.Insert(0, (char)('0' + (mantissa % 10)));
724 for (k = sb_decimal.Length - 1;
725 k >= 0 && sb_decimal[k] == '0'; k--);
726 sb_decimal.Remove(k + 1, sb_decimal.Length - k - 1);
727 for (int i = f.DotPos - 2; i >= 0; i--) {
729 if (i > 0 && format[i-1] == '\\') {
746 sb.Append(sb_decimal[0]);
747 sb.Append(nfi.NumberDecimalSeparator);
748 for (int j = 1, i = f.DotPos + 1; i < f.ExpPos; i++) {
752 sb.Append(format[++i]);
755 if (j >= sb_decimal.Length) {
761 if (j < sb_decimal.Length) {
762 if ((i == f.ExpPos - 1) &&
763 (j < sb_decimal.Length - 1)) {
764 int a = sb_decimal[j] - '0';
765 int b = sb_decimal[j+1] - '0';
766 if (((b == 5) && ((a % 2) == 0)) || (b > 5)) {
769 sb.Append((char)('0' + (a % 10)));
772 sb.Append(sb_decimal[j++]);
781 sb.Append(format[f.ExpPos]);
786 inicio = f.ExpPos + 1;
787 if (format[inicio] == '-') {
790 else if (format[inicio] == '+') {
797 while (fin < format.Length && format[fin++] == '0');
798 StringBuilder sb_exponent = new StringBuilder();
799 exponent = Math.Abs(exponent);
800 while (exponent > 0) {
801 sb_exponent.Insert(0, (char)('0' + (exponent % 10)));
804 while (sb_exponent.Length < (fin - inicio)) {
805 sb_exponent.Insert(0, '0');
807 sb.Append(sb_exponent.ToString());
808 for (int i = fin; i < format.Length; i++) {
809 sb.Append(format[i]);
811 return sb.ToString();
814 f.ExpPos = format.Length;
817 while (exponent < 0) {
818 mantissa = (long) Math.Round((double)mantissa / 10);
821 f.DotPos = format.Length;
824 StringBuilder sb_decimal = new StringBuilder();
825 while (exponent < 0) {
826 sb_decimal.Insert(0, (char)('0' + (mantissa % 10)));
831 for (k = sb_decimal.Length - 1;
832 k >= 0 && sb_decimal[k] == '0'; k--);
833 sb_decimal.Remove(k + 1, sb_decimal.Length - k - 1);
834 if (sb_decimal.Length > 0) {
835 sb.Append(nfi.NumberDecimalSeparator);
837 else if (format[f.DotPos + 1] == '0') {
838 sb.Append(nfi.NumberDecimalSeparator);
840 bool terminado = false;
841 for (int j = 0, i = f.DotPos + 1; i < f.ExpPos; i++) {
842 if (format[i] == '0' || format[i] == '#') {
843 if (j < sb_decimal.Length) {
844 sb.Append(sb_decimal[j++]);
846 else if (format[i] == '0' && !terminado) {
849 else if (format[i] == '#' && !terminado) {
853 else if (format[i] == '\\') {
854 sb.Append(format[++i]);
857 sb.Append(format[i]);
862 for (int i = f.DotPos - 1; i >= f.FirstFormatPos; i--) {
863 if (format[i] == '#' || format[i] == '0') {
864 if (exponent > 0 || mantissa > 0 || format[i] == '0') {
865 if (f.Groupping && gro == nfi.NumberGroupSizes[0]) {
866 sb.Insert(0, nfi.NumberGroupSeparator);
874 else if (mantissa > 0) {
875 sb.Insert(0, (char)('0' + (mantissa % 10)));
878 else if (format[i] == '0') {
883 else if (format[i] != ',') {
884 sb.Insert(0, format[i]);
886 else if (i > 0 && format[i-1] == '\\') {
887 sb.Insert(0, format[i]);
891 while (exponent > 0) {
892 if (f.Groupping && gro == nfi.NumberGroupSizes[0]) {
893 sb.Insert(0, nfi.NumberGroupSeparator);
900 while (mantissa > 0) {
901 if (f.Groupping && gro == nfi.NumberGroupSizes[0]) {
902 sb.Insert(0, nfi.NumberGroupSeparator);
906 sb.Insert(0, (char)('0' + (mantissa % 10)));
909 for (int i = f.FirstFormatPos - 1; i >= 0; i--) {
910 sb.Insert(0, format[i]);
912 return sb.ToString();