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