2 // System.DecimalFormatter.cs
5 // Martin Weindel (martin.weindel@t-online.de)
7 // (C) Martin Weindel, Derek Holden dholden@draper.com
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:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
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;
43 internal sealed class DecimalFormatter
46 private static bool ParseFormat (string format, out char specifier, out int precision)
51 int length = format.Length;
52 if (length < 1 || length > 3)
55 char[] chars = format.ToCharArray ();
56 specifier = Char.ToUpperInvariant(chars[0]);
63 if (chars[1] < '0' || chars[1] > '9')
66 precision = chars[1] - '0';
70 if (chars[1] < '0' || chars[2] < '0' || chars[1] > '9' || chars[2] > '9')
73 precision = (chars[1] - '0') * 10 + (chars[2] - '0');
79 public static string NumberToString(string format, NumberFormatInfo nfi, Decimal value)
83 format = format.Trim ();
84 if (!DecimalFormatter.ParseFormat(format, out specifier, out precision))
86 throw new FormatException (Locale.GetText ("The specified format is invalid"));
91 // first calculate number of digits or decimals needed for format
95 decimals = (precision >= 0) ? precision : nfi.CurrencyDecimalDigits;
97 case 'F': goto case 'N';
99 decimals = (precision >= 0) ? precision : nfi.NumberDecimalDigits;
102 digits = (precision >= 0) ? precision : 0;
105 digits = (precision >= 0) ? precision+1 : 7;
108 decimals = (precision >= 0) ? precision+2 : nfi.PercentDecimalDigits+2;
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)
131 // now build the format
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);
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)
151 sb.Insert((decPos <= 0) ? 1 : decPos, nfi.NumberDecimalSeparator);
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);
174 sb.Insert(0, nfi.NegativeSign);
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)
194 bool bFix = (digits == 0 && decPos >= -3) || (digits >= decPos && decPos >= -3 && digits != 0);
196 bool bFix = ((digits == 0) || ((digits >= decPos) && (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);
208 while (decPos <= 0) {
210 if (dig != 0 && decPos != 0)
214 return FormatFixedPoint(nfi, sb, sb.Length - decPos, decPos, sign);
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)
229 if (groupSizes != null)
233 for (int i = 0; i < groupSizes.GetLength(0); i++)
235 int size = groupSizes[i];
239 if (digitCount < decPos)
241 sb.Insert(decPos - digitCount, groupSeparator);
242 offset += groupSeparator.Length;
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);
278 switch (nfi.NumberNegativePattern)
281 return "(" + s + ")";
283 return nfi.NegativeSign + s;
285 return nfi.NegativeSign + " " + s;
287 return s + nfi.NegativeSign;
289 return s + " " + nfi.NegativeSign;
291 throw new ArgumentException(Locale.GetText ("Invalid NumberNegativePattern"));
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);
308 switch (nfi.CurrencyNegativePattern)
311 return "(" + nfi.CurrencySymbol + s + ")";
313 return nfi.NegativeSign + nfi.CurrencySymbol + s;
315 return nfi.CurrencySymbol + nfi.NegativeSign + s;
317 return nfi.CurrencySymbol + s + nfi.NegativeSign;
319 return "(" + s + nfi.CurrencySymbol + ")";
321 return nfi.NegativeSign + s + nfi.CurrencySymbol;
323 return s + nfi.NegativeSign + nfi.CurrencySymbol;
325 return s + nfi.CurrencySymbol + nfi.NegativeSign;
327 return nfi.NegativeSign + s + " " + nfi.CurrencySymbol;
329 return nfi.NegativeSign + nfi.CurrencySymbol + " " + s;
331 return s + " " + nfi.CurrencySymbol + nfi.NegativeSign;
333 return nfi.CurrencySymbol + " " + s + nfi.NegativeSign;
335 return nfi.CurrencySymbol + " " + nfi.NegativeSign + s;
337 return s + nfi.NegativeSign + " " + nfi.CurrencySymbol;
339 return "(" + nfi.CurrencySymbol + " " + s + ")";
341 return "(" + s + " " + nfi.CurrencySymbol + ")";
343 throw new ArgumentException(Locale.GetText ("Invalid CurrencyNegativePattern"));
348 switch (nfi.CurrencyPositivePattern)
351 return nfi.CurrencySymbol + s;
353 return s + nfi.CurrencySymbol;
355 return nfi.CurrencySymbol + " " + s;
357 return s + " " + nfi.CurrencySymbol;
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);
372 switch (nfi.PercentNegativePattern)
375 return nfi.NegativeSign + s + " " + nfi.PercentSymbol;
377 return nfi.NegativeSign + s + nfi.PercentSymbol;
379 return nfi.NegativeSign + nfi.PercentSymbol + s;
381 throw new ArgumentException(Locale.GetText ("Invalid PercentNegativePattern"));
386 switch (nfi.PercentPositivePattern)
389 return s + " " + nfi.PercentSymbol;
391 return s + nfi.PercentSymbol;
393 return nfi.PercentSymbol + s;
395 throw new ArgumentException("Invalid PercentPositivePattern");
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 ();