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