**** Merged from MCS ****
[mono-project.git] / mcs / class / corlib / System / DecimalFormatter.cs
blob6c08f0dda70c474ec162d23b4ed887c0547e21ef
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 //
11 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
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.
34 // Internal class for formatting decimal numbers.
36 using System.Globalization;
37 using System.Text;
38 using System;
40 namespace System
43 internal sealed class DecimalFormatter
46 private static bool ParseFormat (string format, out char specifier, out int precision)
48 precision = -1;
49 specifier = '\0';
51 int length = format.Length;
52 if (length < 1 || length > 3)
53 return false;
55 char[] chars = format.ToCharArray ();
56 specifier = Char.ToUpperInvariant(chars[0]);
58 if (length == 1)
59 return true;
61 if (length == 2)
63 if (chars[1] < '0' || chars[1] > '9')
64 return false;
66 precision = chars[1] - '0';
68 else
70 if (chars[1] < '0' || chars[2] < '0' || chars[1] > '9' || chars[2] > '9')
71 return false;
73 precision = (chars[1] - '0') * 10 + (chars[2] - '0');
76 return true;
79 public static string NumberToString(string format, NumberFormatInfo nfi, Decimal value)
81 char specifier;
82 int precision;
83 format = format.Trim ();
84 if (!DecimalFormatter.ParseFormat(format, out specifier, out precision))
86 throw new FormatException (Locale.GetText ("The specified format is invalid"));
89 int digits = -1;
90 int decimals = 0;
91 // first calculate number of digits or decimals needed for format
92 switch (specifier)
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;
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)
121 throw new FormatException(); // should never happen
124 string TempString = new String(buf);
125 TempString = TempString.Trim(new char[] {(char)0x0});
126 StringBuilder sb = new StringBuilder(TempString, TempString.Length);
128 if (sb.ToString () == String.Empty && decPos > 0 && sign == 0)
129 sb.Append ('0');
131 // now build the format
132 switch (specifier)
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"));
146 private static string FormatFixedPoint(NumberFormatInfo nfi, StringBuilder sb,
147 int decimals, int decPos, int sign)
149 if (decimals > 0)
151 sb.Insert((decPos <= 0) ? 1 : decPos, nfi.NumberDecimalSeparator);
154 if (sign != 0)
156 sb.Insert(0, nfi.NegativeSign);
159 return sb.ToString();
162 private static string FormatExponential(NumberFormatInfo nfi, StringBuilder sb,
163 int digits, int decPos, int sign, char echar, bool exp3flag)
165 // insert decimal separator
166 if (digits > 1 || (digits == 0 && sb.Length > 1))
168 sb.Insert(1, nfi.NumberDecimalSeparator);
171 // insert sign
172 if (sign != 0)
174 sb.Insert(0, nfi.NegativeSign);
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));
186 return sb.ToString();
189 private static string FormatGeneral(NumberFormatInfo nfi, StringBuilder sb,
190 int digits, int decPos, int sign, char gchar)
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--;
207 if (bFix) {
208 while (decPos <= 0) {
209 sb.Insert (0, '0');
210 if (dig != 0 && decPos != 0)
211 dig++;
212 decPos++;
214 return FormatFixedPoint(nfi, sb, sb.Length - decPos, decPos, sign);
216 else {
217 return FormatExponential(nfi, sb, dig, decPos, sign, (char)(gchar-2), false);
221 private static string FormatGroupAndDec(StringBuilder sb, int decimals, int decPos,
222 int[] groupSizes, string groupSeparator, string decSeparator)
224 int offset = 0;
226 // Groups
227 if (decPos > 0)
229 if (groupSizes != null)
231 int lastSize = 0;
232 int digitCount = 0;
233 for (int i = 0; i < groupSizes.GetLength(0); i++)
235 int size = groupSizes[i];
236 if (size > 0)
238 digitCount += size;
239 if (digitCount < decPos)
241 sb.Insert(decPos - digitCount, groupSeparator);
242 offset += groupSeparator.Length;
245 lastSize = size;
248 if (lastSize > 0)
250 while (true)
252 digitCount +=lastSize;
253 if (digitCount >= decPos) break;
254 sb.Insert(decPos - digitCount, groupSeparator);
255 offset += groupSeparator.Length;
261 if ((decimals > 0) && (decPos+offset < sb.Length))
263 sb.Insert(offset + ((decPos <= 0) ? 1 : decPos), decSeparator);
266 return sb.ToString();
269 private static string FormatNumber(NumberFormatInfo nfi, StringBuilder sb,
270 int decimals, int decPos, int sign)
272 string s = FormatGroupAndDec(sb, decimals, decPos,
273 nfi.NumberGroupSizes, nfi.NumberGroupSeparator, nfi.NumberDecimalSeparator);
275 // sign
276 if (sign != 0)
278 switch (nfi.NumberNegativePattern)
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"));
294 else
296 return s;
300 private static string FormatCurrency(NumberFormatInfo nfi, StringBuilder sb,
301 int decimals, int decPos, int sign)
303 string s = FormatGroupAndDec(sb, decimals, decPos,
304 nfi.CurrencyGroupSizes, nfi.CurrencyGroupSeparator, nfi.CurrencyDecimalSeparator);
306 if (sign != 0)
307 { // negative
308 switch (nfi.CurrencyNegativePattern)
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"));
346 else
348 switch (nfi.CurrencyPositivePattern)
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"));
364 private static string FormatPercent(NumberFormatInfo nfi, StringBuilder sb,
365 int decimals, int decPos, int sign)
367 string s = FormatGroupAndDec(sb, decimals, decPos+2,
368 nfi.PercentGroupSizes, nfi.PercentGroupSeparator, nfi.PercentDecimalSeparator);
370 if (sign != 0)
371 { // negative
372 switch (nfi.PercentNegativePattern)
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"));
384 else
386 switch (nfi.PercentPositivePattern)
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");
400 [MonoTODO]
401 private static string FormatNormalized(NumberFormatInfo nfi, StringBuilder sb,
402 int digits, int decPos, int sign)
404 //LAMESPEC: how should this format look like ? Is this a fixed point format ?
405 throw new NotImplementedException ();