// // System.FloatingPointFormatter.cs // // Authors: // Pedro Martinez Julia // Jon Skeet // Sebastien Pouliot // // Copyright (C) 2003 Pedro Martíez Juliá // Copyright (C) 2004 Jon Skeet // Copyright (C) 2004 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Text; using System.Collections; using System.Globalization; namespace System { internal class FloatingPointFormatter { struct Format { public double p; public double p10; public int dec_len; public int dec_len_min; } Format format1; Format format2; public FloatingPointFormatter (double p, double p10, int dec_len, int dec_len_min, double p2, double p102, int dec_len2, int dec_len_min2) { format1.p = p; format1.p10 = p10; format1.dec_len = dec_len; format1.dec_len_min = dec_len_min; format2.p = p2; format2.p10 = p102; format2.dec_len = dec_len2; format2.dec_len_min = dec_len_min2; } public string GetStringFrom (string format, NumberFormatInfo nfi, double value) { if (format == null || format == "") { format = "G"; } if (nfi == null) { nfi = NumberFormatInfo.CurrentInfo; } if (Double.IsNaN(value)) { return nfi.NaNSymbol; } if (Double.IsNegativeInfinity(value)) { return nfi.NegativeInfinitySymbol; } if (Double.IsPositiveInfinity(value)) { return nfi.PositiveInfinitySymbol; } char specifier; int precision; if (!ParseFormat(format, out specifier, out precision)) { return FormatCustom (format1, value, nfi, format); } Format formatData = format1;//(precision > format1.dec_len+1) ? format2 : format1; switch (specifier) { case 'C': return FormatCurrency (formatData, value, nfi, precision); case 'D': throw new FormatException(Locale.GetText( "The specified format is invalid") + ": " + format); case 'E': formatData = (precision > format1.dec_len) ? format2 : format1; return FormatExponential (formatData, value, nfi, precision, format[0]); case 'F': return FormatFixedPoint (formatData, value, nfi, precision); case 'G': return FormatGeneral (formatData, value, nfi, precision); case 'N': return FormatNumber (formatData, value, nfi, precision); case 'P': return FormatPercent (formatData, value, nfi, precision); case 'R': return FormatReversible (value, nfi, precision); case 'X': throw new FormatException(Locale.GetText( "The specified format is invalid") + ": " + format); default: throw new FormatException(Locale.GetText( "The specified format is invalid") + ": " + format); } } private bool ParseFormat (string format, out char specifier, out int precision) { specifier = '\0'; precision = format2.dec_len; // FIXME: Math.Round is used and the max is 15. if (precision > 15) precision = 15; switch (format.Length) { case 1: specifier = Char.ToUpperInvariant(format[0]); precision = -1; return true; case 2: if (Char.IsLetter(format[0]) && Char.IsDigit(format[1])) { specifier = Char.ToUpperInvariant(format[0]); precision = Convert.ToInt32(format[1] - '0'); return true; } break; case 3: if (Char.IsLetter(format[0]) && Char.IsDigit(format[1]) && Char.IsDigit(format[2])) { specifier = Char.ToUpperInvariant(format[0]); precision = Convert.ToInt32(format.Substring(1, 2)); return true; } break; } return false; } // Math.Round use banker's rounding while this is not what must // be used for string formatting (see bug #60111) // http://bugzilla.ximian.com/show_bug.cgi?id=60111 // FIXME: should be moved out of here post Mono 1.0 private double Round (double value) { double int_part = Math.Floor (value); double dec_part = value - int_part; if (dec_part >= 0.5) { int_part++; } return int_part; } // FIXME: should be moved out of here post Mono 1.0 private double Round (double value, int digits) { if (digits == 0) return Round (value); double p = Math.Pow (10, digits); double int_part = Math.Floor (value); double dec_part = value - int_part; dec_part *= 1000000000000000L; dec_part = Math.Floor (dec_part); dec_part /= (1000000000000000L / p); dec_part = Round (dec_part); dec_part /= p; return int_part + dec_part; } private void Normalize (Format formatData, double value, int precision, out long mantissa, out int exponent) { mantissa = 0; exponent = 0; if (value == 0.0 || Double.IsInfinity(value) || Double.IsNaN(value)) { return; } value = Math.Abs(value); if (precision <= (formatData.dec_len) && precision >= 0) { value = Round (value, precision); } if (value == 0.0 || Double.IsInfinity(value) || Double.IsNaN(value)) { return; } if (value >= formatData.p10) { while (value >= formatData.p10) { value /= 10; exponent++; } } else if (value < formatData.p) { while (value < formatData.p) { value *= 10; exponent--; } } mantissa = (long) Round(value); } private string FormatCurrency (Format formatData, double value, NumberFormatInfo nfi, int precision) { precision = (precision >= 0) ? precision : nfi.CurrencyDecimalDigits; string numb = FormatNumberInternal (formatData, value, nfi, precision); if (value < 0) { switch (nfi.CurrencyNegativePattern) { case 0: return "(" + nfi.CurrencySymbol + numb + ")"; case 1: return nfi.NegativeSign + nfi.CurrencySymbol + numb; case 2: return nfi.CurrencySymbol + nfi.NegativeSign + numb; case 3: return nfi.CurrencySymbol + numb + nfi.NegativeSign; case 4: return "(" + numb + nfi.CurrencySymbol + ")"; case 5: return nfi.NegativeSign + numb + nfi.CurrencySymbol; case 6: return numb + nfi.NegativeSign + nfi.CurrencySymbol; case 7: return numb + nfi.CurrencySymbol + nfi.NegativeSign; case 8: return nfi.NegativeSign + numb + " " + nfi.CurrencySymbol; case 9: return nfi.NegativeSign + nfi.CurrencySymbol + " " + numb; case 10: return numb + " " + nfi.CurrencySymbol + nfi.NegativeSign; case 11: return nfi.CurrencySymbol + " " + numb + nfi.NegativeSign; case 12: return nfi.CurrencySymbol + " " + nfi.NegativeSign + numb; case 13: return numb + nfi.NegativeSign + " " + nfi.CurrencySymbol; case 14: return "(" + nfi.CurrencySymbol + " " + numb + ")"; case 15: return "(" + numb + " " + nfi.CurrencySymbol + ")"; default: throw new ArgumentException(Locale.GetText( "Invalid CurrencyNegativePattern")); } } else { switch (nfi.CurrencyPositivePattern) { case 0: return nfi.CurrencySymbol + numb ; case 1: return numb + nfi.CurrencySymbol; case 2: return nfi.CurrencySymbol + " " + numb; case 3: return numb + " " + nfi.CurrencySymbol; default: throw new ArgumentException(Locale.GetText( "invalid CurrencyPositivePattern")); } } } private string FormatExponential (Format formatData, double value, NumberFormatInfo nfi, int precision, char exp_char) { StringBuilder sb = new StringBuilder(); precision = (precision >= 0) ? precision : 6; int decimals = precision; long mantissa; int exponent; Normalize (formatData, value, precision, out mantissa, out exponent); if (formatData.dec_len > precision) { double aux = mantissa; for (int i = 0; i < formatData.dec_len - precision; i++) { aux /= 10; } mantissa = (long) Round(aux); for (int i = 0; i < formatData.dec_len - precision; i++) { mantissa *= 10; } } bool not_null = false; if (mantissa != 0.0) { for (int i = 0; i < formatData.dec_len || mantissa >= 10; i++) { if ((not_null == false) && ((mantissa % 10) != 0)) { not_null = true; } if (not_null) { sb.Insert(0, (char)('0' + (mantissa % 10))); precision--; } mantissa /= 10; exponent++; } } if (decimals == 0) { sb = new StringBuilder(); sb.Append((char)('0' + (mantissa % 10))); } else { while (precision > 0) { sb.Append('0'); precision--; } if (sb.Length == 0) { sb.Insert(0, "0"); } sb.Insert (0, (char)('0' + (mantissa % 10)) + nfi.NumberDecimalSeparator); } if (exponent >= 0) { sb.Append(exp_char + nfi.PositiveSign); } else { sb.Append(exp_char + nfi.NegativeSign); } sb.Append(Math.Abs(exponent).ToString("000")); if (value < 0.0) { sb.Insert(0, nfi.NegativeSign); } return sb.ToString(); } private string FormatFixedPoint (Format formatData, double value, NumberFormatInfo nfi, int precision) { StringBuilder sb = new StringBuilder(); precision = (precision >= 0) ? precision : nfi.NumberDecimalDigits; int decimals = precision; long mantissa; int exponent; Normalize (formatData, value, precision, out mantissa, out exponent); if (exponent >= 0) { while (decimals > 0) { sb.Append("0"); decimals--; } } else { int decimal_limit = -(decimals + 1); while (exponent < 0) { if (exponent > decimal_limit) { sb.Insert(0, (char)('0' + (mantissa % 10))); } mantissa /= 10; exponent++; decimals--; } if (decimals > 0) { sb.Append ('0', decimals); decimals = 0; } } if (precision != 0) { sb.Insert(0, nfi.NumberDecimalSeparator); } if (mantissa == 0) { sb.Insert(0, "0"); } else { while (exponent > 0) { sb.Insert(0, "0"); exponent--; } while (mantissa != 0) { sb.Insert(0, (char)('0' + (mantissa % 10))); mantissa /= 10; } } if (value < 0.0) { sb.Insert(0, nfi.NegativeSign); } return sb.ToString(); } private string FormatGeneral (Format formatData, double value, NumberFormatInfo nfi, int precision) { StringBuilder sb = new StringBuilder(); if (value == 0.0) { sb.Append("0"); } else { precision = (precision > 0) ? precision : formatData.dec_len+1; long mantissa; int exponent; Normalize (formatData, value, precision, out mantissa, out exponent); if (precision > 0) { double dmant = mantissa; for (int i = 0; i < formatData.dec_len - precision + 1; i++) { dmant /= 10; } mantissa = (long) Round (dmant); for (int i = 0; i < formatData.dec_len - precision + 1; i++) { mantissa *= 10; } } /* Calculate the exponent we would get using the scientific notation */ int snExponent = exponent; long snMantissa = mantissa; while (snMantissa >= 10) { snMantissa /= 10; snExponent++; } if (snExponent > -5 && snExponent < precision) { bool not_null = false; while (exponent < 0) { if ((not_null == false) && ((mantissa % 10) != 0)) { not_null = true; } if (not_null) { sb.Insert(0, (char)('0' + (mantissa % 10))); } mantissa /= 10; exponent++; } if (sb.Length != 0) { sb.Insert(0, nfi.NumberDecimalSeparator); } if (mantissa == 0) { sb.Insert(0, "0"); } else { while (mantissa > 0) { sb.Insert(0, (char)('0' + (mantissa % 10))); mantissa /= 10; } } } else { bool not_null = false; while (mantissa >= 10) { if ((not_null == false) && ((mantissa % 10) != 0)) { not_null = true; } if (not_null) { sb.Insert (0, (char)('0' + (mantissa % 10))); } mantissa /= 10; exponent++; } if (sb.Length != 0) sb.Insert(0, nfi.NumberDecimalSeparator); sb.Insert(0, (char)('0' + (mantissa % 10)) ); if (exponent > 0) { sb.Append("E" + nfi.PositiveSign); } else { sb.Append("E" + nfi.NegativeSign); } sb.Append(Math.Abs(exponent).ToString("00")); } if (value < 0.0) { sb.Insert(0, nfi.NegativeSign); } } return sb.ToString(); } private string FormatNumber (Format formatData, double value, NumberFormatInfo nfi, int precision) { precision = (precision >= 0) ? precision : nfi.NumberDecimalDigits; string numb = FormatNumberInternal (formatData, value, nfi, precision); if (value < 0) { switch (nfi.NumberNegativePattern) { case 0: return "(" + numb + ")"; case 1: return nfi.NegativeSign + numb; case 2: return nfi.NegativeSign + " " + numb; case 3: return numb + nfi.NegativeSign; case 4: return numb + " " + nfi.NegativeSign; default: throw new ArgumentException(Locale.GetText( "Invalid NumberNegativePattern")); } } return numb; } private string FormatPercent (Format formatData, double value, NumberFormatInfo nfi, int precision) { precision = (precision >= 0) ? precision : nfi.PercentDecimalDigits; string numb = FormatNumberInternal (formatData, value*100, nfi, precision); if (value < 0) { switch (nfi.PercentNegativePattern) { case 0: return nfi.NegativeSign + numb + " " + nfi.PercentSymbol; case 1: return nfi.NegativeSign + numb + nfi.PercentSymbol; case 2: return nfi.NegativeSign + nfi.PercentSymbol + numb; default: throw new ArgumentException(Locale.GetText( "Invalid PercentNegativePattern")); } } else { switch (nfi.PercentPositivePattern) { case 0: return numb + " " + nfi.PercentSymbol; case 1: return numb + nfi.PercentSymbol; case 2: return nfi.PercentSymbol + numb; default: throw new ArgumentException(Locale.GetText( "invalid PercehtPositivePattern")); } } } private string FormatNumberInternal (Format formatData, double value, NumberFormatInfo nfi, int precision) { StringBuilder sb = new StringBuilder(); int decimals = precision; long mantissa; int exponent; Normalize (formatData, value, precision, out mantissa, out exponent); if (exponent >= 0) { while (decimals > 0) { sb.Append("0"); decimals--; } } else { int decimal_limit = -(decimals + 1); while (exponent < 0) { if (exponent > decimal_limit) { sb.Insert(0, (char)('0' + (mantissa % 10))); } mantissa /= 10; exponent++; decimals--; } if (decimals > 0) { sb.Append ('0', decimals); decimals = 0; } } if (precision != 0) { sb.Insert(0, nfi.NumberDecimalSeparator); } if (mantissa == 0) { sb.Insert(0, "0"); } else { int groupIndex = 0; int groupPos = 0; int groupSize = nfi.NumberGroupSizes[0]; if (groupSize == 0) groupSize = int.MaxValue; while (exponent > 0 || mantissa != 0) { if (groupPos == groupSize) { sb.Insert (0, nfi.NumberGroupSeparator); groupPos = 0; if (groupIndex < nfi.NumberGroupSizes.Length - 1) { groupIndex++; groupSize = nfi.NumberGroupSizes[groupIndex]; if (groupSize == 0) groupSize = int.MaxValue; } } if (exponent > 0) { sb.Insert (0, "0"); exponent--; } else { sb.Insert(0, (char)('0' + (mantissa % 10))); mantissa /= 10; } groupPos++; } } return sb.ToString(); } // from http://www.yoda.arachsys.com/csharp/floatingpoint.html // used with permission from original author private string FormatReversible (double value, NumberFormatInfo nfi, int precision) { // Translate the double into sign, exponent and mantissa. long bits = BitConverter.DoubleToInt64Bits (value); bool negative = (bits >> 63)==1; int exponent = (int) ((bits >> 52) & 0x7ffL); long mantissa = bits & 0xfffffffffffffL; // Subnormal numbers; exponent is effectively one higher, // but there's no extra normalisation bit in the mantissa if (exponent == 0) { exponent++; } // Normal numbers; leave exponent as it is but add extra // bit to the front of the mantissa else { mantissa = mantissa | (1L<<52); } // Bias the exponent. It's actually biased by 1023, but we're // treating the mantissa as m.0 rather than 0.m, so we need // to subtract another 52 from it. exponent -= 1075; if (mantissa == 0) { return "0"; } // Normalize while((mantissa & 1) == 0) { // i.e., Mantissa is even mantissa >>= 1; exponent++; } // Construct a new decimal expansion with the mantissa ArbitraryDecimal ad = new ArbitraryDecimal (mantissa); // If the exponent is less than 0, we need to repeatedly // divide by 2 - which is the equivalent of multiplying // by 5 and dividing by 10. if (exponent < 0) { for (int i=0; i < -exponent; i++) ad.MultiplyBy (5); ad.Shift (-exponent); } // Otherwise, we need to repeatedly multiply by 2 else { for (int i=0; i < exponent; i++) ad.MultiplyBy(2); } // Finally, return the string with an appropriate sign if (negative) return nfi.NegativeSign + ad.ToString (nfi); else return ad.ToString (nfi); } private string FormatCustom (Format formatData, double value, NumberFormatInfo nfi, string format) { int first_semicolon, second_semicolon, third_semicolon; first_semicolon = format.IndexOf(';'); second_semicolon = format.IndexOf(';', first_semicolon + 1); if (second_semicolon < 0) { if (first_semicolon == -1) { if (value < 0.0) { string result = FormatCustomParser (formatData, value, nfi, format); if (result == "0") { return "0"; } if (result.Length > 0) { result = nfi.NegativeSign + result; } return result; } return FormatCustomParser (formatData, value, nfi, format); } if (value < 0.0) { return FormatCustomParser (formatData, value, nfi, format.Substring(first_semicolon + 1)); } return FormatCustomParser (formatData, value, nfi, format.Substring(0, first_semicolon - 1)); } if (value > 0.0) { return FormatCustomParser (formatData, value, nfi, format.Substring(0, first_semicolon - 1)); } else if (value < 0.0) { return FormatCustomParser (formatData, value, nfi, format.Substring (first_semicolon + 1, second_semicolon - first_semicolon - 1)); } third_semicolon = second_semicolon < 0 ? - 1 : format.IndexOf (';', second_semicolon + 1); if (third_semicolon < 0) return FormatCustomParser (formatData, value, nfi, format.Substring(second_semicolon + 1)); else return FormatCustomParser (formatData, value, nfi, format.Substring(second_semicolon + 1, third_semicolon - second_semicolon - 1)); } private struct Flags { public int NumberOfColons; public bool Groupping; public bool Percent; public bool Permille; public int DotPos; public int ExpPos; public int FirstFormatPos; public int IntegralLength; public int DecimalLength; public int ExponentialLength; } private Flags AnalizeFormat (string format) { Flags f = new Flags(); f.NumberOfColons = 0; f.DotPos = -1; f.ExpPos = -1; f.Groupping = false; f.Percent = false; f.FirstFormatPos = -1; int aux = 0, i = 0, count = 0; foreach (char c in format) { switch (c) { case ',': aux++; break; case '0': case '#': if (f.FirstFormatPos < 0) { f.FirstFormatPos = i; } if (aux > 0) { f.Groupping = true; aux = 0; } if (count < 15) count++; break; case '.': if (f.DotPos >= 0) break; // ignore f.DotPos = i; f.IntegralLength = count; count = 0; if (aux > 0) { f.NumberOfColons = aux; aux = 0; } break; case '%': f.Percent = true; break; case '\u2030': f.Permille = true; break; case 'e': case 'E': f.DecimalLength = count; count = 0; f.ExpPos = i; break; } i++; } if (aux > 0) { f.NumberOfColons = aux; } if (f.DecimalLength > 0) { f.ExponentialLength = count; } else { f.DecimalLength = count; } return f; } private string FormatCustomParser (Format formatData, double value, NumberFormatInfo nfi, string format) { long mantissa; int exponent; Flags f = AnalizeFormat(format); if (f.FirstFormatPos < 0) { return format; } if (((f.Percent) || (f.Permille) || (f.NumberOfColons > 0)) && (f.ExpPos < 0)) { int len = f.DecimalLength; int exp = 0; if (f.Percent) { len += 2; exp += 2; } else if (f.Permille) { len += 3; exp += 3; } if (f.NumberOfColons > 0) { len -= (3 * f.NumberOfColons); exp -= 3 * f.NumberOfColons; } if (len < 0) { len = 0; } value = Round(value, len); Normalize (formatData, value, 15, out mantissa, out exponent); exponent += exp; } else { value = Round(value, f.DecimalLength); Normalize (formatData, value, 15, out mantissa, out exponent); } StringBuilder sb = new StringBuilder(); if (f.ExpPos > 0) { StringBuilder sb_decimal = new StringBuilder(); while (mantissa > 0) { sb_decimal.Insert(0, (char)('0' + (mantissa % 10))); mantissa /= 10; exponent++; } exponent--; int k; for (k = sb_decimal.Length - 1; k >= 0 && sb_decimal[k] == '0'; k--); sb_decimal.Remove(k + 1, sb_decimal.Length - k - 1); for (int i = f.DotPos - 2; i >= 0; i--) { char c = format[i]; if (i > 0 && format[i-1] == '\\') { sb.Insert(0, c); i -= 2; continue; } switch (c) { case ',': case '#': break; case '0': sb.Insert(0, '0'); break; default: sb.Insert(0, c); break; } } sb.Append(sb_decimal[0]); sb.Append(nfi.NumberDecimalSeparator); for (int j = 1, i = f.DotPos + 1; i < f.ExpPos; i++) { char c = format[i]; switch (c) { case '\\': sb.Append(format[++i]); break; case '0': if (j >= sb_decimal.Length) { sb.Append('0'); break; } goto case '#'; case '#': if (j < sb_decimal.Length) { if ((i == f.ExpPos - 1) && (j < sb_decimal.Length - 1)) { int a = sb_decimal[j] - '0'; int b = sb_decimal[j+1] - '0'; if (((b == 5) && ((a % 2) == 0)) || (b > 5)) { a++; } sb.Append((char)('0' + (a % 10))); } else { sb.Append(sb_decimal[j++]); } } break; default: sb.Append(c); break; } } sb.Append(format[f.ExpPos]); if (exponent < 0) { sb.Append('-'); } int fin, inicio; inicio = f.ExpPos + 1; if (format[inicio] == '-') { inicio++; } else if (format[inicio] == '+') { if (exponent >= 0) { sb.Append('+'); } inicio++; } fin = inicio; while (fin < format.Length && format[fin++] == '0'); StringBuilder sb_exponent = new StringBuilder(); exponent = Math.Abs(exponent); while (exponent > 0) { sb_exponent.Insert(0, (char)('0' + (exponent % 10))); exponent /= 10; } while (sb_exponent.Length < (fin - inicio)) { sb_exponent.Insert(0, '0'); } sb.Append(sb_exponent.ToString()); for (int i = fin; i < format.Length; i++) { sb.Append(format[i]); } return sb.ToString(); } else { f.ExpPos = format.Length; } if (f.DotPos < 0) { while (exponent < 0) { mantissa = (long) Round((double)mantissa / 10); exponent++; } f.DotPos = format.Length; } else { StringBuilder sb_decimal = new StringBuilder(); while (exponent < 0) { sb_decimal.Insert(0, (char)('0' + (mantissa % 10))); mantissa /= 10; exponent++; } int k; for (k = sb_decimal.Length - 1; k >= 0 && sb_decimal[k] == '0'; k--); sb_decimal.Remove(k + 1, sb_decimal.Length - k - 1); if (sb_decimal.Length > 0) { sb.Append(nfi.NumberDecimalSeparator); } else if (format[f.DotPos + 1] == '0') { sb.Append(nfi.NumberDecimalSeparator); } bool terminado = false; for (int j = 0, i = f.DotPos + 1; i < f.ExpPos; i++) { if (format[i] == '0' || format[i] == '#') { if (j < sb_decimal.Length) { sb.Append(sb_decimal[j++]); } else if (format[i] == '0' && !terminado) { sb.Append('0'); } else if (format[i] == '#' && !terminado) { terminado = true; } } else if (format[i] == '\\') { sb.Append(format[++i]); } else if (format [i] == '%') sb.Append (nfi.PercentSymbol); else if (format [i] == '\u2030') sb.Append (nfi.PerMilleSymbol); else { sb.Append(format[i]); } } } int gro = 0; for (int i = f.DotPos - 1; i >= f.FirstFormatPos; i--) { if (format[i] == '#' || format[i] == '0') { if (exponent > 0 || mantissa > 0 || format[i] == '0') { if (f.Groupping && gro == nfi.NumberGroupSizes[0]) { sb.Insert(0, nfi.NumberGroupSeparator); gro = 0; } gro++; if (exponent > 0) { sb.Insert(0, '0'); exponent--; } else if (mantissa > 0) { sb.Insert(0, (char)('0' + (mantissa % 10))); mantissa /= 10; } else if (format[i] == '0') { sb.Insert(0, '0'); } } } else if (format [i] == '%') sb.Insert (0, nfi.PercentSymbol); else if (format [i] == '\u2030') sb.Insert (0, nfi.PerMilleSymbol); else if (format[i] != ',') { sb.Insert(0, format[i]); } else if (i > 0 && format[i-1] == '\\') { sb.Insert(0, format[i]); i -= 2; } } while (exponent > 0) { if (f.Groupping && gro == nfi.NumberGroupSizes[0]) { sb.Insert(0, nfi.NumberGroupSeparator); gro = 0; } gro++; sb.Insert(0, '0'); exponent--; } while (mantissa > 0) { if (f.Groupping && gro == nfi.NumberGroupSizes[0]) { sb.Insert(0, nfi.NumberGroupSeparator); gro = 0; } gro++; sb.Insert(0, (char)('0' + (mantissa % 10))); mantissa /= 10; } for (int i = f.FirstFormatPos - 1; i >= 0; i--) { if (format [i] == '%') sb.Insert (0, nfi.PercentSymbol); else if (format [i] == '\u2030') sb.Insert (0, nfi.PerMilleSymbol); else if (format [i] != '.') sb.Insert(0, format[i]); } return sb.ToString(); } } // from http://www.yoda.arachsys.com/csharp/floatingpoint.html // used with permission from original author internal class ArbitraryDecimal { /// Digits in the decimal expansion, one byte per digit byte[] digits; /// /// How many digits are *after* the decimal point /// int decimalPoint=0; /// /// Constructs an arbitrary decimal expansion from the given long. /// The long must not be negative. /// internal ArbitraryDecimal (long x) { string tmp = x.ToString (CultureInfo.InvariantCulture); digits = new byte [tmp.Length]; for (int i=0; i < tmp.Length; i++) digits[i] = (byte) (tmp[i] - '0'); Normalize (); } /// /// Multiplies the current expansion by the given amount, which should /// only be 2 or 5. /// internal void MultiplyBy (int amount) { byte[] result = new byte [digits.Length+1]; for (int i=digits.Length-1; i >= 0; i--) { int resultDigit = digits [i] * amount + result [i+1]; result [i] = (byte)(resultDigit / 10); result [i+1] = (byte)(resultDigit % 10); } if (result [0] != 0) { digits = result; } else { Array.Copy (result, 1, digits, 0, digits.Length); } Normalize (); } /// /// Shifts the decimal point; a negative value makes /// the decimal expansion bigger (as fewer digits come after the /// decimal place) and a positive value makes the decimal /// expansion smaller. /// internal void Shift (int amount) { decimalPoint += amount; } /// /// Removes leading/trailing zeroes from the expansion. /// internal void Normalize () { int first; for (first=0; first < digits.Length; first++) { if (digits [first] != 0) break; } int last; for (last = digits.Length - 1; last >= 0; last--) { if (digits [last] != 0) break; } if ((first == 0) && (last == digits.Length - 1)) return; byte[] tmp = new byte [last-first+1]; for (int i=0; i < tmp.Length; i++) tmp [i] = digits [i + first]; decimalPoint -= digits.Length - (last + 1); digits = tmp; } /// /// Converts the value to a proper decimal string representation. /// public string ToString (NumberFormatInfo nfi) { char[] digitString = new char [digits.Length]; for (int i=0; i < digits.Length; i++) digitString [i] = (char)(digits [i] + '0'); // Simplest case - nothing after the decimal point, // and last real digit is non-zero, eg value=35 if (decimalPoint == 0) { return new string (digitString); } // Fairly simple case - nothing after the decimal // point, but some 0s to add, eg value=350 if (decimalPoint < 0) { return new string (digitString) + new string ('0', -decimalPoint); } // Nothing before the decimal point, eg 0.035 if (decimalPoint >= digitString.Length) { return "0" + nfi.NumberDecimalSeparator + new string ('0',(decimalPoint-digitString.Length))+ new string (digitString); } // Most complicated case - part of the string comes // before the decimal point, part comes after it, // eg 3.5 return new string (digitString, 0, digitString.Length - decimalPoint) + nfi.NumberDecimalSeparator + new string (digitString, digitString.Length - decimalPoint, decimalPoint); } } }