// ==++== // // Copyright (c) Microsoft Corporation. All rights reserved. // // ==--== namespace System { using System; using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using NumberFormatInfo = System.Globalization.NumberFormatInfo; using NumberStyles = System.Globalization.NumberStyles; // The Number class implements methods for formatting and parsing // numeric values. To format and parse numeric values, applications should // use the Format and Parse methods provided by the numeric // classes (Byte, Int16, Int32, Int64, // Single, Double, and Decimal). 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); // // 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. // // 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. // // // FormatDescription // // DdDecimal 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. // // EeEngineering (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. // // FfFixed 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 is used. // // GgGeneral 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, 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'. // // NnNumber 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 is used. // // XxHexadecimal 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. // // // ValueFormatResult // 12345.6789C$12,345.68 // -12345.6789C($12,345.68) // 12345D12345 // 12345D800012345 // 12345.6789E1.234568E+004 // 12345.6789E101.2345678900E+004 // 12345.6789e41.2346e+004 // 12345.6789F12345.68 // 12345.6789F012346 // 12345.6789F612345.678900 // 12345.6789G12345.6789 // 12345.6789G712345.68 // 123456789G71.234568E8 // 12345.6789N12,345.68 // 123456789N4123,456,789.0000 // 0x2c45ex2c45e // 0x2c45eX2C45E // 0x2c45eX80002C45E // // // 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. // // // CharacterDescription // // 0Digit 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. // // OtherAll 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. // // // SectionsResult // // OneThe format string applies to all // values. // // TwoThe 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. // // ThreeThe 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); // // 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 // Decimal, Engineering, Fixed point, General, or Number standard formats // (the C, D, E, F, G, and N format specifiers) are guaranteed to be parsable // by the Parse methods if the NumberStyles.Any style is // specified. Note, however, that the Parse methods do not accept // NaNs or Infinities. // internal class Number { private Number() { } private Number(ref Decimal value) { this.precision = DecimalPrecision; this.negative = value.negative; int index = NumberMaxDigits; while (value.mid32 != 0 || value.hi32 != 0) { Int32ToDecChars(this.digits, ref index, DecDivMod1E9(ref value), 9); } Int32ToDecChars(this.digits, ref index, value.lo32, 0); int digitCount = NumberMaxDigits - index; this.scale = digitCount - value.scale; int destIndex = 0; while (digitCount > 0) { this.digits[destIndex] = this.digits[index]; destIndex++; index++; digitCount--; } this.digits[destIndex] = '\0'; } private Number(int value) { this.precision = INT32_PRECISION; if (value >= 0) { this.negative = false; } else { this.negative = true; value = -value; } int index = INT32_PRECISION; Int32ToDecChars(this.digits, ref index, unchecked((uint) value), 0); int digitCount = INT32_PRECISION - index; int destIndex = 0; this.scale = digitCount; while (digitCount > 0) { this.digits[destIndex] = this.digits[index]; destIndex++; index++; digitCount--; } this.digits[destIndex] = '\0'; } private Number(uint value) { this.precision = INT32_PRECISION; this.negative = false; int index = INT32_PRECISION; Int32ToDecChars(this.digits, ref index, value, 0); int digitCount = INT32_PRECISION - index; int destIndex = 0; this.scale = digitCount; while (digitCount > 0) { this.digits[destIndex] = this.digits[index]; destIndex++; index++; digitCount--; } this.digits[destIndex] = '\0'; } private Number(long value) { this.precision = INT64_PRECISION; if (value >= 0) { this.negative = false; } else { this.negative = true; value = -value; } int index = INT64_PRECISION; Int64ToDecChars(this.digits, ref index, unchecked((ulong) value), 0); int digitCount = INT64_PRECISION - index; int destIndex = 0; this.scale = digitCount; while (digitCount > 0) { this.digits[destIndex] = this.digits[index]; destIndex++; index++; digitCount--; } this.digits[destIndex] = '\0'; } private Number(ulong value) { this.precision = INT64_PRECISION; this.negative = false; int index = INT64_PRECISION; Int64ToDecChars(this.digits, ref index, value, 0); int digitCount = INT64_PRECISION - index; int destIndex = 0; this.scale = digitCount; while (digitCount > 0) { this.digits[destIndex] = this.digits[index]; destIndex++; index++; digitCount--; } this.digits[destIndex] = '\0'; } // inline this!!! private static bool EndString(String s, int p) { return p==s.Length || s[p] == '\0'; } // inline void AddStringRef(wchar** ppBuffer, STRINGREF strRef) private static int AddStringRef(char[] ppBuffer, int index, String strRef) { int length = strRef.Length; for (int str = 0; str < length; index++, str++) { ppBuffer[index] = strRef[str]; } return index; } // markples: see also Lightning\Src\VM\COMNumber.cpp:: // wchar* MatchChars(wchar* p, wchar* str) // will now return -1 instead of NULL on failure private static int MatchChars(String str1, int p, String str) { int str_i=0; if (EndString(str,str_i)) return -1; for (; !EndString(str,str_i); p++, str_i++) { if (str1[p] != str[str_i]) //We only hurt the failure case { if ((str[str_i] == 0xA0) && (str1[p] == 0x20)) // This fix is for French or Kazakh cultures. // Since a user cannot type 0xA0 as a space // character we use 0x20 space character // instead to mean the same. continue; return -1; } } return p; } // REVIEW: (markples) why is this the only thing that is hardcoded // anywhere in the system? private bool ISWHITE(char ch) { return (((ch) == 0x20)||((ch) >= 0x09 && (ch) <= 0x0D)); } private const int STATE_SIGN = 0x0001; private const int STATE_PARENS = 0x0002; private const int STATE_DIGITS = 0x0004; private const int STATE_NONZERO = 0x0008; private const int STATE_DECIMAL = 0x0010; private const int STATE_HEXLEAD = 0x0020; // #defines in Lightning\Src\VM\COMNumber.cpp private const int NUMBER_MAXDIGITS = 31; private const int INT32_PRECISION = 10; private const int UINT32_PRECISION = 10; private const int INT64_PRECISION = 20; private const int UINT64_PRECISION = 20; private const int FLOAT_PRECISION = 7; private const int DOUBLE_PRECISION = 15; private const int MIN_BUFFER_SIZE = 105; private const int SCALE_NAN = unchecked((int)0x80000000); private const int SCALE_INF = 0x7FFFFFFF; private static String[] posPercentFormats = { "# %", "#%", "%#" }; // BUGBUG yslin: have to verify on the negative Percent // format for real format. private static String[] negPercentFormats = { "-# %", "-#%", "-%#" }; private static String[] negNumberFormats = { "(#)", "-#", "- #", "#-", "# -" }; private static String posNumberFormat = "#"; [MethodImpl(MethodImplOptions.InternalCall)] [GCAnnotation(GCOption.NOGC)] [StackBound(1024)] private static extern bool ecvt(double value, int digits, char[] buf, out int decimalpoint, out bool negative); // markples: see also Lightning\Src\VM\COMNumber.cpp:: // void DoubleToNumber(double value, int precision, NUMBER* number) private Number(double value, int precision) { this.precision = precision; ecvt(value, precision, digits, out this.scale, out this.negative); } // code below depends on seeing the null terminator... private static char Get(String str, int i) { return i= 2) { if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { p = 2; } } char ch = Get(str, p); while (true) { //Eat whitespace unless we've found a sign. if (ISWHITE(ch) && ((style & NumberStyles.AllowLeadingWhite) != 0) && (!((state & STATE_SIGN) != 0) || (((state & STATE_SIGN) != 0) && (NumberFormatInfo.numberNegativePattern == 2)))) { // Do nothing here. We will increase p at the end of the loop } else if ((signflag = (((style & NumberStyles.AllowLeadingSign) != 0) && !((state & STATE_SIGN) != 0))) && (next = MatchChars(str, p, NumberFormatInfo.positiveSign)) != -1) { state |= STATE_SIGN; p = next - 1; } else if (signflag && (next = MatchChars(str, p, NumberFormatInfo.negativeSign)) != -1){ state |= STATE_SIGN; this.negative = true; p = next - 1; } else if (ch == '(' && ((style & NumberStyles.AllowParentheses) != 0) && !((state & STATE_SIGN) != 0)) { state |= STATE_SIGN | STATE_PARENS; this.negative = true; } else { break; } ch = Get(str, ++p); } int digCount = 0; int digEnd = 0; while (true) { if ((ch >= '0' && ch <= '9') || ( ((style & NumberStyles.AllowHexSpecifier) != 0) && ((ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')))) { state |= STATE_DIGITS; if (ch != '0' || ((state & STATE_NONZERO) != 0)) { if (digCount < NUMBER_MAXDIGITS) { this.digits[digCount++] = ch; if (ch != '0') digEnd = digCount; } if (!((state & STATE_DECIMAL) != 0)) this.scale++; state |= STATE_NONZERO; } else if ((state & STATE_DECIMAL) != 0) this.scale--; } else if (((style & NumberStyles.AllowDecimalPoint) != 0) && !((state & STATE_DECIMAL) != 0) && ((next = MatchChars(str, p, decSep)) != -1)) { state |= STATE_DECIMAL; p = next - 1; } else if (((style & NumberStyles.AllowThousands) != 0) && ((state & STATE_DIGITS) != 0) && !((state & STATE_DECIMAL) != 0) && ((next = MatchChars(str, p, groupSep)) != -1)) { p = next - 1; } else { break; } ch = Get(str, ++p); } bool negExp = false; this.precision = digEnd; this.digits[digEnd] = '\0'; if ((state & STATE_DIGITS) != 0) { if ((ch == 'E' || ch == 'e') && ((style & NumberStyles.AllowExponent) != 0)) { int temp = p; ch = Get(str, ++p); if ((next = MatchChars(str, p, NumberFormatInfo.positiveSign)) != -1) { ch = Get(str, p = next); } else if ((next = MatchChars(str, p, NumberFormatInfo.negativeSign))!= -1){ ch = Get(str, p = next); negExp = true; } if (ch >= '0' && ch <= '9') { int exp = 0; do { exp = exp * 10 + (ch - '0'); ch = Get(str, ++p); if (exp>1000) { exp=9999; while(ch>='0' && ch<='9') { ch = Get(str, ++p); } } } while (ch >= '0' && ch <= '9'); if (negExp) exp = -exp; this.scale += exp; } else { p = temp; ch = Get(str, p); } } while (true) { if (ISWHITE(ch) && ((style & NumberStyles.AllowTrailingWhite) != 0)) { // do nothing } else if ((signflag = (((style & NumberStyles.AllowTrailingSign) != 0) && !((state & STATE_SIGN) != 0))) && (next = MatchChars(str, p, NumberFormatInfo.positiveSign)) != -1) { state |= STATE_SIGN; p = next - 1; } else if (signflag && (next = MatchChars(str, p, NumberFormatInfo.negativeSign)) != -1) { state |= STATE_SIGN; this.negative = true; p = next - 1; } else if (ch == ')' && ((state & STATE_PARENS) != 0)) { state &= ~STATE_PARENS; } else { break; } ch = Get(str, ++p); } if (!((state & STATE_PARENS) != 0)) { if (!((state & STATE_NONZERO) != 0)) { this.scale = 0; if (!((state & STATE_DECIMAL) != 0)) { this.negative = false; } } // *str = p; // return 1; return EndString(str, p); } } // *str = p; // return 0; return false; } // markples: see also Lightning\Src\VM\COMNumber.cpp:: //void StringToNumber(STRINGREF str, int options, // NUMBER* number, NUMFMTREF numfmt) private Number(String str, NumberStyles style) { { //THROWSCOMPLUSEXCEPTION(); if (str == null) { throw new NullReferenceException(); } if (!NumberFormatInfo.validForParseAsNumber) { throw new ArgumentException("Argument_AmbiguousNumberInfo"); } //wchar* p = str->GetBuffer(); if (!ParseNumber(str, style)) { throw new FormatException("Format_InvalidString"); } } } public static String FormatDecimal(Decimal value, String format) { // rusa: see also Lightning\Src\VM\COMNumber.cpp::FormatDecimal if (format == null) { throw new ArgumentNullException("format"); } Number number = new Number(ref value); return FPNumberToString(number, format); } public static String FormatDouble(double value, String format) { // rusa: see also Lightning\Src\VM\COMNumber.cpp::FormatDouble Number number; int digits; double dTest; char fmt = ParseFormatSpecifier(format, out digits); char val = (char) (fmt & 0xFFDF); int precision = DOUBLE_PRECISION; switch (val) { case 'R': //In order to give numbers that are both //friendly to display and round-trippable, we //parse the number using 15 digits and then //determine if it round trips to the same value. //If it does, we convert that NUMBER to a //string, otherwise we reparse using 17 digits //and display that. number = new Number(value, DOUBLE_PRECISION); if (number.scale == SCALE_NAN) { return NumberFormatInfo.nanSymbol; } if (number.scale == SCALE_INF) { return number.negative ? NumberFormatInfo.negativeInfinitySymbol : NumberFormatInfo.positiveInfinitySymbol; } NumberToDouble(number, out dTest); if (dTest == value) { return number.ToString('G', DOUBLE_PRECISION); } number = new Number(value, 17); return number.ToString('G', 17); case 'E': // Here we round values less than E14 to 15 digits if (digits > 14) { precision = 17; } break; case 'G': // Here we round values less than G15 to 15 // digits, G16 and G17 will not be touched if (digits > 15) { precision = 17; } break; } number = new Number(value, precision); return FPNumberToString(number, format); } public static String FormatInt32(int value, String format) { // rusa: see also Lightning\Src\VM\COMNumber.cpp::FormatInt32 int digits; char fmt = ParseFormatSpecifier(format, out digits); switch (fmt) { case 'g': case 'G': { if (digits > 0) break; goto case 'D'; } case 'd': case 'D': { return Int32ToDecString(value, digits, NumberFormatInfo.negativeSign); } case 'x': case 'X': { return Int32ToHexString(unchecked((uint) value), (char) (fmt-('X'-'A'+10)), digits); } default: { break; } } Number number = new Number(value); if (fmt == 0) { return number.ToStringFormat(format); } else { return number.ToString(fmt, digits); } } public static String FormatUInt32(uint value, String format) { // rusa: see also Lightning\Src\VM\COMNumber.cpp::FormatUInt32 int digits; char fmt = ParseFormatSpecifier(format, out digits); switch (fmt) { case 'g': case 'G': { if (digits > 0) break; goto case 'D'; } case 'd': case 'D': { return UInt32ToDecString(value, digits); } case 'x': case 'X': { return Int32ToHexString(value, (char) (fmt-('X'-'A'+10)), digits); } default: { break; } } Number number = new Number(value); if (fmt == 0) { return number.ToStringFormat(format); } else { return number.ToString(fmt, digits); } } public static String FormatInt64(long value, String format) { // rusa: see also Lightning\Src\VM\COMNumber.cpp::FormatInt64 int digits; char fmt = ParseFormatSpecifier(format, out digits); switch (fmt) { case 'g': case 'G': { if (digits > 0) break; goto case 'D'; } case 'd': case 'D': { return Int64ToDecString(value, digits, NumberFormatInfo.negativeSign); } case 'x': case 'X': { return Int64ToHexString(unchecked((ulong) value), (char) (fmt-('X'-'A'+10)), digits); } default: { break; } } Number number = new Number(value); if (fmt == 0) { return number.ToStringFormat(format); } else { return number.ToString(fmt, digits); } } public static String FormatUInt64(ulong value, String format) { // rusa: see also Lightning\Src\VM\COMNumber.cpp::FormatUInt64 int digits; char fmt = ParseFormatSpecifier(format, out digits); switch (fmt) { case 'g': case 'G': { if (digits > 0) break; goto case 'D'; } case 'd': case 'D': { return UInt64ToDecString(value, digits); } case 'x': case 'X': { return Int64ToHexString(value, (char) (fmt-('X'-'A'+10)), digits); } default: { break; } } Number number = new Number(value); if (fmt == 0) { return number.ToStringFormat(format); } else { return number.ToString(fmt, digits); } } public static String FormatSingle(float value, String format) { // rusa: see also Lightning\Src\VM\COMNumber.cpp::FormatSingle Number number; int digits; double dTest; double argsValue = value; char fmt = ParseFormatSpecifier(format, out digits); char val = (char) (fmt & 0xFFDF); int precision = FLOAT_PRECISION; switch (val) { case 'R': //In order to give numbers that are both //friendly to display and round-trippable, we //parse the number using 7 digits and then //determine if it round trips to the same value. //If it does, we convert that NUMBER to a //string, otherwise we reparse using 9 digits //and display that. number = new Number(argsValue, FLOAT_PRECISION); if (number.scale == SCALE_NAN) { return NumberFormatInfo.nanSymbol; } if (number.scale == SCALE_INF) { return number.negative ? NumberFormatInfo.negativeInfinitySymbol : NumberFormatInfo.positiveInfinitySymbol; } NumberToDouble(number, out dTest); // HACK: force restriction to float float[] hack = new float[1]; hack[0] = (float) dTest; float fTest = hack[0]; if (fTest == value) { return number.ToString('G', FLOAT_PRECISION); } number = new Number(argsValue, 9); return number.ToString('G', 9); case 'E': // Here we round values less than E14 to 15 digits if (digits > 6) { precision = 9; } break; case 'G': // Here we round values less than G15 to 15 // digits, G16 and G17 will not be touched if (digits > 7) { precision = 9; } break; } number = new Number(value, precision); return FPNumberToString(number, format); } public static Decimal ParseDecimal(String s, NumberStyles style) { // rusa: see also Lightning\Src\VM\COMNumber.cpp::ParseDecimal Number number = new Number(s, style); Decimal result; if (!NumberToDecimal(number, out result)) { throw new OverflowException("Overflow_Decimal"); } return result; } public static double ParseDouble(String s, NumberStyles style) { Number number = new Number(s, style); double result; NumberToDouble(number, out result); return result; } public static int ParseInt32(String s, NumberStyles style) { // rusa: see also Lightning\Src\VM\COMNumber.cpp::ParseInt32 Number number = new Number(s, style); int result; if ((style & NumberStyles.AllowHexSpecifier) != 0) { uint temp; if (!HexNumberToUInt32(number, out temp)) { throw new OverflowException("Overflow_Int32"); } result = unchecked((int) temp); } else { if (!NumberToInt32(number, out result)) { throw new OverflowException("Overflow_Int32"); } } return result; } public static uint ParseUInt32(String s, NumberStyles style) { // rusa: see also Lightning\Src\VM\COMNumber.cpp::ParseUInt32 Number number = new Number(s, style); uint result; if ((style & NumberStyles.AllowHexSpecifier) != 0) { if (!HexNumberToUInt32(number, out result)) { throw new OverflowException("Overflow_UInt32"); } } else { if (!NumberToUInt32(number, out result)) { throw new OverflowException("Overflow_UInt32"); } } return result; } public static long ParseInt64(String s, NumberStyles style) { // rusa: see also Lightning\Src\VM\COMNumber.cpp::ParseInt64 Number number = new Number(s, style); long result; if ((style & NumberStyles.AllowHexSpecifier) != 0) { ulong temp; if (!HexNumberToUInt64(number, out temp)) { throw new OverflowException("Overflow_UInt32"); } result = unchecked((long) temp); } else { if (!NumberToInt64(number, out result)) { throw new OverflowException("Overflow_UInt32"); } } return result; } public static ulong ParseUInt64(String s, NumberStyles style) { // rusa: see also Lightning\Src\VM\COMNumber.cpp::ParseUInt64 Number number = new Number(s, style); ulong result; if ((style & NumberStyles.AllowHexSpecifier) != 0) { if (!HexNumberToUInt64(number, out result)) { throw new OverflowException("Overflow_UInt32"); } } else { if (!NumberToUInt64(number, out result)) { throw new OverflowException("Overflow_UInt32"); } } return result; } public static float ParseSingle(String s, NumberStyles style) { return (float)ParseDouble(s, style); } // STRINGREF FPNumberToString(NUMBER* number, STRINGREF str, // NUMFMTREF numfmt) private static String FPNumberToString(Number number, String format) { char fmt; int digits; if (number.scale == SCALE_NAN) { return NumberFormatInfo.nanSymbol; } if (number.scale == SCALE_INF) { return number.negative ? NumberFormatInfo.negativeInfinitySymbol : NumberFormatInfo.positiveInfinitySymbol; } fmt = ParseFormatSpecifier(format, out digits); if (fmt != 0) { return number.ToString(fmt, digits); } return number.ToStringFormat(format); } // STRINGREF NumberToString(NUMBER* number, wchar format, // int digits, NUMFMTREF numfmt) private String ToString(char format, int digits) { long newBufferLen = MIN_BUFFER_SIZE; //CQuickBytesSpecifySize buf; char[] buffer = null; int dst; // wchar* dst = NULL; char ftype = (char) (format & 0xFFDF); int digCount = 0; switch (ftype) { case 'F': if (digits < 0) digits = NumberFormatInfo.numberDecimalDigits; if (this.scale < 0) digCount = 0; else digCount = this.scale + digits; newBufferLen += digCount; // For number and exponent newBufferLen += NumberFormatInfo.negativeSign.Length; newBufferLen += NumberFormatInfo.numberDecimalSeparator.Length; //newBufferLen = newBufferLen * sizeof(WCHAR); //_ASSERTE(newBufferLen >= MIN_BUFFER_SIZE * sizeof(WCHAR)); buffer = new char[newBufferLen]; //if (!buffer) // COMPlusThrowOM(); dst = 0; // buffer; RoundNumber(this.scale + digits); if (this.negative) { dst = AddStringRef(buffer, dst, NumberFormatInfo.negativeSign); } dst = FormatFixed(buffer, dst, digits, null, NumberFormatInfo.numberDecimalSeparator, null); break; case 'N': // Since we are using digits in our calculation if (digits < 0) digits = NumberFormatInfo.numberDecimalDigits; if (this.scale < 0) digCount = 0; else digCount = this.scale + digits; newBufferLen += digCount; // For number and exponent newBufferLen += NumberFormatInfo.negativeSign.Length; // For all the grouping sizes newBufferLen += NumberFormatInfo.numberGroupSeparator.Length * digCount; newBufferLen += NumberFormatInfo.numberDecimalSeparator.Length; //newBufferLen = newBufferLen * sizeof(WCHAR); //_ASSERTE(newBufferLen >= MIN_BUFFER_SIZE * sizeof(WCHAR)); buffer = new char[newBufferLen]; //if (!buffer) // COMPlusThrowOM(); dst = 0; // buffer; RoundNumber(this.scale + digits); dst = FormatNumber(buffer, dst, digits); break; case 'E': if (digits < 0) digits = 6; digits++; newBufferLen += digits; // For number and exponent newBufferLen += (NumberFormatInfo.negativeSign.Length + NumberFormatInfo.positiveSign.Length) * 2; newBufferLen += NumberFormatInfo.numberDecimalSeparator.Length; //newBufferLen = newBufferLen * sizeof(WCHAR); //_ASSERTE(newBufferLen >= MIN_BUFFER_SIZE * sizeof(WCHAR)); buffer = new char[newBufferLen]; //if (!buffer) // COMPlusThrowOM(); dst = 0; // buffer; RoundNumber(digits); if (this.negative) { dst = AddStringRef(buffer, dst, NumberFormatInfo.negativeSign); } dst = FormatScientific(buffer, dst, digits, format); break; case 'G': if (digits < 1) digits = this.precision; newBufferLen += digits; // For number and exponent newBufferLen += (NumberFormatInfo.negativeSign.Length + NumberFormatInfo.positiveSign.Length) *2; newBufferLen += NumberFormatInfo.numberDecimalSeparator.Length; //newBufferLen = newBufferLen * sizeof(WCHAR); //_ASSERTE(newBufferLen >= MIN_BUFFER_SIZE * sizeof(WCHAR)); buffer = new char[newBufferLen]; //if (!buffer) // COMPlusThrowOM(); dst = 0; // buffer; RoundNumber(digits); if (this.negative) { dst = AddStringRef(buffer, dst, NumberFormatInfo.negativeSign); } dst = FormatGeneral(buffer, dst, digits, (char) (format - ('G' - 'E'))); break; case 'P': if (digits < 0) digits = NumberFormatInfo.percentDecimalDigits; this.scale += 2; if (this.scale < 0) digCount = 0; else digCount = this.scale + digits; newBufferLen += digCount; // For number and exponent newBufferLen += NumberFormatInfo.negativeSign.Length; // For all the grouping sizes newBufferLen += NumberFormatInfo.percentGroupSeparator.Length * digCount; newBufferLen += NumberFormatInfo.percentDecimalSeparator.Length; newBufferLen += NumberFormatInfo.percentSymbol.Length; //newBufferLen = newBufferLen * sizeof(WCHAR); //_ASSERTE(newBufferLen >= MIN_BUFFER_SIZE * sizeof(WCHAR)); buffer = new char[newBufferLen]; //if (!buffer) // COMPlusThrowOM(); dst = 0; // buffer; RoundNumber(this.scale + digits); dst = FormatPercent(buffer, dst, digits); break; default: throw new FormatException("Format_BadFormatSpecifier"); // COMPlusThrow(kFormatException,L"Format_BadFormatSpecifier"); } //_ASSERTE((dst - buffer >= 0) && (dst - buffer) <= newBufferLen); return String.StringCTOR(buffer, 0, dst); } private static bool NumberToDecimal(Number number, out Decimal value) { throw new Exception("System.Number.NumberToDecimal not implemented in Bartok!"); } [MethodImpl(MethodImplOptions.InternalCall)] [GCAnnotation(GCOption.NOGC)] [StackBound(1024)] private static extern double atof(byte[] s); private static void NumberToDouble(Number number, out double value) { if (number.digits[0] != 0) { byte[] buffer = new byte[64]; int index = 0; char[] src = number.digits; if (number.negative) buffer[index++] = (byte) '-'; for (int j=0; j= 100) { if (i > 999) i = 999; buffer[index++] = (byte) (i / 100 + (int) '0'); i %= 100; } buffer[index++] = (byte) (i / 10 + (int) '0'); buffer[index++] = (byte) (i % 10 + (int) '0'); } buffer[index] = (byte)'\0'; value = atof(buffer); } else { value = 0; } } // int NumberToInt32(NUMBER* number, int* value) // Returns 1 (true) on success, 0 (false) for fail. private static bool NumberToInt32(Number number, out int value) { // markples: see also Lightning\Src\VM\COMNumber.cpp::NumberToInt32 int i = number.scale; if (i > INT32_PRECISION || i < number.precision) goto broken; char[] c = number.digits; int p = 0; int n = 0; while (--i >= 0) { if ((uint)n > (0x7FFFFFFF / 10)) goto broken; n *= 10; if (c[p] != '\0') n += c[p++] - '0'; } if (number.negative) { n = -n; if (n > 0) goto broken; } else { if (n < 0) goto broken; } value = n; return true; broken: value = 0; return false; } private static bool NumberToUInt32(Number number, out uint value) { // markples: see also Lightning\Src\VM\COMNumber.cpp::NumberToInt32 int i = number.scale; if (i > UINT32_PRECISION || i < number.precision) goto broken; if (number.negative) goto broken; char[] c = number.digits; int p = 0; uint n = 0; while (--i >= 0) { if ((uint)n > (0xFFFFFFFF / 10)) goto broken; n *= 10; if (c[p] != '\0') n += (uint)(c[p++] - '0'); } value = n; return true; broken: value = 0; return false; } private static bool NumberToInt64(Number number, out long value) { // markples: see also Lightning\Src\VM\COMNumber.cpp::NumberToInt32 int i = number.scale; if (i > UINT64_PRECISION || i < number.precision) goto broken; char[] c = number.digits; int p = 0; long n = 0; while (--i >= 0) { if ((ulong)n > (0x7FFFFFFFFFFFFFFF / 10)) goto broken; n *= 10; if (c[p] != '\0') n += c[p++] - '0'; } if (number.negative) { n = -n; if (n > 0) goto broken; } else { if (n < 0) goto broken; } value = n; return true; broken: value = 0; return false; } private static bool NumberToUInt64(Number number, out ulong value) { // markples: see also Lightning\Src\VM\COMNumber.cpp::NumberToInt32 int i = number.scale; if (i > INT64_PRECISION || i < number.precision) goto broken; if (number.negative) goto broken; char[] c = number.digits; int p = 0; ulong n = 0; while (--i >= 0) { if ((ulong)n > (0xFFFFFFFFFFFFFFFF / 10)) goto broken; n *= 10; if (c[p] != '\0') n += (ulong)(c[p++] - '0'); } value = n; return true; broken: value = 0; return false; } private static bool HexNumberToUInt32(Number number, out uint value) { // markples: see also Lightning\Src\VM\COMNumber.cpp::HexNumberToInt32 int i = number.scale; if (i > UINT32_PRECISION || i < number.precision) { goto broken; } if (number.negative) { goto broken; } char[] c = number.digits; int p = 0; uint n = 0; while (--i >= 0) { if ((uint)n > (0xFFFFFFFF / 16)) { goto broken; } n *= 16; if (c[p] >= '0' && c[p] <= '9') { n += (uint)(c[p++] - '0'); } else if (c[p] >= 'a' && c[p] <= 'f') { n += 10 + (uint)(c[p++] - 'a'); } else if (c[p] >= 'A' && c[p] <= 'F') { n += 10 + (uint)(c[p++] - 'A'); } } value = n; return true; broken: value = 0; return false; } private static bool HexNumberToUInt64(Number number, out ulong value) { // markples: see also Lightning\Src\VM\COMNumber.cpp::HexNumberToInt32 int i = number.scale; if (i > INT64_PRECISION || i < number.precision) { goto broken; } if (number.negative) { goto broken; } char[] c = number.digits; int p = 0; ulong n = 0; while (--i >= 0) { if ((ulong)n > (0xFFFFFFFFFFFFFFFF / 16)) { goto broken; } n *= 16; if (c[p] >= '0' && c[p] <= '9') { n += (uint)(c[p++] - '0'); } else if (c[p] >= 'a' && c[p] <= 'f') { n += 10 + (uint)(c[p++] - 'a'); } else if (c[p] >= 'A' && c[p] <= 'F') { n += 10 + (uint)(c[p++] - 'A'); } } value = n; return true; broken: value = 0; return false; } private static char ParseFormatSpecifier(string format, out int digits) { if (format != null) { int index = 0; char c = Get(format, index); if (c != 0) { if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') { index++; int n = -1; c = Get(format, index); if (c >= '0' && c <= '9') { n = (c - '0'); index++; c = Get(format, index); while (c >= '0' && c <= '9') { n = n * 10 + (c - '0'); index++; c = Get(format, index); if (n >= 10) break; } } if (c == 0) { digits = n; return Get(format, 0); } } digits = -1; return '\0'; } } digits = -1; return 'G'; } // rusa: see also Lightning\Src\VM\COMNumber.cpp::NumberToStringFormat private String ToStringFormat(String format) { int thousandCount = 0; int section = (this.digits[0] == 0 ? 2 : (this.negative ? 1 : 0)); int sectionOffset = FindSection(format, section); int firstDigit; int lastDigit; bool scientific; int decimalPos; int src; int percent; int permille; int thousandSeps; char ch; while (true) { int digitCount = 0; decimalPos = -1; firstDigit = 0x7FFFFFFF; lastDigit = 0; scientific = false; percent = 0; permille = 0; int thousandPos = -1; thousandSeps = 0; int scaleAdjust = 0; src = sectionOffset; ch = Get(format, src); src++; while (ch != '\0' && ch != ';') { switch (ch) { case '#': digitCount++; break; case '0': if (firstDigit == 0x7FFFFFFF) { firstDigit = digitCount; } digitCount++; lastDigit = digitCount; break; case '.': if (decimalPos < 0) { decimalPos = digitCount; } break; case ',': if (digitCount > 0 && decimalPos < 0) { if (thousandPos >= 0) { if (thousandPos == digitCount) { thousandCount++; break; } thousandSeps = 1; } thousandPos = digitCount; thousandCount = 1; } break; case '%': percent++; scaleAdjust += 2; break; case '\u2030': permille++; scaleAdjust += 3; break; case '\'': case '"': while (Get(format, src) != 0 && Get(format, src) != ch) { src++; } break; case '\\': if (Get(format, src) != 0) { src++; } break; case 'E': if (Get(format, src) == '0' || ((Get(format, src) == '+' || Get(format, src) == '-') && Get(format, src+1) == '0')) { do { src++; } while (Get(format, src) != 0); scientific = true; } break; } ch = Get(format, src); src++; } if (decimalPos < 0) { decimalPos = digitCount; } if (thousandPos >= 0) { if (thousandPos == decimalPos) { scaleAdjust -= thousandCount * 3; } else { thousandSeps = 1; } } if (this.digits[0] != 0) { this.scale += scaleAdjust; int pos = (scientific ? digitCount : (this.scale + digitCount - decimalPos)); this.RoundNumber(pos); if (this.digits[0] == 0) { src = FindSection(format, 2); if (src != sectionOffset) { sectionOffset = src; continue; } } } else { this.negative = false; } break; } firstDigit = (firstDigit < decimalPos) ? decimalPos - firstDigit : 0; lastDigit = (lastDigit > decimalPos) ? decimalPos - lastDigit : 0; int digPos; int adjust; if (scientific) { digPos = decimalPos; adjust = 0; } else { digPos = (this.scale > decimalPos) ? this.scale : decimalPos; adjust = this.scale - decimalPos; } src = sectionOffset; ulong maxStrIncLen = unchecked((ulong) (this.negative ? NumberFormatInfo.negativeSign.Length : NumberFormatInfo.positiveSign.Length)); maxStrIncLen += (uint) NumberFormatInfo.numberDecimalSeparator.Length; if (scientific) { int inc1 = NumberFormatInfo.positiveSign.Length; int inc2 = NumberFormatInfo.negativeSign.Length; maxStrIncLen += (uint) ((inc1 > inc2) ? inc1 : inc2); } if (percent > 0) { maxStrIncLen += (ulong) ((NumberFormatInfo.percentSymbol.Length) * percent); } if (permille > 0) { maxStrIncLen += (ulong) ((NumberFormatInfo.perMilleSymbol.Length) * permille); } ulong adjustLength = (adjust > 0) ? (uint) adjust : 0U; int bufferLength = 125; int[] thousandSepPos = null; int thousandSepCtr = -1; if (thousandSeps != 0) { int groupSizeLen = NumberFormatInfo.numberGroupSizes.Length; if (groupSizeLen == 0) { thousandSeps = 0; } else { thousandSepPos = new int[bufferLength]; long groupTotalSizeCount = NumberFormatInfo.numberGroupSizes[0]; int groupSizeIndex = 0; int groupSize = (int) groupTotalSizeCount; int totalDigits = digPos + ((adjust < 0) ? adjust : 0); int numDigits = (firstDigit > totalDigits) ? firstDigit : totalDigits; while (numDigits > groupTotalSizeCount) { if (groupSize == 0) { break; } thousandSepCtr++; thousandSepPos[thousandSepCtr] = (int) groupTotalSizeCount; if (groupSizeIndex < groupSizeLen - 1) { groupSizeIndex++; groupSize = NumberFormatInfo.numberGroupSizes[groupSizeIndex]; } groupTotalSizeCount += groupSize; if (bufferLength - thousandSepCtr < 10) { bufferLength *= 2; int[] oldThousandSepPos = thousandSepPos; thousandSepPos = new int[bufferLength]; for (int i = 0; i < thousandSepCtr; i++) { thousandSepPos[i] = oldThousandSepPos[i]; } } } adjustLength += (ulong) ((thousandSepCtr + 1) * NumberFormatInfo.numberGroupSeparator.Length); } } maxStrIncLen += adjustLength; ulong tempLen = ((ulong) format.Length + maxStrIncLen + 10U); if (tempLen > 0x7FFFFFFF) { throw new OutOfMemoryException("Cannot create a formatted string that big!"); } uint bufferLen = (uint) tempLen; if (bufferLen < 250) { bufferLen = 250; } int bufferIndex = 0; char[] buffer = new char[bufferLen]; int dst = 0; if (this.negative && sectionOffset == 0) { bufferIndex = AddStringRef(buffer, bufferIndex, NumberFormatInfo.negativeSign); } ch = Get(format, src); src++; int digitOffset = 0; while (ch != 0 && ch != ';') { if (bufferLength - dst < 10) { bufferLen *= 2; char[] oldBuffer = buffer; buffer = new char[bufferLen]; for (int i = 0; i < dst; i++) { buffer[i] = oldBuffer[i]; } } switch (ch) { case '#': case '0': { while (adjust > 0) { buffer[dst] = ((this.digits[digitOffset] != 0) ? this.digits[digitOffset++] : '0'); dst++; if (thousandSeps != 0 && digPos > 1 && thousandSepCtr >= 0) { if (digPos == thousandSepPos[thousandSepCtr] + 1) { dst = AddStringRef(buffer, dst, NumberFormatInfo.numberGroupSeparator); thousandSepCtr--; } } digPos--; adjust--; } if (adjust < 0) { adjust++; ch = (digPos <= firstDigit) ? '0' : '\0'; } else { ch = ((this.digits[digitOffset] != 0) ? this.digits[digitOffset++] : ((digPos > lastDigit) ? '0' : '\0')); } if (ch != 0) { if (digPos == 0) { dst = AddStringRef(buffer, dst, NumberFormatInfo.numberDecimalSeparator); } buffer[dst] = ch; dst++; if (thousandSeps != 0 && digPos > 1 && thousandSepCtr >= 0) { if (digPos == thousandSepPos[thousandSepCtr] + 1) { dst = AddStringRef(buffer, dst, NumberFormatInfo.numberGroupSeparator); thousandSepCtr--; } } } digPos--; break; } case '.': break; case '\u2030': dst = AddStringRef(buffer, dst, NumberFormatInfo.perMilleSymbol); break; case '%': dst = AddStringRef(buffer, dst, NumberFormatInfo.percentSymbol); break; case ',': break; case '\'': case '"': while (Get(format, src) != 0 && Get(format, src) != ch) { buffer[dst] = Get(format, src); dst++; src++; if (dst == bufferLen-1) { if ((ulong) (bufferLen - dst) < maxStrIncLen) { bufferLen *= 2; char[] oldBuffer = buffer; buffer = new char[bufferLen]; for (int i = 0; i < dst; i++) { buffer[i] = oldBuffer[i]; } } } } if (Get(format, src) != 0) { src++; } break; case '\\': if (Get(format, src) != 0) { buffer[dst] = Get(format, src); dst++; src++; } break; case 'E': case 'e': { String sign = null; int i = 0; if (scientific) { if (Get(format, src) == '0') { i++; } else if (Get(format, src) == '+' && Get(format, src+1) == '0') { sign = NumberFormatInfo.positiveSign; } else if (Get(format, src) == '-' && Get(format, src+1) == '0') { // Do nothing } else { buffer[dst] = ch; dst++; break; } while (Get(format, ++src) == '0') { i++; } if (i > 10) { i = 10; } int exp = ((this.digits[0] == 0) ? 0 : (this.scale - decimalPos)); dst = FormatExponent(buffer, dst, exp, ch, sign, NumberFormatInfo.negativeSign, i); scientific = false; } else { buffer[dst] = ch; dst++; if (Get(format, src) == '+' || Get(format, src) == '-') { buffer[dst] = Get(format, src); dst++; src++; } while (Get(format, src) != 0) { buffer[dst] = Get(format, src); dst++; src++; } } break; } default: buffer[dst] = ch; dst++; break; } ch = Get(format, src); src++; } return String.StringCTOR(buffer, 0, dst); } private static int FindSection(String format, int section) { if (section == 0) { return 0; } int src = 0; while (true) { char ch = Get(format, src); src++; switch (ch) { case '\'': case '"': while (Get(format, src) != '\0' && Get(format, src) != ch) { src++; } break; case '\\': if (Get(format, src) != 0) { src++; } break; case ';': section--; if (section != 0) { break; } if (Get(format, src) != 0 && Get(format, src) != ';') { return src; } return 0; case '\0': return 0; } } } // markples: see also Lightning\Src\VM\COMNumber.cpp:: // STRINGREF Int32ToDecStr(int value, int digits, STRINGREF sNegative) private static String Int32ToDecString(int value, int digits, String sign) { //THROWSCOMPLUSEXCEPTION(); //CQuickBytes buf; int bufferLength = 100; // was UINT int negLength = 0; // wchar* src = NULL; if (digits < 1) digits = 1; if (value < 0) { //src = sNegative->GetBuffer(); negLength = sign.Length; if (negLength > 85) { bufferLength = negLength + 15; //was implicit C++ cast } } char[] buffer = new char[bufferLength]; //if (!buffer) // COMPlusThrowOM(); int p = bufferLength; Int32ToDecChars(buffer, ref p, (uint)(value >= 0? value: -value), digits); if (value < 0) { for (int i = negLength - 1; i >= 0; i--) { buffer[--p] = sign[i]; // *(--p) = *(src+i); } } // _ASSERTE( buffer + bufferLength - p >=0 && buffer <= p); return String.StringCTOR(buffer, p, bufferLength-p); // return COMString::NewString(p, buffer + bufferLength - p); } private static String Int32ToHexString(uint value, char hexBase, int digits) { char[] buffer = new char[100]; if (digits < 1) { digits = 1; } int start = Int32ToHexChars(buffer, 100, value, hexBase, digits); return String.StringCTOR(buffer, start, 100-start); } private static int Int32ToHexChars(char[] buffer, int offset, uint value, char hexBase, int digits) { while (digits > 0 || value != 0) { digits--; uint digit = value & 0xf; offset--; buffer[offset] = (char) (digit + (digit < 10 ? '0' : hexBase)); value >>= 4; } return offset; } private static String UInt32ToDecString(uint value, int digits) { int bufferLength = 100; if (digits < 1) digits = 1; char[] buffer = new char[bufferLength]; int p = bufferLength; Int32ToDecChars(buffer, ref p, value, digits); return String.StringCTOR(buffer, p, bufferLength-p); } // used to be macros private static uint LO32(ulong x) { return (uint)(x); } private static uint HI32(ulong x) { return (uint)((((ulong)x) & 0xFFFFFFFF00000000L) >> 32); } // markples: see also Lightning\Src\VM\COMNumber.cpp:: //STRINGREF Int64ToDecStr(__int64 value, int digits, STRINGREF sNegative) private static String Int64ToDecString(long value, int digits, String sign) { //THROWSCOMPLUSEXCEPTION(); //CQuickBytes buf; if (digits < 1) digits = 1; int signNum = (int)HI32(unchecked((ulong)value)); int bufferLength = 100; // was UINT if (signNum < 0) { value = -value; int negLength = sign.Length; if (negLength > 75) {// Since max is 20 digits bufferLength = negLength + 25; } } char[] buffer = new char[bufferLength]; //if (!buffer) // COMPlusThrowOM(); int p = bufferLength; while (HI32(unchecked((ulong)value)) != 0) { uint rem; value = (long) Int64DivMod1E9((ulong)value, out rem); Int32ToDecChars(buffer, ref p, rem, 9); digits -= 9; } Int32ToDecChars(buffer, ref p, LO32(unchecked((ulong)value)), digits); if (signNum < 0) { for (int i=sign.Length - 1; i >= 0; i--) { buffer[--p] = sign[i]; } } return String.StringCTOR(buffer, p, bufferLength-p); } private static int Int64ToHexChars(char[] buffer, int offset, ulong value, char hexBase, int digits) { while (digits > 0 || value != 0) { digits--; uint digit = ((uint)value) & 0xf; offset--; buffer[offset] = (char) (digit + (digit < 10 ? '0' : hexBase)); value >>= 4; } return offset; } private static String Int64ToHexString(ulong value, char hexBase, int digits) { char[] buffer = new char[100]; if (digits < 1) { digits = 1; } int start = Int64ToHexChars(buffer, 100, value, hexBase, digits); return String.StringCTOR(buffer, start, 100-start); } private static String UInt64ToDecString(ulong value, int digits) { int bufferLength = 100; char[] buffer = new char[bufferLength]; if (digits < 1) digits = 1; int p = bufferLength; while (HI32(value) != 0) { uint rem; value = Int64DivMod1E9(value, out rem); Int32ToDecChars(buffer, ref p, rem, 9); digits -= 9; } Int32ToDecChars(buffer, ref p, LO32(value), digits); return String.StringCTOR(buffer, p, bufferLength-p); } // markples: see also Lightning\Src\VM\COMNumber.cpp:: // wchar* Int32ToDecChars(wchar* p, unsigned int value, int digits) // There's a x86 asm version there too. private static void Int32ToDecChars(char[] buffer, ref int bufferIndex, uint value, int digits) { while (--digits >= 0 || value != 0) { buffer[--bufferIndex] = (char) (value % 10 + '0'); value /= 10; } } private static void Int64ToDecChars(char[] buffer, ref int bufferIndex, ulong value, int digits) { while (--digits >= 0 || value != 0) { buffer[--bufferIndex] = (char) (value % 10 + '0'); value /= 10; } } // markples: see also Lightning\Src\VM\COMNumber.cpp:: // void RoundNumber(NUMBER* number, int pos) private void RoundNumber(int pos) { int i = 0; while (i < pos && this.digits[i] != 0) i++; if (i == pos && this.digits[i] >= '5') { while (i > 0 && this.digits[i - 1] == '9') i--; if (i > 0) { this.digits[i - 1]++; } else { this.scale++; this.digits[0] = '1'; i = 1; } } else { while (i > 0 && this.digits[i - 1] == '0') i--; } if (i == 0) { this.scale = 0; this.negative = false; } this.digits[i] = '\0'; } // wchar* FormatExponent(wchar* buffer, int value, wchar expChar, // STRINGREF posSignStr, STRINGREF negSignStr, // int minDigits) private int FormatExponent(char[] buffer, int bufferIndex, int value, char expChar, String posSignStr, String negSignStr, int minDigits) { char[] digits = new char[11]; buffer[bufferIndex++] = expChar; if (value < 0) { bufferIndex = AddStringRef(buffer, bufferIndex, negSignStr); value = -value; } else { if (posSignStr != null) { bufferIndex = AddStringRef(buffer, bufferIndex, posSignStr); } } int p = 10; // REVIEW: (int) was implicit in C++ code Int32ToDecChars(digits, ref p, checked((uint)value), minDigits); int i = 10 - p; while (--i >= 0) buffer[bufferIndex++] = digits[p++]; return bufferIndex; } //wchar* FormatGeneral(wchar* buffer, NUMBER* number, int digits, // wchar expChar, NUMFMTREF numfmt) private int FormatGeneral(char[] buffer, int bufferIndex, int digits, char expChar) { int digPos = this.scale; bool scientific = false; if (digPos > digits || digPos < -3) { digPos = 1; scientific = true; } int dig = 0; // number->digits; if (digPos > 0) { do { buffer[bufferIndex++] = this.digits[dig] != 0 ? this.digits[dig++] : '0'; } while (--digPos > 0); } else { buffer[bufferIndex++] = '0'; } if (this.digits[dig] != 0) { bufferIndex = AddStringRef(buffer, bufferIndex, NumberFormatInfo.numberDecimalSeparator); while (digPos < 0) { buffer[bufferIndex++] = '0'; digPos++; } do { buffer[bufferIndex++] = this.digits[dig++]; } while (this.digits[dig] != 0); } if (scientific) { bufferIndex = FormatExponent(buffer, bufferIndex, this.scale - 1, expChar, NumberFormatInfo.positiveSign, NumberFormatInfo.negativeSign, 2); } return bufferIndex; } // wchar* FormatScientific(wchar* buffer, NUMBER* number, // int digits, wchar expChar, NUMFMTREF numfmt) private int FormatScientific(char[] buffer, int bufferIndex, int digits, char expChar) { int dig = 0; // number->digits; buffer[bufferIndex++] = this.digits[dig] != 0 ? this.digits[dig++] : '0'; if (digits != 1) { // For E0 we would like to suppress the decimal point bufferIndex = AddStringRef(buffer, bufferIndex, NumberFormatInfo.numberDecimalSeparator); } while (--digits > 0) { buffer[bufferIndex++] = this.digits[dig] != 0 ? this.digits[dig++] : '0'; } int e = this.digits[0] == 0 ? 0 : this.scale - 1; bufferIndex = FormatExponent(buffer, bufferIndex, e, expChar, NumberFormatInfo.positiveSign, NumberFormatInfo.negativeSign, 3); return bufferIndex; } // REVIEW: call the real wcslen? private static int wcslen(char[] c, int i) { int j; for(j=i; jdigits; if (digPos > 0) { if (groupDigits != null) { // index into the groupDigits array. int groupSizeIndex = 0; // the current total of group size. int groupSizeCount = groupDigits[groupSizeIndex]; // the length of groupDigits array. int groupSizeLen = groupDigits.Length; // the length of the result buffer string. int bufferSize = digPos; // the length of the group separator string. int groupSeparatorLen = sGroup.Length; // the current group size. int groupSize = 0; // // Find out the size of the string buffer for the result. // if (groupSizeLen != 0) // You can pass in 0 length arrays { while (digPos > groupSizeCount) { groupSize = groupDigits[groupSizeIndex]; if (groupSize == 0) { break; } bufferSize += groupSeparatorLen; if (groupSizeIndex < groupSizeLen - 1) { groupSizeIndex++; } groupSizeCount += groupDigits[groupSizeIndex]; if (groupSizeCount < 0 || bufferSize < 0) { throw new ArgumentOutOfRangeException(); // if we overflow //COMPlusThrow(kArgumentOutOfRangeException); } } // If you passed in an array with one // entry as 0, groupSizeCount == 0 if (groupSizeCount == 0) groupSize = 0; else groupSize = groupDigits[0]; } groupSizeIndex = 0; int digitCount = 0; int digStart; int digLength = (int)wcslen(this.digits, dig); digStart = (digPos=0; i--) { buffer[p--] = (i 0) { digitCount++; if (digitCount == groupSize && i != 0) { for (int j = groupSeparatorLen - 1; j >=0; j--) { buffer[p--] = sGroup[j]; } if (groupSizeIndex < groupSizeLen - 1) { groupSizeIndex++; groupSize = groupDigits[groupSizeIndex]; } digitCount = 0; } } } bufferIndex += bufferSize; dig += digStart; } else { do { buffer[bufferIndex++] = this.digits[dig] != 0 ? this.digits[dig++] : '0'; } while (--digPos > 0); } } else { buffer[bufferIndex++] = '0'; } if (digits > 0) { bufferIndex = AddStringRef(buffer, bufferIndex, sDecimal); while (digPos < 0 && digits > 0) { buffer[bufferIndex++] = '0'; digPos++; digits--; } while (digits > 0) { buffer[bufferIndex++] = this.digits[dig] != 0 ? this.digits[dig++] : '0'; digits--; } } return bufferIndex; } // wchar* FormatNumber(wchar* buffer, NUMBER* number, // int digits, NUMFMTREF numfmt) private int FormatNumber(char[] buffer, int bufferIndex, int digits) { char ch; String fmt = this.negative ? negNumberFormats[NumberFormatInfo.numberNegativePattern] : posNumberFormat; int fmtIndex = 0; for (; fmtIndex < fmt.Length; fmtIndex++) { ch = fmt[fmtIndex]; switch (ch) { case '#': bufferIndex = FormatFixed(buffer, bufferIndex, digits, NumberFormatInfo.numberGroupSizes, NumberFormatInfo.numberDecimalSeparator, NumberFormatInfo.numberGroupSeparator); break; case '-': bufferIndex = AddStringRef(buffer, bufferIndex, NumberFormatInfo.negativeSign); break; default: buffer[bufferIndex++] = ch; break; } } return bufferIndex; } // wchar* FormatPercent(wchar* buffer, NUMBER* number, // int digits, NUMFMTREF numfmt) private int FormatPercent(char[] buffer, int bufferIndex, int digits) { char ch; String fmt = this.negative ? negPercentFormats[NumberFormatInfo.percentNegativePattern] : posPercentFormats[NumberFormatInfo.percentPositivePattern]; int fmtIndex = 0; while ((ch = fmt[fmtIndex++]) != 0) { switch (ch) { case '#': bufferIndex = FormatFixed(buffer, bufferIndex, digits, NumberFormatInfo.percentGroupSizes, NumberFormatInfo.percentDecimalSeparator, NumberFormatInfo.percentGroupSeparator); break; case '-': bufferIndex = AddStringRef(buffer, bufferIndex, NumberFormatInfo.negativeSign); break; case '%': bufferIndex = AddStringRef(buffer, bufferIndex, NumberFormatInfo.percentSymbol); break; default: buffer[bufferIndex++] = ch; break; } } return bufferIndex; } private uint D32DivMod1E9(uint hi32, uint lo32, out uint newlo32) { ulong n = (((ulong) hi32) << 32) | lo32; newlo32 = (uint) (n / 1000000000); return (uint) (n % 1000000000); } private uint DecDivMod1E9(ref Decimal value) { uint newhi, newmid, newlo; uint result = D32DivMod1E9(D32DivMod1E9(D32DivMod1E9(0, value.hi32, out newhi), value.mid32, out newmid), value.lo32, out newlo); value.hi32 = newhi; value.mid32 = newmid; value.lo32 = newlo; return result; } // markples: see also Lightning\Src\VM\COMNumber.cpp:: // unsigned int Int64DivMod1E9(unsigned __int64* value) // There's a x86 asm version there too. // The interface is different because Bartok does not support // taking the address of 64 bit values. private static ulong Int64DivMod1E9(ulong value, out uint rem) { rem = (uint)(value % 1000000000); value /= 1000000000; return value; } public static bool TryParseDouble(String s, NumberStyles style,out double result) { throw new Exception("System.Number.TryParseDouble not implemented in Bartok!"); } private const int DecimalPrecision = 29; private const int NumberMaxDigits = 31; private int precision; private int scale; private bool negative; private char[] digits = new char[NumberMaxDigits+1]; } }