diff --git a/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs b/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs index 0a7a7a4a89536c..828db72caea4a9 100644 --- a/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs +++ b/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs @@ -356,7 +356,54 @@ private static unsafe bool AllowHyphenDuringParsing(NumberFormatInfo info) return ret; } + private interface IDigitParser + { + static abstract bool IsValidChar(char c); + static abstract bool IsHexOrBinaryParser(); + } + + private readonly struct IntegerDigitParser : IDigitParser + { + public static bool IsValidChar(char c) => char.IsAsciiDigit(c); + + public static bool IsHexOrBinaryParser() => false; + } + + private readonly struct HexDigitParser : IDigitParser + { + public static bool IsValidChar(char c) => HexConverter.IsHexChar((int)c); + + public static bool IsHexOrBinaryParser() => true; + } + + private readonly struct BinaryDigitParser : IDigitParser + { + public static bool IsValidChar(char c) + { + return (uint)c - '0' <= 1; + } + + public static bool IsHexOrBinaryParser() => true; + } + + private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles options, scoped ref NumberBuffer number, StringBuilder? sb, NumberFormatInfo numfmt, bool parseDecimal) + { + if ((options & NumberStyles.AllowHexSpecifier) != 0) + { + return ParseNumberStyle(ref str, strEnd, options, ref number, sb, numfmt, parseDecimal); + } + + if ((options & NumberStyles.AllowBinarySpecifier) != 0) + { + return ParseNumberStyle(ref str, strEnd, options, ref number, sb, numfmt, parseDecimal); + } + + return ParseNumberStyle(ref str, strEnd, options, ref number, sb, numfmt, parseDecimal); + } + + private static unsafe bool ParseNumberStyle(ref char* str, char* strEnd, NumberStyles options, scoped ref NumberBuffer number, StringBuilder? sb, NumberFormatInfo numfmt, bool parseDecimal) + where TDigitParser : struct, IDigitParser { Debug.Assert(str != null); Debug.Assert(strEnd != null); @@ -440,11 +487,11 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles int digEnd = 0; while (true) { - if (char.IsAsciiDigit(ch) || (((options & NumberStyles.AllowHexSpecifier) != 0) && char.IsBetween((char)(ch | 0x20), 'a', 'f'))) + if (TDigitParser.IsValidChar(ch)) { state |= StateDigits; - if (ch != '0' || (state & StateNonZero) != 0 || (bigNumber && ((options & NumberStyles.AllowHexSpecifier) != 0))) + if (ch != '0' || (state & StateNonZero) != 0 || (bigNumber && TDigitParser.IsHexOrBinaryParser())) { if (digCount < maxParseDigits) { diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs index 18c55cc62cd003..ff7fe60f324790 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -22,10 +22,10 @@ public readonly struct BigInteger IBinaryInteger, ISignedNumber { - private const uint kuMaskHighBit = unchecked((uint)int.MinValue); - private const int kcbitUint = 32; - private const int kcbitUlong = 64; - private const int DecimalScaleFactorMask = 0x00FF0000; + internal const uint kuMaskHighBit = unchecked((uint)int.MinValue); + internal const int kcbitUint = 32; + internal const int kcbitUlong = 64; + internal const int DecimalScaleFactorMask = 0x00FF0000; // For values int.MinValue < n <= int.MaxValue, the value is stored in sign // and _bits is null. For all other values, sign is +1 or -1 and the bits are in _bits diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs index c31e73503dfbea..0a05f0dddd2946 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -285,7 +285,8 @@ internal static class BigNumber | NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingSign | NumberStyles.AllowParentheses | NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands | NumberStyles.AllowExponent - | NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier); + | NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier + | NumberStyles.AllowBinarySpecifier); private static ReadOnlySpan UInt32PowersOfTen => new uint[] { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; @@ -371,10 +372,13 @@ internal static ParsingStatus TryParseBigInteger(ReadOnlySpan value, Numbe { return HexNumberToBigInteger(ref bigNumber, out result); } - else + + if ((style & NumberStyles.AllowBinarySpecifier) != 0) { - return NumberToBigInteger(ref bigNumber, out result); + return BinaryNumberToBigInteger(ref bigNumber, out result); } + + return NumberToBigInteger(ref bigNumber, out result); } internal static BigInteger ParseBigInteger(string value, NumberStyles style, NumberFormatInfo info) @@ -511,6 +515,112 @@ private static ParsingStatus HexNumberToBigInteger(ref BigNumberBuffer number, o } } + private static ParsingStatus BinaryNumberToBigInteger(ref BigNumberBuffer number, out BigInteger result) + { + if (number.digits is null || number.digits.Length == 0) + { + result = default; + return ParsingStatus.Failed; + } + + int totalDigitCount = number.digits.Length - 1; // Ignore trailing '\0' + int partialDigitCount; + + (int blockCount, int remainder) = int.DivRem(totalDigitCount, BigInteger.kcbitUint); + if (remainder == 0) + { + partialDigitCount = 0; + } + else + { + blockCount++; + partialDigitCount = BigInteger.kcbitUint - remainder; + } + + Debug.Assert(number.digits[0] is '0' or '1'); + bool isNegative = number.digits[0] == '1'; + uint currentBlock = isNegative ? 0xFF_FF_FF_FFu : 0x0; + + uint[]? arrayFromPool = null; + Span buffer = ((uint)blockCount <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : arrayFromPool = ArrayPool.Shared.Rent(blockCount)).Slice(0, blockCount); + + int bufferPos = blockCount - 1; + + try + { + foreach (ReadOnlyMemory digitsChunkMem in number.digits.GetChunks()) + { + ReadOnlySpan chunkDigits = digitsChunkMem.Span; + for (int i = 0; i < chunkDigits.Length; i++) + { + char digitChar = chunkDigits[i]; + if (digitChar == '\0') + { + break; + } + + Debug.Assert(digitChar is '0' or '1'); + currentBlock = (currentBlock << 1) | (uint)(digitChar - '0'); + partialDigitCount++; + + if (partialDigitCount == BigInteger.kcbitUint) + { + buffer[bufferPos--] = currentBlock; + partialDigitCount = 0; + + // we do not need to reset currentBlock now, because it should always set all its bits by left shift in subsequent iterations + } + } + } + + Debug.Assert(partialDigitCount == 0 && bufferPos == -1); + + buffer = buffer.TrimEnd(0u); + + int sign; + uint[]? bits; + + if (buffer.IsEmpty) + { + sign = 0; + bits = null; + } + else if (buffer.Length == 1) + { + sign = (int)buffer[0]; + bits = null; + + if ((!isNegative && sign < 0) || sign == int.MinValue) + { + bits = new[] { (uint)sign }; + sign = isNegative ? -1 : 1; + } + } + else + { + sign = isNegative ? -1 : 1; + bits = buffer.ToArray(); + + if (isNegative) + { + NumericsHelpers.DangerousMakeTwosComplement(bits); + } + } + + result = new BigInteger(sign, bits); + return ParsingStatus.OK; + } + finally + { + if (arrayFromPool is not null) + { + ArrayPool.Shared.Return(arrayFromPool); + } + } + } + // // This threshold is for choosing the algorithm to use based on the number of digits. // @@ -1002,6 +1112,105 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig } } + private static string? FormatBigIntegerToBinary(bool targetSpan, BigInteger value, int digits, Span destination, out int charsWritten, out bool spanSuccess) + { + // Get the bytes that make up the BigInteger. + byte[]? arrayToReturnToPool = null; + Span bytes = stackalloc byte[64]; // arbitrary threshold + if (!value.TryWriteOrCountBytes(bytes, out int bytesWrittenOrNeeded)) + { + bytes = arrayToReturnToPool = ArrayPool.Shared.Rent(bytesWrittenOrNeeded); + bool success = value.TryWriteBytes(bytes, out _); + Debug.Assert(success); + } + bytes = bytes.Slice(0, bytesWrittenOrNeeded); + + Debug.Assert(!bytes.IsEmpty); + + byte highByte = bytes[^1]; + + int charsInHighByte = 9 - byte.LeadingZeroCount(value._sign >= 0 ? highByte : (byte)~highByte); + long tmpCharCount = charsInHighByte + ((long)(bytes.Length - 1) << 3); + + if (tmpCharCount > Array.MaxLength) + { + Debug.Assert(arrayToReturnToPool is not null); + ArrayPool.Shared.Return(arrayToReturnToPool); + + throw new FormatException(SR.Format_TooLarge); + } + + int charsForBits = (int)tmpCharCount; + + Debug.Assert(digits < Array.MaxLength); + int charsIncludeDigits = Math.Max(digits, charsForBits); + + try + { + scoped ValueStringBuilder sb; + if (targetSpan) + { + if (charsIncludeDigits > destination.Length) + { + charsWritten = 0; + spanSuccess = false; + return null; + } + + // Because we have ensured destination can take actual char length, so now just use ValueStringBuilder as wrapper so that subsequent logic can be reused by 2 flows (targetSpan and non-targetSpan); + // meanwhile there is no need to copy to destination again after format data for targetSpan flow. + sb = new ValueStringBuilder(destination); + } + else + { + // each byte is typically eight chars + sb = charsIncludeDigits > 512 + ? new ValueStringBuilder(charsIncludeDigits) + : new ValueStringBuilder(stackalloc char[512]); + } + + if (digits > charsForBits) + { + sb.Append(value._sign >= 0 ? '0' : '1', digits - charsForBits); + } + + AppendByte(ref sb, highByte, charsInHighByte - 1); + + for (int i = bytes.Length - 2; i >= 0; i--) + { + AppendByte(ref sb, bytes[i]); + } + + Debug.Assert(sb.Length == charsIncludeDigits); + + if (targetSpan) + { + charsWritten = charsIncludeDigits; + spanSuccess = true; + return null; + } + + charsWritten = 0; + spanSuccess = false; + return sb.ToString(); + } + finally + { + if (arrayToReturnToPool is not null) + { + ArrayPool.Shared.Return(arrayToReturnToPool); + } + } + + static void AppendByte(ref ValueStringBuilder sb, byte b, int startHighBit = 7) + { + for (int i = startHighBit; i >= 0; i--) + { + sb.Append((char)('0' + ((b >> i) & 0x1))); + } + } + } + internal static string FormatBigInteger(BigInteger value, string? format, NumberFormatInfo info) { return FormatBigInteger(targetSpan: false, value, format, format, info, default, out _, out _)!; @@ -1026,7 +1235,10 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo { return FormatBigIntegerToHex(targetSpan, value, fmt, digits, info, destination, out charsWritten, out spanSuccess); } - + if (fmt == 'b' || fmt == 'B') + { + return FormatBigIntegerToBinary(targetSpan, value, digits, destination, out charsWritten, out spanSuccess); + } if (value._bits == null) { diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs index 533d3c623df4d8..5ce1bc05abbc24 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs @@ -79,6 +79,7 @@ public static void RunProviderToStringTests() RunSimpleProviderToStringTests(s_random, "N", nfi, nfi.NumberDecimalDigits, NumberFormatter); RunSimpleProviderToStringTests(s_random, "P", nfi, nfi.PercentDecimalDigits, PercentFormatter); RunSimpleProviderToStringTests(s_random, "X", nfi, 0, HexFormatter); + RunSimpleProviderToStringTests(s_random, "B", nfi, 0, BinaryFormatter); RunSimpleProviderToStringTests(s_random, "R", nfi, 0, DecimalFormatter); } @@ -186,6 +187,17 @@ public static void RunStandardFormatToStringTests() RunStandardFormatToStringTests_Helper(s_random, "x100", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, -100, HexFormatter); RunStandardFormatToStringTests_Helper(s_random, "x" + intMaxPlus1String, CultureInfo.CurrentCulture.NumberFormat.NegativeSign, -101, HexFormatter, true); + // Binary + RunStandardFormatToStringTests_Helper(s_random, "B", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 0, BinaryFormatter); + RunStandardFormatToStringTests_Helper(s_random, "B0", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 0, BinaryFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b1", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 1, BinaryFormatter); + RunStandardFormatToStringTests_Helper(s_random, "B2", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 2, BinaryFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b5", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 5, BinaryFormatter); + RunStandardFormatToStringTests_Helper(s_random, "B33", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 33, BinaryFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b99", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 99, BinaryFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b100", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 100, BinaryFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b" + intMaxPlus1String, CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 101, BinaryFormatter, true); + // RoundTrip RunStandardFormatToStringTests_Helper(s_random, "R", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 0, DecimalFormatter); RunStandardFormatToStringTests_Helper(s_random, "R0", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 0, DecimalFormatter); @@ -308,6 +320,17 @@ public static void RunRegionSpecificStandardFormatToStringTests() RunStandardFormatToStringTests_Helper(s_random, "x100", culture.NumberFormat.NegativeSign, -100, HexFormatter); RunStandardFormatToStringTests_Helper(s_random, "x" + intMaxPlus1String, culture.NumberFormat.NegativeSign, -101, HexFormatter, true); + // Binary + RunStandardFormatToStringTests_Helper(s_random, "B", culture.NumberFormat.NegativeSign, 0, BinaryFormatter); + RunStandardFormatToStringTests_Helper(s_random, "B0", culture.NumberFormat.NegativeSign, 0, BinaryFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b1", culture.NumberFormat.NegativeSign, 1, BinaryFormatter); + RunStandardFormatToStringTests_Helper(s_random, "B2", culture.NumberFormat.NegativeSign, 2, BinaryFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b5", culture.NumberFormat.NegativeSign, 5, BinaryFormatter); + RunStandardFormatToStringTests_Helper(s_random, "B33", culture.NumberFormat.NegativeSign, 33, BinaryFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b99", culture.NumberFormat.NegativeSign, 99, BinaryFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b100", culture.NumberFormat.NegativeSign, 100, BinaryFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b" + intMaxPlus1String, culture.NumberFormat.NegativeSign, 101, BinaryFormatter, true); + // RoundTrip RunStandardFormatToStringTests_Helper(s_random, "R", culture.NumberFormat.NegativeSign, 0, DecimalFormatter); RunStandardFormatToStringTests_Helper(s_random, "R0", culture.NumberFormat.NegativeSign, 0, DecimalFormatter); @@ -1023,6 +1046,23 @@ private static string PercentFormatter(string input, int precision, NumberFormat return pre + GroupFormatDigits(input, nfi.PercentGroupSeparator, nfi.PercentGroupSizes, nfi.PercentDecimalSeparator, precision) + post; } + private static string BinaryFormatter(string input, int precision, NumberFormatInfo nfi) + { + string output = ConvertDecimalToBinary(input, nfi); + + if (output[0] == '1') + { + output = OneString(precision - output.Length) + output; + } + else + { + Debug.Assert(output[0] == '0'); + output = ZeroString(precision - output.Length) + output; + } + + return output; + } + private static string HexFormatter(string input, int precision, NumberFormatInfo nfi) { bool upper = true; @@ -1634,7 +1674,7 @@ private static string GetHexDigitSequence(int min, int max, Random random) } private static string GetRandomInvalidFormatChar(Random random) { - char[] digits = new char[] { 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f', 'G', 'g', 'N', 'n', 'P', 'p', 'X', 'x', 'R', 'r' }; + char[] digits = new char[] { 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f', 'G', 'g', 'N', 'n', 'P', 'p', 'X', 'x', 'B', 'b', 'R', 'r' }; char result = 'C'; while (result == 'C') { @@ -1763,6 +1803,34 @@ private static string ConvertDecimalToHex(string input, bool upper, NumberFormat return output; } + private static string ConvertDecimalToBinary(string input, NumberFormatInfo nfi) + { + string output = string.Empty; + BigInteger bi = BigInteger.Parse(input, nfi); + byte[] bytes = bi.ToByteArray(); + int[] chars = new int[bytes.Length * 8]; + for (int i = 0; i < bytes.Length; i++) + { + chars[i * 8] = bytes[i] % 2; + chars[i * 8 + 1] = (bytes[i] / 2) % 2; + chars[i * 8 + 2] = (bytes[i] / 4) % 2; + chars[i * 8 + 3] = (bytes[i] / 8) % 2; + chars[i * 8 + 4] = (bytes[i] / 16) % 2; + chars[i * 8 + 5] = (bytes[i] / 32) % 2; + chars[i * 8 + 6] = (bytes[i] / 64) % 2; + chars[i * 8 + 7] = (bytes[i] / 128) % 2; + } + + ReadOnlySpan trimmedChars = chars.AsSpan(0, chars.AsSpan().LastIndexOf(chars[^1] == 0 ? 1 : 0) + 2); + + for (int i = trimmedChars.Length - 1; i >= 0; i--) + { + output = $"{output}{trimmedChars[i]}"; + } + + return output; + } + private static string ConvertToExp(string input, int places) { char[] temp = input.Substring(0, places + 2).ToCharArray(); @@ -1938,6 +2006,11 @@ private static string ZeroString(int size) return size >= 1 ? new string('0', size) : string.Empty; } + private static string OneString(int size) + { + return size >= 1 ? new string('1', size) : string.Empty; + } + private static string FString(int size, bool upper) { string ret = string.Empty; diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index 785dc209b24e64..95ed804cd464ec 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs @@ -58,10 +58,12 @@ void Test() VerifyNumberStyles(NumberStyles.AllowExponent, s_random); VerifyNumberStyles(NumberStyles.AllowCurrencySymbol, s_random); VerifyNumberStyles(NumberStyles.AllowHexSpecifier, s_random); + VerifyBinaryNumberStyles(NumberStyles.AllowBinarySpecifier, s_random); //composite NumberStyles VerifyNumberStyles(NumberStyles.Integer, s_random); VerifyNumberStyles(NumberStyles.HexNumber, s_random); + VerifyBinaryNumberStyles(NumberStyles.BinaryNumber, s_random); VerifyNumberStyles(NumberStyles.Number, s_random); VerifyNumberStyles(NumberStyles.Float, s_random); VerifyNumberStyles(NumberStyles.Currency, s_random); @@ -295,6 +297,136 @@ private static void VerifyDefaultParse(Random random) } } + private static void VerifyBinaryNumberStyles(NumberStyles ns, Random random) + { + VerifyParseToString(null, ns, false, null); + VerifyParseToString(string.Empty, ns, false); + VerifyParseToString("0", ns, true); + VerifyParseToString("000", ns, true); + VerifyParseToString("1", ns, true); + VerifyParseToString("001", ns, true); + + // SimpleNumbers - Small + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(GetBinaryDigitSequence(1, 10, random), ns, true); + } + + // SimpleNumbers - Large + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(GetBinaryDigitSequence(100, 1000, random), ns, true); + } + + // Leading White + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString("\u0009\u0009\u0009" + GetBinaryDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingWhite) != 0)); + VerifyParseToString("\u000A\u000A\u000A" + GetBinaryDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingWhite) != 0)); + VerifyParseToString("\u000B\u000B\u000B" + GetBinaryDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingWhite) != 0)); + VerifyParseToString("\u000C\u000C\u000C" + GetBinaryDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingWhite) != 0)); + VerifyParseToString("\u000D\u000D\u000D" + GetBinaryDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingWhite) != 0)); + VerifyParseToString("\u0020\u0020\u0020" + GetBinaryDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingWhite) != 0)); + } + + // Trailing White + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(GetBinaryDigitSequence(1, 100, random) + "\u0009\u0009\u0009", ns, FailureNotExpectedForTrailingWhite(ns, false)); + VerifyParseToString(GetBinaryDigitSequence(1, 100, random) + "\u000A\u000A\u000A", ns, FailureNotExpectedForTrailingWhite(ns, false)); + VerifyParseToString(GetBinaryDigitSequence(1, 100, random) + "\u000B\u000B\u000B", ns, FailureNotExpectedForTrailingWhite(ns, false)); + VerifyParseToString(GetBinaryDigitSequence(1, 100, random) + "\u000C\u000C\u000C", ns, FailureNotExpectedForTrailingWhite(ns, false)); + VerifyParseToString(GetBinaryDigitSequence(1, 100, random) + "\u000D\u000D\u000D", ns, FailureNotExpectedForTrailingWhite(ns, false)); + VerifyParseToString(GetBinaryDigitSequence(1, 100, random) + "\u0020\u0020\u0020", ns, FailureNotExpectedForTrailingWhite(ns, true)); + } + + // Leading Sign + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(CultureInfo.CurrentCulture.NumberFormat.NegativeSign + GetBinaryDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingSign) != 0)); + VerifyParseToString(CultureInfo.CurrentCulture.NumberFormat.PositiveSign + GetBinaryDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingSign) != 0)); + } + + // Trailing Sign + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(GetBinaryDigitSequence(1, 100, random) + CultureInfo.CurrentCulture.NumberFormat.NegativeSign, ns, ((ns & NumberStyles.AllowTrailingSign) != 0)); + VerifyParseToString(GetBinaryDigitSequence(1, 100, random) + CultureInfo.CurrentCulture.NumberFormat.PositiveSign, ns, ((ns & NumberStyles.AllowTrailingSign) != 0)); + } + + // Parentheses + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString("(" + GetBinaryDigitSequence(1, 100, random) + ")", ns, ((ns & NumberStyles.AllowParentheses) != 0)); + } + + // Decimal Point - end + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(GetBinaryDigitSequence(1, 100, random) + CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator, ns, ((ns & NumberStyles.AllowDecimalPoint) != 0)); + } + + // Decimal Point - middle + for (int i = 0; i < s_samples; i++) + { + string digits = GetBinaryDigitSequence(1, 100, random); + VerifyParseToString(digits + CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator + "000", ns, ((ns & NumberStyles.AllowDecimalPoint) != 0), digits); + } + + // Decimal Point - non-zero decimal + for (int i = 0; i < s_samples; i++) + { + string digits = GetBinaryDigitSequence(1, 100, random); + VerifyParseToString(digits + CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator + GetBinaryDigitSequence(20, 25, random), ns, false, digits); + } + + // Exponent + for (int i = 0; i < s_samples; i++) + { + string digits = GetBinaryDigitSequence(1, 100, random); + string exp = GetBinaryDigitSequence(1, 3, random); + int expValue = int.Parse(exp); + string zeros = new string('0', expValue); + //Positive Exponents + VerifyParseToString(digits + "e" + CultureInfo.CurrentCulture.NumberFormat.PositiveSign + exp, ns, ((ns & NumberStyles.AllowExponent) != 0), digits + zeros); + //Negative Exponents + bool valid = ((ns & NumberStyles.AllowExponent) != 0); + for (int j = digits.Length; (valid && (j > 0) && (j > digits.Length - expValue)); j--) + { + if (digits[j - 1] != '0') + { + valid = false; + } + } + if (digits.Length - int.Parse(exp) > 0) + { + VerifyParseToString(digits + "e" + CultureInfo.CurrentCulture.NumberFormat.NegativeSign + exp, ns, valid, digits.Substring(0, digits.Length - int.Parse(exp))); + } + else + { + VerifyParseToString(digits + "e" + CultureInfo.CurrentCulture.NumberFormat.NegativeSign + exp, ns, valid, "0"); + } + } + + // Currency Symbol + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(CultureInfo.CurrentCulture.NumberFormat.CurrencySymbol + GetBinaryDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowCurrencySymbol) != 0)); + } + + // Bin Specifier + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(GetBinaryDigitSequence(1, 15, random), ns, ((ns & NumberStyles.AllowBinarySpecifier) != 0)); + } + + // Invalid Chars + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(GetBinaryDigitSequence(1, 100, random) + GetRandomInvalidChar(random) + GetBinaryDigitSequence(1, 10, random), ns, false); + } + } + private static void VerifyNumberStyles(NumberStyles ns, Random random) { VerifyParseToString(null, ns, false, null); @@ -463,7 +595,7 @@ private static void VerifyFailParseToString(string num1, Type expectedExceptionT private static void VerifyParseToString(string num1, NumberStyles ns, bool failureNotExpected) { - VerifyParseToString(num1, ns, failureNotExpected, Fix(num1.Trim(), ((ns & NumberStyles.AllowHexSpecifier) != 0), failureNotExpected)); + VerifyParseToString(num1, ns, failureNotExpected, Fix(num1.Trim(), ((ns & NumberStyles.AllowHexSpecifier) != 0), (ns & NumberStyles.AllowBinarySpecifier) != 0, failureNotExpected)); } static void VerifyParseSpanToString(string num1, NumberStyles ns, bool failureNotExpected, string expected) @@ -641,6 +773,19 @@ private static string GetHexDigitSequence(int min, int max, Random random) return result; } + private static string GetBinaryDigitSequence(int min, int max, Random random) + { + string result = string.Empty; + int size = random.Next(min, max); + + for (int i = 0; i < size; i++) + { + result += random.Next(0, 2); + } + + return result; + } + private static string GetRandomInvalidChar(Random random) { char[] digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F' }; @@ -669,15 +814,15 @@ private static string GetRandomInvalidChar(Random random) private static string Fix(string input) { - return Fix(input, false); + return Fix(input, false, false); } - private static string Fix(string input, bool isHex) + private static string Fix(string input, bool isHex, bool isBinary) { - return Fix(input, isHex, true); + return Fix(input, isHex, isBinary, true); } - private static string Fix(string input, bool isHex, bool failureNotExpected) + private static string Fix(string input, bool isHex, bool isBinary, bool failureNotExpected) { string output = input; @@ -687,6 +832,10 @@ private static string Fix(string input, bool isHex, bool failureNotExpected) { output = ConvertHexToDecimal(output); } + else if (isBinary) + { + output = ConvertBinaryToDecimal(output); + } while (output.StartsWith("0") & (output.Length > 1)) { output = output.Substring(1); @@ -705,6 +854,30 @@ private static string Fix(string input, bool isHex, bool failureNotExpected) return output; } + private static string ConvertBinaryToDecimal(string input) + { + const int HexBlockSize = 4; + + string compensatedInput = input.Length % HexBlockSize == 0 ? input : new string(input[0], HexBlockSize - input.Length % HexBlockSize) + input; + + var hexBuffer = new List(compensatedInput.Length / HexBlockSize); + + int pos = 0; + while (pos < compensatedInput.Length) + { + int currentHexValue = 0; + + for (int posInHex = HexBlockSize - 1; posInHex >= 0; posInHex--) + { + currentHexValue += int.Parse(compensatedInput[pos].ToString()) * (1 << posInHex); + pos++; + } + hexBuffer.Add(currentHexValue.ToString("X")[0]); + } + + return ConvertHexToDecimal(new string(hexBuffer.ToArray())); + } + private static string ConvertHexToDecimal(string input) { char[] inArr = input.ToCharArray();