// ==++== // // Copyright (c) Microsoft Corporation. All rights reserved. // // ==--== // The BigNumber class implements methods for formatting and parsing // big numeric values. To format and parse numeric values, applications should // use the Format and Parse methods provided by the numeric // classes (BigInteger). Those // Format and Parse methods share a common implementation // provided by this class, and are thus documented in detail here. // // Formatting // // The Format methods provided by the numeric classes are all of the // form // // public static String Format(XXX value, String format); // public static String Format(XXX value, String format, NumberFormatInfo info); // // where XXX is the name of the particular numeric class. The methods convert // the numeric value to a string using the format string given by the // format parameter. If the format parameter is null or // an empty string, the number is formatted as if the string "G" (general // format) was specified. The info parameter specifies the // NumberFormatInfo instance to use when formatting the number. If the // info parameter is null or omitted, the numeric formatting information // is obtained from the current culture. The NumberFormatInfo supplies // such information as the characters to use for decimal and thousand // separators, and the spelling and placement of currency symbols in monetary // values. // // Format strings fall into two categories: Standard format strings and // user-defined format strings. A format string consisting of a single // alphabetic character (A-Z or a-z), optionally followed by a sequence of // digits (0-9), is a standard format string. All other format strings are // used-defined format strings. // // A standard format string takes the form Axx, where A is an // alphabetic character called the format specifier and xx is a // sequence of digits called the precision specifier. The format // specifier controls the type of formatting applied to the number and the // precision specifier controls the number of significant digits or decimal // places of the formatting operation. The following table describes the // supported standard formats. // // C c - Currency format. The number is // converted to a string that represents a currency amount. The conversion is // controlled by the currency format information of the NumberFormatInfo // used to format the number. The precision specifier indicates the desired // number of decimal places. If the precision specifier is omitted, the default // currency precision given by the NumberFormatInfo is used. // // D d - Decimal format. This format is // supported for integral types only. The number is converted to a string of // decimal digits, prefixed by a minus sign if the number is negative. The // precision specifier indicates the minimum number of digits desired in the // resulting string. If required, the number will be left-padded with zeros to // produce the number of digits given by the precision specifier. // // E e Engineering (scientific) format. // The number is converted to a string of the form // "-d.ddd...E+ddd" or "-d.ddd...e+ddd", where each // 'd' indicates a digit (0-9). The string starts with a minus sign if the // number is negative, and one digit always precedes the decimal point. The // precision specifier indicates the desired number of digits after the decimal // point. If the precision specifier is omitted, a default of 6 digits after // the decimal point is used. The format specifier indicates whether to prefix // the exponent with an 'E' or an 'e'. The exponent is always consists of a // plus or minus sign and three digits. // // F f Fixed point format. The number is // converted to a string of the form "-ddd.ddd....", where each // 'd' indicates a digit (0-9). The string starts with a minus sign if the // number is negative. The precision specifier indicates the desired number of // decimal places. If the precision specifier is omitted, the default numeric // precision given by the NumberFormatInfo is used. // // G g - General format. The number is // converted to the shortest possible decimal representation using fixed point // or scientific format. The precision specifier determines the number of // significant digits in the resulting string. If the precision specifier is // omitted, the number of significant digits is determined by the type of the // number being converted (10 for int, 19 for long, 7 for // float, 15 for double, 19 for Currency, and 29 for // Decimal). Trailing zeros after the decimal point are removed, and the // resulting string contains a decimal point only if required. The resulting // string uses fixed point format if the exponent of the number is less than // the number of significant digits and greater than or equal to -4. Otherwise, // the resulting string uses scientific format, and the case of the format // specifier controls whether the exponent is prefixed with an 'E' or an // 'e'. // // N n Number format. The number is // converted to a string of the form "-d,ddd,ddd.ddd....", where // each 'd' indicates a digit (0-9). The string starts with a minus sign if the // number is negative. Thousand separators are inserted between each group of // three digits to the left of the decimal point. The precision specifier // indicates the desired number of decimal places. If the precision specifier // is omitted, the default numeric precision given by the // NumberFormatInfo is used. // // X x - Hexadecimal format. This format is // supported for integral types only. The number is converted to a string of // hexadecimal digits. The format specifier indicates whether to use upper or // lower case characters for the hexadecimal digits above 9 ('X' for 'ABCDEF', // and 'x' for 'abcdef'). The precision specifier indicates the minimum number // of digits desired in the resulting string. If required, the number will be // left-padded with zeros to produce the number of digits given by the // precision specifier. // // Some examples of standard format strings and their results are shown in the // table below. (The examples all assume a default NumberFormatInfo.) // // Value Format Result // 12345.6789 C $12,345.68 // -12345.6789 C ($12,345.68) // 12345 D 12345 // 12345 D8 00012345 // 12345.6789 E 1.234568E+004 // 12345.6789 E10 1.2345678900E+004 // 12345.6789 e4 1.2346e+004 // 12345.6789 F 12345.68 // 12345.6789 F0 12346 // 12345.6789 F6 12345.678900 // 12345.6789 G 12345.6789 // 12345.6789 G7 12345.68 // 123456789 G7 1.234568E8 // 12345.6789 N 12,345.68 // 123456789 N4 123,456,789.0000 // 0x2c45e x 2c45e // 0x2c45e X 2C45E // 0x2c45e X8 0002C45E // // Format strings that do not start with an alphabetic character, or that start // with an alphabetic character followed by a non-digit, are called // user-defined format strings. The following table describes the formatting // characters that are supported in user defined format strings. // // // 0 - Digit placeholder. If the value being // formatted has a digit in the position where the '0' appears in the format // string, then that digit is copied to the output string. Otherwise, a '0' is // stored in that position in the output string. The position of the leftmost // '0' before the decimal point and the rightmost '0' after the decimal point // determines the range of digits that are always present in the output // string. // // # - Digit placeholder. If the value being // formatted has a digit in the position where the '#' appears in the format // string, then that digit is copied to the output string. Otherwise, nothing // is stored in that position in the output string. // // . - Decimal point. The first '.' character // in the format string determines the location of the decimal separator in the // formatted value; any additional '.' characters are ignored. The actual // character used as a the decimal separator in the output string is given by // the NumberFormatInfo used to format the number. // // , - Thousand separator and number scaling. // The ',' character serves two purposes. First, if the format string contains // a ',' character between two digit placeholders (0 or #) and to the left of // the decimal point if one is present, then the output will have thousand // separators inserted between each group of three digits to the left of the // decimal separator. The actual character used as a the decimal separator in // the output string is given by the NumberFormatInfo used to format the // number. Second, if the format string contains one or more ',' characters // immediately to the left of the decimal point, or after the last digit // placeholder if there is no decimal point, then the number will be divided by // 1000 times the number of ',' characters before it is formatted. For example, // the format string '0,,' will represent 100 million as just 100. Use of the // ',' character to indicate scaling does not also cause the formatted number // to have thousand separators. Thus, to scale a number by 1 million and insert // thousand separators you would use the format string '#,##0,,'. // // % - Percentage placeholder. The presence of // a '%' character in the format string causes the number to be multiplied by // 100 before it is formatted. The '%' character itself is inserted in the // output string where it appears in the format string. // // E+ E- e+ e- - Scientific notation. // If any of the strings 'E+', 'E-', 'e+', or 'e-' are present in the format // string and are immediately followed by at least one '0' character, then the // number is formatted using scientific notation with an 'E' or 'e' inserted // between the number and the exponent. The number of '0' characters following // the scientific notation indicator determines the minimum number of digits to // output for the exponent. The 'E+' and 'e+' formats indicate that a sign // character (plus or minus) should always precede the exponent. The 'E-' and // 'e-' formats indicate that a sign character should only precede negative // exponents. // // \ - Literal character. A backslash character // causes the next character in the format string to be copied to the output // string as-is. The backslash itself isn't copied, so to place a backslash // character in the output string, use two backslashes (\\) in the format // string. // // 'ABC' "ABC" - Literal string. Characters // enclosed in single or double quotation marks are copied to the output string // as-is and do not affect formatting. // // ; - Section separator. The ';' character is // used to separate sections for positive, negative, and zero numbers in the // format string. // // Other - All other characters are copied to // the output string in the position they appear. // // For fixed point formats (formats not containing an 'E+', 'E-', 'e+', or // 'e-'), the number is rounded to as many decimal places as there are digit // placeholders to the right of the decimal point. If the format string does // not contain a decimal point, the number is rounded to the nearest // integer. If the number has more digits than there are digit placeholders to // the left of the decimal point, the extra digits are copied to the output // string immediately before the first digit placeholder. // // For scientific formats, the number is rounded to as many significant digits // as there are digit placeholders in the format string. // // To allow for different formatting of positive, negative, and zero values, a // user-defined format string may contain up to three sections separated by // semicolons. The results of having one, two, or three sections in the format // string are described in the table below. // // Sections: // // One - The format string applies to all values. // // Two - The first section applies to positive values // and zeros, and the second section applies to negative values. If the number // to be formatted is negative, but becomes zero after rounding according to // the format in the second section, then the resulting zero is formatted // according to the first section. // // Three - The first section applies to positive // values, the second section applies to negative values, and the third section // applies to zeros. The second section may be left empty (by having no // characters between the semicolons), in which case the first section applies // to all non-zero values. If the number to be formatted is non-zero, but // becomes zero after rounding according to the format in the first or second // section, then the resulting zero is formatted according to the third // section. // // For both standard and user-defined formatting operations on values of type // float and double, if the value being formatted is a NaN (Not // a Number) or a positive or negative infinity, then regardless of the format // string, the resulting string is given by the NaNSymbol, // PositiveInfinitySymbol, or NegativeInfinitySymbol property of // the NumberFormatInfo used to format the number. // // Parsing // // The Parse methods provided by the numeric classes are all of the form // // public static XXX Parse(String s); // public static XXX Parse(String s, int style); // public static XXX Parse(String s, int style, NumberFormatInfo info); // // where XXX is the name of the particular numeric class. The methods convert a // string to a numeric value. The optional style parameter specifies the // permitted style of the numeric string. It must be a combination of bit flags // from the NumberStyles enumeration. The optional info parameter // specifies the NumberFormatInfo instance to use when parsing the // string. If the info parameter is null or omitted, the numeric // formatting information is obtained from the current culture. // // Numeric strings produced by the Format methods using the Currency, // Decimal, Engineering, Fixed point, General, or Number standard formats // (the C, D, E, F, G, and N format specifiers) are guaranteed to be parseable // by the Parse methods if the NumberStyles.Any style is // specified. Note, however, that the Parse methods do not accept // NaNs or Infinities. // namespace System.Numerics { using System; using System.Diagnostics.Contracts; using System.Globalization; using System.Runtime.CompilerServices; using System.Security; using System.Text; using Conditional = System.Diagnostics.ConditionalAttribute; internal static class BigNumber { #if !SILVERLIGHT || FEATURE_NETCORE private const NumberStyles InvalidNumberStyles = ~(NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite | NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingSign | NumberStyles.AllowParentheses | NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands | NumberStyles.AllowExponent | NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier); internal struct BigNumberBuffer { public StringBuilder digits; public int precision; public int scale; public bool sign; // negative sign exists public static BigNumberBuffer Create() { BigNumberBuffer number = new BigNumberBuffer(); number.digits = new StringBuilder(); return number; } } internal static bool TryValidateParseStyleInteger(NumberStyles style, out ArgumentException e) { // Check for undefined flags if ((style & InvalidNumberStyles) != 0) { e = new ArgumentException(SR.GetString(SR.Argument_InvalidNumberStyles, "style")); return false; } if ((style & NumberStyles.AllowHexSpecifier) != 0) { // Check for hex number if ((style & ~NumberStyles.HexNumber) != 0) { e = new ArgumentException(SR.GetString(SR.Argument_InvalidHexStyle)); return false; } } e = null; return true; } [SecuritySafeCritical] internal unsafe static Boolean TryParseBigInteger(String value, NumberStyles style, NumberFormatInfo info, out BigInteger result) { result = BigInteger.Zero; ArgumentException e; if (!TryValidateParseStyleInteger(style, out e)) throw e; // TryParse still throws ArgumentException on invalid NumberStyles BigNumberBuffer bignumber = BigNumberBuffer.Create(); Byte * numberBufferBytes = stackalloc Byte[Number.NumberBuffer.NumberBufferBytes]; Number.NumberBuffer number = new Number.NumberBuffer(numberBufferBytes); result = 0; if (!Number.TryStringToNumber(value, style, ref number, bignumber.digits, info, false)) { return false; } bignumber.precision = number.precision; bignumber.scale = number.scale; bignumber.sign = number.sign; if ((style & NumberStyles.AllowHexSpecifier) != 0) { if (!HexNumberToBigInteger(ref bignumber, ref result)) { return false; } } else { if (!NumberToBigInteger(ref bignumber, ref result)) { return false; } } return true; } internal unsafe static BigInteger ParseBigInteger(String value, NumberStyles style, NumberFormatInfo info) { if (value == null) throw new ArgumentNullException("value"); ArgumentException e; if (!TryValidateParseStyleInteger(style, out e)) throw e; BigInteger result = BigInteger.Zero; if (!TryParseBigInteger(value, style, info, out result)) { throw new FormatException(SR.GetString(SR.Overflow_ParseBigInteger)); } return result; } private unsafe static Boolean HexNumberToBigInteger(ref BigNumberBuffer number, ref BigInteger value) { if (number.digits == null || number.digits.Length == 0) return false; int len = number.digits.Length - 1; // ignore trailing '\0' byte[] bits = new byte[(len / 2) + (len % 2)]; bool shift = false; bool isNegative = false; int bitIndex = 0; // parse the string into a little-endian two's complement byte array // string value : O F E B 7 \0 // string index (i) : 0 1 2 3 4 5 <-- // byte[] (bitIndex): 2 1 1 0 0 <-- // for (int i = len-1; i > -1; i--) { char c = number.digits[i]; byte b; if (c >= '0' && c <= '9') { b = (byte)(c - '0'); } else if (c >= 'A' && c <= 'F') { b = (byte)((c - 'A') + 10); } else { Contract.Assert(c >= 'a' && c <= 'f'); b = (byte)((c - 'a') + 10); } if (i == 0 && (b & 0x08) == 0x08) isNegative = true; if (shift) { bits[bitIndex] = (byte)(bits[bitIndex] | (b << 4)); bitIndex++; } else { bits[bitIndex] = isNegative ? (byte)(b | 0xF0) : (b); } shift = !shift; } value = new BigInteger(bits); return true; } private unsafe static Boolean NumberToBigInteger(ref BigNumberBuffer number, ref BigInteger value) { Int32 i = number.scale; Int32 cur = 0; value = 0; while (--i >= 0) { value *= 10; if (number.digits[cur] != '\0') { value += (Int32)(number.digits[cur++] - '0'); } } while (number.digits[cur] != '\0') { if (number.digits[cur++] != '0') return false; // disallow non-zero trailing decimal places } if (number.sign) { value = -value; } return true; } #endif //!SILVERLIGHT ||FEATURE_NETCORE // this function is consistent with VM\COMNumber.cpp!COMNumber::ParseFormatSpecifier internal static char ParseFormatSpecifier(String format, out Int32 digits) { digits = -1; if (String.IsNullOrEmpty(format)) { return 'R'; } int i = 0; char ch = format[i]; if (ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z') { i++; int n = -1; if (i < format.Length && format[i] >= '0' && format[i] <= '9') { n = format[i++] - '0'; while (i < format.Length && format[i] >= '0' && format[i] <= '9') { n = n * 10 + (format[i++] - '0'); if (n >= 10) break; } } if (i >= format.Length || format[i] == '\0') { digits = n; return ch; } } return (char)0; // custom format } private static String FormatBigIntegerToHexString(BigInteger value, char format, int digits, NumberFormatInfo info) { StringBuilder sb = new StringBuilder(); byte[] bits = value.ToByteArray(); String fmt = null; int cur = bits.Length-1; if (cur > -1) { // [FF..F8] drop the high F as the two's complement negative number remains clear // [F7..08] retain the high bits as the two's complement number is wrong without it // [07..00] drop the high 0 as the two's complement positive number remains clear bool clearHighF = false; byte head = bits[cur]; if (head > 0xF7) { head -= 0xF0; clearHighF = true; } if (head < 0x08 || clearHighF) { // {0xF8-0xFF} print as {8-F} // {0x00-0x07} print as {0-7} fmt = String.Format(CultureInfo.InvariantCulture, "{0}1", format); sb.Append(head.ToString(fmt, info)); cur--; } } if (cur > -1) { fmt = String.Format(CultureInfo.InvariantCulture, "{0}2", format); while (cur > -1) { sb.Append(bits[cur--].ToString(fmt, info)); } } if (digits > 0 && digits > sb.Length) { // insert leading zeros. User specified "X5" so we create "0ABCD" instead of "ABCD" sb.Insert(0, (value._sign >= 0 ? ("0") : (format == 'x' ? "f" : "F")), digits - sb.Length); } return sb.ToString(); } // // internal [unsafe] static String FormatBigInteger(BigInteger value, String format, NumberFormatInfo info) { // #if !SILVERLIGHT ||FEATURE_NETCORE [SecuritySafeCritical] #endif // !SILVERLIGHT ||FEATURE_NETCORE internal #if !SILVERLIGHT ||FEATURE_NETCORE unsafe #endif //!SILVERLIGHT ||FEATURE_NETCORE static String FormatBigInteger(BigInteger value, String format, NumberFormatInfo info) { int digits = 0; char fmt = ParseFormatSpecifier(format, out digits); if (fmt == 'x' || fmt == 'X') return FormatBigIntegerToHexString(value, fmt, digits, info); bool decimalFmt = (fmt == 'g' || fmt == 'G' || fmt == 'd' || fmt == 'D' || fmt == 'r' || fmt == 'R'); #if SILVERLIGHT ||FEATURE_NETCORE || MONO if (!decimalFmt) { // Silverlight supports invariant formats only throw new FormatException(SR.GetString(SR.Format_InvalidFormatSpecifier)); } #endif //SILVERLIGHT ||FEATURE_NETCORE || MONO if (value._bits == null) { if (fmt == 'g' || fmt == 'G' || fmt == 'r' || fmt == 'R') { if (digits > 0) format = String.Format(CultureInfo.InvariantCulture, "D{0}", digits.ToString(CultureInfo.InvariantCulture)); else format = "D"; } return value._sign.ToString(format, info); } // First convert to base 10^9. const uint kuBase = 1000000000; // 10^9 const int kcchBase = 9; int cuSrc = BigInteger.Length(value._bits); int cuMax; try { cuMax = checked(cuSrc * 10 / 9 + 2); } catch (OverflowException e) { throw new FormatException(SR.GetString(SR.Format_TooLarge), e); } uint[] rguDst = new uint[cuMax]; int cuDst = 0; for (int iuSrc = cuSrc; --iuSrc >= 0; ) { uint uCarry = value._bits[iuSrc]; for (int iuDst = 0; iuDst < cuDst; iuDst++) { Contract.Assert(rguDst[iuDst] < kuBase); ulong uuRes = NumericsHelpers.MakeUlong(rguDst[iuDst], uCarry); rguDst[iuDst] = (uint)(uuRes % kuBase); uCarry = (uint)(uuRes / kuBase); } if (uCarry != 0) { rguDst[cuDst++] = uCarry % kuBase; uCarry /= kuBase; if (uCarry != 0) rguDst[cuDst++] = uCarry; } } int cchMax; try { // Each uint contributes at most 9 digits to the decimal representation. cchMax = checked(cuDst * kcchBase); } catch (OverflowException e) { throw new FormatException(SR.GetString(SR.Format_TooLarge), e); } if (decimalFmt) { if (digits > 0 && digits > cchMax) cchMax = digits; if (value._sign < 0) { try { // Leave an extra slot for a minus sign. cchMax = checked(cchMax + info.NegativeSign.Length); } catch (OverflowException e) { throw new FormatException(SR.GetString(SR.Format_TooLarge), e); } } } int rgchBufSize; try { // We'll pass the rgch buffer to native code, which is going to treat it like a string of digits, so it needs // to be null terminated. Let's ensure that we can allocate a buffer of that size. rgchBufSize = checked(cchMax + 1); } catch (OverflowException e) { throw new FormatException(SR.GetString(SR.Format_TooLarge), e); } char[] rgch = new char[rgchBufSize]; int ichDst = cchMax; for (int iuDst = 0; iuDst < cuDst - 1; iuDst++) { uint uDig = rguDst[iuDst]; Contract.Assert(uDig < kuBase); for (int cch = kcchBase; --cch >= 0; ) { rgch[--ichDst] = (char)('0' + uDig % 10); uDig /= 10; } } for (uint uDig = rguDst[cuDst - 1]; uDig != 0; ) { rgch[--ichDst] = (char)('0' + uDig % 10); uDig /= 10; } #if !SILVERLIGHT ||FEATURE_NETCORE if (!decimalFmt) { // // Go to the VM for GlobLoc aware formatting // Byte * numberBufferBytes = stackalloc Byte[Number.NumberBuffer.NumberBufferBytes]; Number.NumberBuffer number = new Number.NumberBuffer(numberBufferBytes); // sign = true for negative and false for 0 and positive values number.sign = (value._sign < 0); // the cut-off point to switch (G)eneral from (F)ixed-point to (E)xponential form number.precision = 29; number.digits[0] = '\0'; number.scale = cchMax - ichDst; int maxDigits = Math.Min(ichDst + 50, cchMax); for (int i = ichDst; i < maxDigits; i++) { number.digits[i - ichDst] = rgch[i]; } fixed(char* pinnedExtraDigits = rgch) { return Number.FormatNumberBuffer(number.PackForNative(), format, info, pinnedExtraDigits + ichDst); } } #endif //!SILVERLIGHT ||FEATURE_NETCORE // Format Round-trip decimal // This format is supported for integral types only. The number is converted to a string of // decimal digits (0-9), prefixed by a minus sign if the number is negative. The precision // specifier indicates the minimum number of digits desired in the resulting string. If required, // the number is padded with zeros to its left to produce the number of digits given by the // precision specifier. int numDigitsPrinted = cchMax - ichDst; while (digits > 0 && digits > numDigitsPrinted) { // pad leading zeros rgch[--ichDst] = '0'; digits--; } if (value._sign < 0) { String negativeSign = info.NegativeSign; for (int i = info.NegativeSign.Length - 1; i > -1; i--) rgch[--ichDst] = info.NegativeSign[i]; } return new String(rgch, ichDst, cchMax - ichDst); } } }