2001-11-21 Miguel de Icaza <miguel@ximian.com>
[mono.git] / mcs / class / corlib / System / DecimalFormatter.cs
1 //
2 // System.DecimalFormatter.cs
3 //
4 // Author:
5 //   Martin Weindel (martin.weindel@t-online.de)
6 //
7 // (C) Martin Weindel, Derek Holden  dholden@draper.com
8 //
9
10 //
11 // Internal class for formatting decimal numbers. 
12
13 using System.Globalization;
14 using System.Text;
15 using S = System;  // only used for switching test implementation\r
16
17 namespace System \r
18 {
19
20     internal sealed class DecimalFormatter \r
21     {
22
23         private static bool ParseFormat (string format, out char specifier,  out int precision)
24         {                                
25             precision = -1;
26             specifier = '\0';
27                     
28             int length = format.Length;
29             if (length < 1 || length > 3)
30                 return false;
31                     
32             char[] chars = format.ToCharArray ();
33             specifier = Char.ToUpper(chars[0]);
34
35             if (length == 1) 
36                 return true;
37                     
38             if (length == 2) \r
39             {
40                 if (chars[1] < '0' || chars[1] > '9')
41                     return false;
42                             
43                 precision = chars[1] - '0';
44             } \r
45             else \r
46             {
47                 if (chars[1] < '0' || chars[2] < '0' || chars[1] > '9' || chars[2] > '9')
48                     return false;
49                             
50                 precision = (chars[1] - '0') * 10 + (chars[2] - '0');
51             }
52                     
53             return true;
54         }        
55
56         public static string NumberToString(string format, NumberFormatInfo nfi, S.Decimal value)\r
57         {
58             char specifier;\r
59             int precision;\r
60             if (!DecimalFormatter.ParseFormat(format, out specifier, out precision)) \r
61             {\r
62                 throw new FormatException (Locale.GetText ("The specified format is invalid"));\r
63             }\r
64 \r
65             int digits = -1;\r
66             int decimals = 0;\r
67             // first calculate number of digits or decimals needed for format\r
68             switch (specifier)\r
69             {\r
70                 case 'C':\r
71                     decimals = (precision >= 0) ? precision : nfi.CurrencyDecimalDigits;\r
72                     break;\r
73                 case 'F': goto case 'N'; \r
74                 case 'N':\r
75                     decimals = (precision >= 0) ? precision : nfi.NumberDecimalDigits;\r
76                     break;\r
77                 case 'G':\r
78                     digits = (precision >= 0) ? precision : 0;\r
79                     break;\r
80                 case 'E': \r
81                     digits = (precision >= 0) ? precision+1 : 7;\r
82                     break;\r
83                 case 'P': \r
84                     decimals = (precision >= 0) ? precision+2 : nfi.PercentDecimalDigits+2;\r
85                     break;\r
86                 case 'Z':\r
87                     digits = 0;\r
88                     break;\r
89             }\r
90 \r
91             // get digit string\r
92             const int bufSize = 40;\r
93             int decPos = 0, sign = 0;\r
94 #if !MSTEST\r
95             char[] buf = new char[bufSize];\r
96             if (S.Decimal.decimal2string(ref value, digits, decimals, buf, bufSize, out decPos, out sign) != 0) \r
97             {\r
98                 throw new FormatException(); // should never happen \r
99             }\r
100 \r
101             StringBuilder sb = new StringBuilder(new String(buf), bufSize);\r
102 #else\r
103             StringBuilder sb = new StringBuilder(bufSize);\r
104             if (S.Decimal.decimal2string(ref value, digits, decimals, sb, bufSize, out decPos, out sign) != 0) \r
105             {\r
106                 throw new FormatException(); // should never happen \r
107             }\r
108 #endif\r
109 \r
110             // now build the format\r
111             switch (specifier)\r
112             {\r
113                 case 'C': return FormatCurrency(nfi, sb, decimals, decPos, sign);\r
114                 case 'N': return FormatNumber(nfi, sb, decimals, decPos, sign);\r
115                 case 'F': return FormatFixedPoint(nfi, sb, decimals, decPos, sign);\r
116                 case 'G': return FormatGeneral(nfi, sb, digits, decPos, sign, format[0]);\r
117                 case 'E': return FormatExponential(nfi, sb, digits, decPos, sign, format[0], true);\r
118                 case 'P': return FormatPercent(nfi, sb, decimals, decPos, sign);\r
119                 case 'Z': return FormatNormalized(nfi, sb, digits, decPos, sign);\r
120                 default: \r
121                     throw new FormatException (Locale.GetText ("The specified format is invalid"));\r
122             }\r
123         }\r
124
125         private static string FormatFixedPoint(NumberFormatInfo nfi, StringBuilder sb, 
126             int decimals, int decPos, int sign)
127         {
128             if (decimals > 0)
129             {
130                 sb.Insert((decPos <= 0) ? 1 : decPos, nfi.NumberDecimalSeparator);
131             }
132
133             if (sign != 0) \r
134             {
135                 sb.Insert(0, nfi.NegativeSign);
136             }
137
138             return sb.ToString();
139         }
140
141         private static string FormatExponential(NumberFormatInfo nfi, StringBuilder sb, 
142             int digits, int decPos, int sign, char echar, bool exp3flag)
143         {
144             // insert decimal separator
145             if (digits > 1 || (digits == 0 && sb.Length > 1)) \r
146             {
147                 sb.Insert(1, nfi.NumberDecimalSeparator);
148             }
149
150             // insert sign
151             if (sign != 0)
152             {
153                 sb.Insert(0, nfi.NegativeSign);
154             }
155
156             // append exponent
157             sb.Append(echar);
158             decPos--;
159             sb.Append((decPos >= 0) ? nfi.PositiveSign : nfi.NegativeSign);
160             if (decPos < 0) decPos *= -1;
161             if (exp3flag) sb.Append('0');
162             sb.Append((char)('0' + decPos/10));
163             sb.Append((char)('0' + decPos%10));
164
165             return sb.ToString();
166         }
167
168         private static string FormatGeneral(NumberFormatInfo nfi, StringBuilder sb, 
169             int digits, int decPos, int sign, char gchar)
170         {
171             int dig = digits;
172             bool bFix = (digits == 0 && decPos >= -3) || (digits >= decPos && decPos >= -3 && digits != 0);
173
174             // remove trailing digits
175             while (sb.Length > 1 && (sb.Length > decPos || !bFix) && sb[sb.Length-1] == '0')
176             {
177                 sb.Remove(sb.Length-1, 1);
178                 if (dig != 0) dig--;
179             }
180
181             if (bFix)\r
182             {
183                 while (decPos <= 0) \r
184                 {
185                     sb.Insert(0, '0');
186                     if (dig != 0 && decPos != 0) dig++;
187                     decPos++;
188                 }
189                 return FormatFixedPoint(nfi, sb, sb.Length - decPos, decPos, sign);
190             }
191             else\r
192             {
193                 return FormatExponential(nfi, sb, dig, decPos, sign, (char)(gchar-2), false);
194             }
195         }
196
197         private static string FormatGroupAndDec(StringBuilder sb, int decimals, int decPos, 
198             int[] groupSizes, string groupSeparator, string decSeparator)
199         {
200             int offset = 0;
201
202             // Groups
203             if (decPos > 0) \r
204             {
205                 if (groupSizes != null) \r
206                 {
207                     int lastSize = 0;
208                     int digitCount = 0;
209                     for (int i = 0; i < groupSizes.GetLength(0); i++) \r
210                     {
211                         int size = groupSizes[i];
212                         if (size > 0) \r
213                         {
214                             digitCount += size;
215                             if (digitCount < decPos) \r
216                             {
217                                 sb.Insert(decPos - digitCount, groupSeparator);
218                                 offset += groupSeparator.Length;
219                             }
220                         }
221                         lastSize = size;
222                     }
223
224                     if (lastSize > 0) \r
225                     {
226                         while (true) \r
227                         {
228                             digitCount +=lastSize;
229                             if (digitCount >= decPos) break;
230                             sb.Insert(decPos - digitCount, groupSeparator);
231                             offset += groupSeparator.Length;
232                         }
233                     }
234                 }
235             }
236
237             if (decimals > 0)
238             {
239                 sb.Insert(offset + ((decPos <= 0) ? 1 : decPos), decSeparator);
240             }
241
242             return sb.ToString();
243         }
244
245         private static string FormatNumber(NumberFormatInfo nfi, StringBuilder sb, 
246             int decimals, int decPos, int sign)
247         {
248             string s = FormatGroupAndDec(sb, decimals, decPos,
249                 nfi.NumberGroupSizes, nfi.NumberGroupSeparator, nfi.NumberDecimalSeparator);
250
251             // sign
252             if (sign != 0) \r
253             {
254                 switch (nfi.NumberNegativePattern)\r
255                 {
256                     case 0:
257                         return "(" + s + ")";
258                     case 1:
259                         return nfi.NegativeSign + s;
260                     case 2:
261                         return nfi.NegativeSign + " " + s;
262                     case 3:
263                         return s + nfi.NegativeSign;
264                     case 4:
265                         return s + " " + nfi.NegativeSign;
266                     default:
267                         throw new ArgumentException(Locale.GetText ("Invalid NumberNegativePattern"));
268                 }
269             } \r
270             else \r
271             {\r
272                 return s;\r
273             }\r
274         }
275
276         private static string FormatCurrency(NumberFormatInfo nfi, StringBuilder sb, 
277             int decimals, int decPos, int sign)
278         {
279             string s = FormatGroupAndDec(sb, decimals, decPos,
280                 nfi.CurrencyGroupSizes, nfi.CurrencyGroupSeparator, nfi.CurrencyDecimalSeparator);
281
282             if (sign != 0) \r
283             { // negative
284                 switch (nfi.CurrencyNegativePattern) \r
285                 {
286                     case 0:
287                         return "(" + nfi.CurrencySymbol + s + ")";
288                     case 1:
289                         return nfi.NegativeSign + nfi.CurrencySymbol + s;
290                     case 2:
291                         return nfi.CurrencySymbol + nfi.NegativeSign + s;
292                     case 3:
293                         return nfi.CurrencySymbol + s + nfi.NegativeSign;
294                     case 4:
295                         return "(" + s + nfi.CurrencySymbol + ")";
296                     case 5:
297                         return nfi.NegativeSign + s + nfi.CurrencySymbol;
298                     case 6:
299                         return s + nfi.NegativeSign + nfi.CurrencySymbol;
300                     case 7:
301                         return s + nfi.CurrencySymbol + nfi.NegativeSign;
302                     case 8:
303                         return nfi.NegativeSign + s + " " + nfi.CurrencySymbol;
304                     case 9:
305                         return nfi.NegativeSign + nfi.CurrencySymbol + " " + s;
306                     case 10:
307                         return s + " " + nfi.CurrencySymbol + nfi.NegativeSign;
308                     case 11:
309                         return nfi.CurrencySymbol + " " + s + nfi.NegativeSign;
310                     case 12:
311                         return nfi.CurrencySymbol + " " + nfi.NegativeSign + s;
312                     case 13:
313                         return s + nfi.NegativeSign + " " + nfi.CurrencySymbol;
314                     case 14:
315                         return "(" + nfi.CurrencySymbol + " " + s + ")";
316                     case 15:
317                         return "(" + s + " " + nfi.CurrencySymbol + ")";
318                     default:
319                         throw new ArgumentException(Locale.GetText ("Invalid CurrencyNegativePattern"));
320                 }
321             }
322             else \r
323             {
324                 switch (nfi.CurrencyPositivePattern) \r
325                 {
326                     case 0:
327                         return nfi.CurrencySymbol + s;
328                     case 1:
329                         return s + nfi.CurrencySymbol;
330                     case 2:
331                         return nfi.CurrencySymbol + " " + s;
332                     case 3:
333                         return s + " " + nfi.CurrencySymbol;
334                     default:
335                         throw new ArgumentException(Locale.GetText ("Invalid CurrencyPositivePattern"));
336                 }
337             }
338         }
339
340         private static string FormatPercent(NumberFormatInfo nfi, StringBuilder sb, 
341             int decimals, int decPos, int sign)
342         {
343             string s = FormatGroupAndDec(sb, decimals, decPos+2, 
344                 nfi.PercentGroupSizes, nfi.PercentGroupSeparator, nfi.PercentDecimalSeparator);
345
346             if (sign != 0) \r
347             { // negative
348                 switch (nfi.PercentNegativePattern) \r
349                 {
350                     case 0:
351                         return nfi.NegativeSign + s + " " + nfi.PercentSymbol;
352                     case 1:
353                         return nfi.NegativeSign + s + nfi.PercentSymbol;
354                     case 2:
355                         return nfi.NegativeSign + nfi.PercentSymbol + s;
356                     default:
357                         throw new ArgumentException(Locale.GetText ("Invalid PercentNegativePattern"));
358                 }
359             }
360             else \r
361             {
362                 switch (nfi.PercentPositivePattern) \r
363                 {
364                     case 0:
365                         return s + " " + nfi.PercentSymbol;
366                     case 1:
367                         return s + nfi.PercentSymbol;
368                     case 2:
369                         return nfi.PercentSymbol + s;
370                     default:
371                         throw new ArgumentException("Invalid PercentPositivePattern");
372                 }
373             }
374         }
375
376         private static string FormatNormalized(NumberFormatInfo nfi, StringBuilder sb, 
377             int digits, int decPos, int sign)
378         {
379             //LAMESPEC: how should this format look like ? Is this a fixed point format ?
380             throw new NotImplementedException ();
381         }
382     }
383 }