2005-01-31 Zoltan Varga <vargaz@freemail.hu>
[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 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 //
34 // Internal class for formatting decimal numbers. 
35
36 using System.Globalization;
37 using System.Text;
38 using System;
39
40 namespace System 
41 {
42
43     internal sealed class DecimalFormatter 
44     {
45
46         private static bool ParseFormat (string format, out char specifier,  out int precision)
47         {                                
48             precision = -1;
49             specifier = '\0';
50                     
51             int length = format.Length;
52             if (length < 1 || length > 3)
53                 return false;
54                     
55             char[] chars = format.ToCharArray ();
56             specifier = Char.ToUpperInvariant(chars[0]);
57
58             if (length == 1) 
59                 return true;
60                     
61             if (length == 2) 
62             {
63                 if (chars[1] < '0' || chars[1] > '9')
64                     return false;
65                             
66                 precision = chars[1] - '0';
67             } 
68             else 
69             {
70                 if (chars[1] < '0' || chars[2] < '0' || chars[1] > '9' || chars[2] > '9')
71                     return false;
72                             
73                 precision = (chars[1] - '0') * 10 + (chars[2] - '0');
74             }
75                     
76             return true;
77         }        
78
79         public static string NumberToString(string format, NumberFormatInfo nfi, Decimal value)
80         {
81             char specifier;
82             int precision;
83             format = format.Trim ();
84             if (!DecimalFormatter.ParseFormat(format, out specifier, out precision)) 
85             {
86                 throw new FormatException (Locale.GetText ("The specified format is invalid"));
87             }
88
89             int digits = -1;
90             int decimals = 0;
91             // first calculate number of digits or decimals needed for format
92             switch (specifier)
93             {
94                 case 'C':
95                     decimals = (precision >= 0) ? precision : nfi.CurrencyDecimalDigits;
96                     break;
97                 case 'F': goto case 'N'; 
98                 case 'N':
99                     decimals = (precision >= 0) ? precision : nfi.NumberDecimalDigits;
100                     break;
101                 case 'G':
102                     digits = (precision >= 0) ? precision : 0;
103                     break;
104                 case 'E': 
105                     digits = (precision >= 0) ? precision+1 : 7;
106                     break;
107                 case 'P': 
108                     decimals = (precision >= 0) ? precision+2 : nfi.PercentDecimalDigits+2;
109                     break;
110                 case 'Z':
111                     digits = 0;
112                     break;
113             }
114
115             // get digit string
116             const int bufSize = 40;
117             int decPos = 0, sign = 0;
118             char[] buf = new char[bufSize];
119             if (Decimal.decimal2string(ref value, digits, decimals, buf, bufSize, out decPos, out sign) != 0) 
120             {
121                 throw new FormatException(); // should never happen 
122             }
123
124                 string TempString = new String(buf);
125                 TempString = TempString.Trim(new char[] {(char)0x0});
126                 StringBuilder sb = new StringBuilder(TempString, TempString.Length);
127
128             if (sb.ToString () == String.Empty && decPos > 0 && sign == 0)
129                     sb.Append ('0');
130
131             // now build the format
132             switch (specifier)
133             {
134                 case 'C': return FormatCurrency(nfi, sb, decimals, decPos, sign);
135                 case 'N': return FormatNumber(nfi, sb, decimals, decPos, sign);
136                 case 'F': return FormatFixedPoint(nfi, sb, decimals, decPos, sign);
137                 case 'G': return FormatGeneral(nfi, sb, digits, decPos, sign, format[0]);
138                 case 'E': return FormatExponential(nfi, sb, digits, decPos, sign, format[0], true);
139                 case 'P': return FormatPercent(nfi, sb, decimals, decPos, sign);
140                 case 'Z': return FormatNormalized(nfi, sb, digits, decPos, sign);
141                 default: 
142                     throw new FormatException (Locale.GetText ("The specified format is invalid"));
143             }
144         }
145
146         private static string FormatFixedPoint(NumberFormatInfo nfi, StringBuilder sb, 
147             int decimals, int decPos, int sign)
148         {
149             if (decimals > 0)
150             {
151                 sb.Insert((decPos <= 0) ? 1 : decPos, nfi.NumberDecimalSeparator);
152             }
153
154             if (sign != 0) 
155             {
156                 sb.Insert(0, nfi.NegativeSign);
157             }
158
159             return sb.ToString();
160         }
161
162         private static string FormatExponential(NumberFormatInfo nfi, StringBuilder sb, 
163             int digits, int decPos, int sign, char echar, bool exp3flag)
164         {
165             // insert decimal separator
166             if (digits > 1 || (digits == 0 && sb.Length > 1)) 
167             {
168                 sb.Insert(1, nfi.NumberDecimalSeparator);
169             }
170
171             // insert sign
172             if (sign != 0)
173             {
174                 sb.Insert(0, nfi.NegativeSign);
175             }
176
177             // append exponent
178             sb.Append(echar);
179             decPos--;
180             sb.Append((decPos >= 0) ? nfi.PositiveSign : nfi.NegativeSign);
181             if (decPos < 0) decPos *= -1;
182             if (exp3flag) sb.Append('0');
183             sb.Append((char)('0' + decPos/10));
184             sb.Append((char)('0' + decPos%10));
185
186             return sb.ToString();
187         }
188
189         private static string FormatGeneral(NumberFormatInfo nfi, StringBuilder sb, 
190             int digits, int decPos, int sign, char gchar)
191         {
192                 int dig = digits;
193 #if NET_1_0
194                 bool bFix = (digits == 0 && decPos >= -3) || (digits >= decPos && decPos >= -3 && digits != 0);
195 #else
196                 bool bFix = ((digits == 0) || ((digits >= decPos) && (digits != 0)));
197 #endif
198                 if (digits > 0) {
199                         // remove trailing digits
200                         while (sb.Length > 1 && (sb.Length > decPos || !bFix) && sb [sb.Length-1] == '0') {
201                                 sb.Remove (sb.Length-1, 1);
202                                 if (dig != 0)
203                                         dig--;
204                         }
205                 }
206
207                 if (bFix) {
208                         while (decPos <= 0) {
209                                 sb.Insert (0, '0');
210                                 if (dig != 0 && decPos != 0)
211                                         dig++;
212                                 decPos++;
213                         }
214                         return FormatFixedPoint(nfi, sb, sb.Length - decPos, decPos, sign);
215                 }
216                 else {
217                         return FormatExponential(nfi, sb, dig, decPos, sign, (char)(gchar-2), false);
218                 }
219         }
220
221         private static string FormatGroupAndDec(StringBuilder sb, int decimals, int decPos, 
222             int[] groupSizes, string groupSeparator, string decSeparator)
223         {
224             int offset = 0;
225
226             // Groups
227             if (decPos > 0) 
228             {
229                 if (groupSizes != null) 
230                 {
231                     int lastSize = 0;
232                     int digitCount = 0;
233                     for (int i = 0; i < groupSizes.GetLength(0); i++) 
234                     {
235                         int size = groupSizes[i];
236                         if (size > 0) 
237                         {
238                             digitCount += size;
239                             if (digitCount < decPos) 
240                             {
241                                 sb.Insert(decPos - digitCount, groupSeparator);
242                                 offset += groupSeparator.Length;
243                             }
244                         }
245                         lastSize = size;
246                     }
247
248                     if (lastSize > 0) 
249                     {
250                         while (true) 
251                         {
252                             digitCount +=lastSize;
253                             if (digitCount >= decPos) break;
254                             sb.Insert(decPos - digitCount, groupSeparator);
255                             offset += groupSeparator.Length;
256                         }
257                     }
258                 }
259             }
260
261             if ((decimals > 0) && (decPos+offset < sb.Length))
262             {
263                 sb.Insert(offset + ((decPos <= 0) ? 1 : decPos), decSeparator);
264             }
265
266             return sb.ToString();
267         }
268
269         private static string FormatNumber(NumberFormatInfo nfi, StringBuilder sb, 
270             int decimals, int decPos, int sign)
271         {
272             string s = FormatGroupAndDec(sb, decimals, decPos,
273                 nfi.NumberGroupSizes, nfi.NumberGroupSeparator, nfi.NumberDecimalSeparator);
274
275             // sign
276             if (sign != 0) 
277             {
278                 switch (nfi.NumberNegativePattern)
279                 {
280                     case 0:
281                         return "(" + s + ")";
282                     case 1:
283                         return nfi.NegativeSign + s;
284                     case 2:
285                         return nfi.NegativeSign + " " + s;
286                     case 3:
287                         return s + nfi.NegativeSign;
288                     case 4:
289                         return s + " " + nfi.NegativeSign;
290                     default:
291                         throw new ArgumentException(Locale.GetText ("Invalid NumberNegativePattern"));
292                 }
293             } 
294             else 
295             {
296                 return s;
297             }
298         }
299
300         private static string FormatCurrency(NumberFormatInfo nfi, StringBuilder sb, 
301             int decimals, int decPos, int sign)
302         {
303             string s = FormatGroupAndDec(sb, decimals, decPos,
304                 nfi.CurrencyGroupSizes, nfi.CurrencyGroupSeparator, nfi.CurrencyDecimalSeparator);
305
306             if (sign != 0) 
307             { // negative
308                 switch (nfi.CurrencyNegativePattern) 
309                 {
310                     case 0:
311                         return "(" + nfi.CurrencySymbol + s + ")";
312                     case 1:
313                         return nfi.NegativeSign + nfi.CurrencySymbol + s;
314                     case 2:
315                         return nfi.CurrencySymbol + nfi.NegativeSign + s;
316                     case 3:
317                         return nfi.CurrencySymbol + s + nfi.NegativeSign;
318                     case 4:
319                         return "(" + s + nfi.CurrencySymbol + ")";
320                     case 5:
321                         return nfi.NegativeSign + s + nfi.CurrencySymbol;
322                     case 6:
323                         return s + nfi.NegativeSign + nfi.CurrencySymbol;
324                     case 7:
325                         return s + nfi.CurrencySymbol + nfi.NegativeSign;
326                     case 8:
327                         return nfi.NegativeSign + s + " " + nfi.CurrencySymbol;
328                     case 9:
329                         return nfi.NegativeSign + nfi.CurrencySymbol + " " + s;
330                     case 10:
331                         return s + " " + nfi.CurrencySymbol + nfi.NegativeSign;
332                     case 11:
333                         return nfi.CurrencySymbol + " " + s + nfi.NegativeSign;
334                     case 12:
335                         return nfi.CurrencySymbol + " " + nfi.NegativeSign + s;
336                     case 13:
337                         return s + nfi.NegativeSign + " " + nfi.CurrencySymbol;
338                     case 14:
339                         return "(" + nfi.CurrencySymbol + " " + s + ")";
340                     case 15:
341                         return "(" + s + " " + nfi.CurrencySymbol + ")";
342                     default:
343                         throw new ArgumentException(Locale.GetText ("Invalid CurrencyNegativePattern"));
344                 }
345             }
346             else 
347             {
348                 switch (nfi.CurrencyPositivePattern) 
349                 {
350                     case 0:
351                         return nfi.CurrencySymbol + s;
352                     case 1:
353                         return s + nfi.CurrencySymbol;
354                     case 2:
355                         return nfi.CurrencySymbol + " " + s;
356                     case 3:
357                         return s + " " + nfi.CurrencySymbol;
358                     default:
359                         throw new ArgumentException(Locale.GetText ("Invalid CurrencyPositivePattern"));
360                 }
361             }
362         }
363
364         private static string FormatPercent(NumberFormatInfo nfi, StringBuilder sb, 
365             int decimals, int decPos, int sign)
366         {
367             string s = FormatGroupAndDec(sb, decimals, decPos+2, 
368                 nfi.PercentGroupSizes, nfi.PercentGroupSeparator, nfi.PercentDecimalSeparator);
369
370             if (sign != 0) 
371             { // negative
372                 switch (nfi.PercentNegativePattern) 
373                 {
374                     case 0:
375                         return nfi.NegativeSign + s + " " + nfi.PercentSymbol;
376                     case 1:
377                         return nfi.NegativeSign + s + nfi.PercentSymbol;
378                     case 2:
379                         return nfi.NegativeSign + nfi.PercentSymbol + s;
380                     default:
381                         throw new ArgumentException(Locale.GetText ("Invalid PercentNegativePattern"));
382                 }
383             }
384             else 
385             {
386                 switch (nfi.PercentPositivePattern) 
387                 {
388                     case 0:
389                         return s + " " + nfi.PercentSymbol;
390                     case 1:
391                         return s + nfi.PercentSymbol;
392                     case 2:
393                         return nfi.PercentSymbol + s;
394                     default:
395                         throw new ArgumentException("Invalid PercentPositivePattern");
396                 }
397             }
398         }
399
400         [MonoTODO]
401         private static string FormatNormalized(NumberFormatInfo nfi, StringBuilder sb, 
402             int digits, int decPos, int sign)
403         {
404             //LAMESPEC: how should this format look like ? Is this a fixed point format ?
405             throw new NotImplementedException ();
406         }
407     }
408 }