From 3c127eaf679aee4d60891700dc2ccc6fbecd5620 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Wed, 26 Apr 2023 19:35:45 +0800 Subject: [PATCH 01/18] Initial draft commit: add FormatBigIntegerToBin(). --- .../src/System/Numerics/BigNumber.cs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) 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..d657c6b2f65ac2 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -1002,6 +1002,71 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig } } + private static string? FormatBigIntegerToBin(BigInteger value, int digits) + { + // Get the bytes that make up the BigInteger. + byte[]? arrayToReturnToPool = null; + Span bits = stackalloc byte[64]; // arbitrary threshold + if (!value.TryWriteOrCountBytes(bits, out int bytesWrittenOrNeeded)) + { + bits = arrayToReturnToPool = ArrayPool.Shared.Rent(bytesWrittenOrNeeded); + bool success = value.TryWriteBytes(bits, out bytesWrittenOrNeeded); + Debug.Assert(success); + } + bits = bits.Slice(0, bytesWrittenOrNeeded); + + Debug.Assert(!bits.IsEmpty); + + byte highByte = bits[bits.Length - 1]; + int charCount = highByte == 0 ? 1 : 8 - byte.LeadingZeroCount(highByte); + charCount += (bits.Length - 1) * 8; + if (digits > charCount) + { + charCount = digits; + } + + // each byte is typically eight chars + var sb = new ValueStringBuilder(stackalloc char[Math.Min(charCount, 512)]); + + if (charCount > 512) + { + sb = new ValueStringBuilder(charCount); + } + + if (digits > charCount) + { + sb.Append(value._sign >= 0 ? '0' : '1', digits - charCount); + } + + if (highByte == 0) + { + sb.Append('0'); + } + else + { + AppendByte(ref sb, highByte, 7 - byte.LeadingZeroCount(highByte)); + } + + for (int i = bits.Length - 2; i >= 0; i--) + { + AppendByte(ref sb, bits[i]); + } + + if (arrayToReturnToPool is not null) + { + ArrayPool.Shared.Return(arrayToReturnToPool); + } + return sb.ToString(); + + 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 _)!; From 64a0eaddce1a303827055c856220071da4369b80 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Fri, 28 Apr 2023 17:37:36 +0800 Subject: [PATCH 02/18] Fix comment: use '?:' to assign ValueStringBuilder variable to make it 'unsafe to return' so that can assign stackalloced. --- .../src/System/Numerics/BigNumber.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) 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 d657c6b2f65ac2..7c5810ff038adf 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -1026,12 +1026,7 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig } // each byte is typically eight chars - var sb = new ValueStringBuilder(stackalloc char[Math.Min(charCount, 512)]); - - if (charCount > 512) - { - sb = new ValueStringBuilder(charCount); - } + ValueStringBuilder sb = charCount > 512 ? new ValueStringBuilder(charCount) : new ValueStringBuilder(stackalloc char[charCount]); if (digits > charCount) { From 4216498c305b65a63af1576f25cbe1b79c5d6c32 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Sat, 29 Apr 2023 11:42:17 +0800 Subject: [PATCH 03/18] Refine FormatBigIntegerToBin(); and consider chars overflow scenario. --- .../src/System/Numerics/BigNumber.cs | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) 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 7c5810ff038adf..3996edd84e1cba 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -1017,20 +1017,30 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig Debug.Assert(!bits.IsEmpty); - byte highByte = bits[bits.Length - 1]; - int charCount = highByte == 0 ? 1 : 8 - byte.LeadingZeroCount(highByte); - charCount += (bits.Length - 1) * 8; - if (digits > charCount) + byte highByte = bits[^1]; + + long tmpCharCount = highByte == 0 ? 1 : 8 - byte.LeadingZeroCount(highByte); + tmpCharCount += (long)(bits.Length - 1) << 3; + + if (tmpCharCount > Array.MaxLength) { - charCount = digits; + Debug.Assert(arrayToReturnToPool is not null); + ArrayPool.Shared.Return(arrayToReturnToPool); + + throw GetException(ParsingStatus.Overflow); } + int charsForBits = (int)tmpCharCount; + + Debug.Assert(digits < Array.MaxLength); + int charsIncludeDigits = digits > charsForBits ? digits : charsForBits; + // each byte is typically eight chars - ValueStringBuilder sb = charCount > 512 ? new ValueStringBuilder(charCount) : new ValueStringBuilder(stackalloc char[charCount]); + ValueStringBuilder sb = charsIncludeDigits > 512 ? new ValueStringBuilder(charsIncludeDigits) : new ValueStringBuilder(stackalloc char[charsIncludeDigits]); - if (digits > charCount) + if (digits > charsForBits) { - sb.Append(value._sign >= 0 ? '0' : '1', digits - charCount); + sb.Append(value._sign >= 0 ? '0' : '1', digits - charsForBits); } if (highByte == 0) From 1bcf7f86bd84a75f475b1038da0279b19b39f0eb Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Mon, 15 May 2023 16:01:19 +0800 Subject: [PATCH 04/18] Update Format code for final binary format definition. --- .../src/System/Numerics/BigNumber.cs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) 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 3996edd84e1cba..b7e5d5cf3abc04 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -1019,8 +1019,8 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig byte highByte = bits[^1]; - long tmpCharCount = highByte == 0 ? 1 : 8 - byte.LeadingZeroCount(highByte); - tmpCharCount += (long)(bits.Length - 1) << 3; + int charsInHighByte = 9 - byte.LeadingZeroCount(value._sign >= 0 ? highByte : (byte)~highByte); + long tmpCharCount = charsInHighByte + ((long)(bits.Length - 1) << 3); if (tmpCharCount > Array.MaxLength) { @@ -1033,7 +1033,7 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig int charsForBits = (int)tmpCharCount; Debug.Assert(digits < Array.MaxLength); - int charsIncludeDigits = digits > charsForBits ? digits : charsForBits; + int charsIncludeDigits = Math.Max(digits, charsForBits); // each byte is typically eight chars ValueStringBuilder sb = charsIncludeDigits > 512 ? new ValueStringBuilder(charsIncludeDigits) : new ValueStringBuilder(stackalloc char[charsIncludeDigits]); @@ -1043,14 +1043,7 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig sb.Append(value._sign >= 0 ? '0' : '1', digits - charsForBits); } - if (highByte == 0) - { - sb.Append('0'); - } - else - { - AppendByte(ref sb, highByte, 7 - byte.LeadingZeroCount(highByte)); - } + AppendByte(ref sb, highByte, charsInHighByte - 1); for (int i = bits.Length - 2; i >= 0; i--) { From 63923da2fc760f876f9d2a7d4c92c63bf8a22c79 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Tue, 23 May 2023 11:24:44 +0800 Subject: [PATCH 05/18] Refine FormatBigIntegerToBin(). --- .../src/System/Numerics/BigNumber.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) 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 b7e5d5cf3abc04..19f5bff1c64ce6 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -1006,28 +1006,28 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig { // Get the bytes that make up the BigInteger. byte[]? arrayToReturnToPool = null; - Span bits = stackalloc byte[64]; // arbitrary threshold - if (!value.TryWriteOrCountBytes(bits, out int bytesWrittenOrNeeded)) + Span bytes = stackalloc byte[64]; // arbitrary threshold + if (!value.TryWriteOrCountBytes(bytes, out int bytesWrittenOrNeeded)) { - bits = arrayToReturnToPool = ArrayPool.Shared.Rent(bytesWrittenOrNeeded); - bool success = value.TryWriteBytes(bits, out bytesWrittenOrNeeded); + bytes = arrayToReturnToPool = ArrayPool.Shared.Rent(bytesWrittenOrNeeded); + bool success = value.TryWriteBytes(bytes, out _); Debug.Assert(success); } - bits = bits.Slice(0, bytesWrittenOrNeeded); + bytes = bytes.Slice(0, bytesWrittenOrNeeded); - Debug.Assert(!bits.IsEmpty); + Debug.Assert(!bytes.IsEmpty); - byte highByte = bits[^1]; + byte highByte = bytes[^1]; int charsInHighByte = 9 - byte.LeadingZeroCount(value._sign >= 0 ? highByte : (byte)~highByte); - long tmpCharCount = charsInHighByte + ((long)(bits.Length - 1) << 3); + long tmpCharCount = charsInHighByte + ((long)(bytes.Length - 1) << 3); if (tmpCharCount > Array.MaxLength) { Debug.Assert(arrayToReturnToPool is not null); ArrayPool.Shared.Return(arrayToReturnToPool); - throw GetException(ParsingStatus.Overflow); + throw new FormatException(SR.Format_TooLarge); } int charsForBits = (int)tmpCharCount; @@ -1045,9 +1045,9 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig AppendByte(ref sb, highByte, charsInHighByte - 1); - for (int i = bits.Length - 2; i >= 0; i--) + for (int i = bytes.Length - 2; i >= 0; i--) { - AppendByte(ref sb, bits[i]); + AppendByte(ref sb, bytes[i]); } if (arrayToReturnToPool is not null) From 19c701dedc28f489c4753befaddc52d7643ce29d Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Thu, 25 May 2023 15:45:54 +0800 Subject: [PATCH 06/18] consider case where output is span --- .../src/System/Numerics/BigNumber.cs | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) 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 19f5bff1c64ce6..7685fef29806a9 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -1002,7 +1002,7 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig } } - private static string? FormatBigIntegerToBin(BigInteger value, int digits) + private static string? FormatBigIntegerToBin(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; @@ -1035,6 +1035,18 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig Debug.Assert(digits < Array.MaxLength); int charsIncludeDigits = Math.Max(digits, charsForBits); + if (targetSpan && charsIncludeDigits > destination.Length) + { + if (arrayToReturnToPool is not null) + { + ArrayPool.Shared.Return(arrayToReturnToPool); + } + + charsWritten = 0; + spanSuccess = false; + return null; + } + // each byte is typically eight chars ValueStringBuilder sb = charsIncludeDigits > 512 ? new ValueStringBuilder(charsIncludeDigits) : new ValueStringBuilder(stackalloc char[charsIncludeDigits]); @@ -1054,6 +1066,16 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig { ArrayPool.Shared.Return(arrayToReturnToPool); } + + if (targetSpan) + { + spanSuccess = sb.TryCopyTo(destination, out charsWritten); + Debug.Assert(spanSuccess); + return null; + } + + charsWritten = 0; + spanSuccess = false; return sb.ToString(); static void AppendByte(ref ValueStringBuilder sb, byte b, int startHighBit = 7) @@ -1089,7 +1111,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 FormatBigIntegerToBin(targetSpan, value, digits, destination, out charsWritten, out spanSuccess); + } if (value._bits == null) { From 56e701f634a3d56fd1808fb158877777282ce476 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Sat, 27 May 2023 10:33:03 +0800 Subject: [PATCH 07/18] Turn to use try..finally to return array pool. --- .../src/System/Numerics/BigNumber.cs | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) 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 7685fef29806a9..976493d005a292 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -1035,49 +1035,49 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig Debug.Assert(digits < Array.MaxLength); int charsIncludeDigits = Math.Max(digits, charsForBits); - if (targetSpan && charsIncludeDigits > destination.Length) + try { - if (arrayToReturnToPool is not null) + if (targetSpan && charsIncludeDigits > destination.Length) { - ArrayPool.Shared.Return(arrayToReturnToPool); + charsWritten = 0; + spanSuccess = false; + return null; } - charsWritten = 0; - spanSuccess = false; - return null; - } + // each byte is typically eight chars + ValueStringBuilder sb = charsIncludeDigits > 512 ? new ValueStringBuilder(charsIncludeDigits) : new ValueStringBuilder(stackalloc char[charsIncludeDigits]); - // each byte is typically eight chars - ValueStringBuilder sb = charsIncludeDigits > 512 ? new ValueStringBuilder(charsIncludeDigits) : new ValueStringBuilder(stackalloc char[charsIncludeDigits]); + if (digits > charsForBits) + { + sb.Append(value._sign >= 0 ? '0' : '1', digits - charsForBits); + } - if (digits > charsForBits) - { - sb.Append(value._sign >= 0 ? '0' : '1', digits - charsForBits); - } + AppendByte(ref sb, highByte, charsInHighByte - 1); - AppendByte(ref sb, highByte, charsInHighByte - 1); + for (int i = bytes.Length - 2; i >= 0; i--) + { + AppendByte(ref sb, bytes[i]); + } - for (int i = bytes.Length - 2; i >= 0; i--) - { - AppendByte(ref sb, bytes[i]); - } + if (targetSpan) + { + spanSuccess = sb.TryCopyTo(destination, out charsWritten); + Debug.Assert(spanSuccess); + return null; + } - if (arrayToReturnToPool is not null) - { - ArrayPool.Shared.Return(arrayToReturnToPool); + charsWritten = 0; + spanSuccess = false; + return sb.ToString(); } - - if (targetSpan) + finally { - spanSuccess = sb.TryCopyTo(destination, out charsWritten); - Debug.Assert(spanSuccess); - return null; + if (arrayToReturnToPool is not null) + { + ArrayPool.Shared.Return(arrayToReturnToPool); + } } - charsWritten = 0; - spanSuccess = false; - return sb.ToString(); - static void AppendByte(ref ValueStringBuilder sb, byte b, int startHighBit = 7) { for (int i = startHighBit; i >= 0; i--) From 817c58ca3d9d36c06dfb4553663c0579cee7d500 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Sat, 27 May 2023 20:37:38 +0800 Subject: [PATCH 08/18] Initial add method BinNumberToBigInteger(). --- .../src/System/Numerics/BigNumber.cs | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) 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 976493d005a292..310a39e3a3134c 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -511,6 +511,96 @@ private static ParsingStatus HexNumberToBigInteger(ref BigNumberBuffer number, o } } + private static ParsingStatus BinNumberToBigInteger(ref BigNumberBuffer number, out BigInteger result) + { + if (number.digits is null || number.digits.Length < 2) + { + result = default; + return ParsingStatus.Failed; + } + + const int DigitsPerBlock = 32; + + int totalDigitCount = number.digits.Length - 1; // Ignore trailing '\0' + + int blockCount = Math.DivRem(totalDigitCount, DigitsPerBlock, out int remainingDigitsInBlock); + if (remainingDigitsInBlock == 0) + { + remainingDigitsInBlock = DigitsPerBlock; + } + else + { + blockCount++; + } + + Debug.Assert(number.digits[0] is '0' or '1'); + bool isNegative = number.digits[0] == '1'; + + uint[]? arrayFromPool = null; + Span bufferSpan = blockCount <= BigIntegerCalculator.StackAllocThreshold ? + stackalloc uint[blockCount] : (arrayFromPool = ArrayPool.Shared.Rent(blockCount)).AsSpan(0, blockCount); + + try + { + uint currentBlock = isNegative ? 0xFF_FF_FF_FF : 0x0; + int bufferPos = blockCount; + foreach (ReadOnlyMemory chunkMem in number.digits.GetChunks()) + { + ReadOnlySpan chunk = chunkMem.Span; + foreach (char c in chunk) + { + if (c == '\0') + { + break; + } + + Debug.Assert(c is '0' or '1'); + currentBlock = (currentBlock << 1) | (uint)(c - '0'); + + if (--remainingDigitsInBlock == 0) + { + bufferSpan[--bufferPos] = currentBlock; + remainingDigitsInBlock = DigitsPerBlock; + + // we do not need to reset currentBlock now, because it should always set all its bits by left shift in subsequent iterations + } + } + + Debug.Assert(bufferPos > 0 || remainingDigitsInBlock == DigitsPerBlock); + } + + Debug.Assert(bufferPos == 0 && remainingDigitsInBlock == DigitsPerBlock); + + if (isNegative) + { + NumericsHelpers.DangerousMakeTwosComplement(bufferSpan); + } + + bufferSpan = bufferSpan.TrimEnd(0u); + if (bufferSpan.IsEmpty) + { + result = BigInteger.Zero; + } + else if (bufferSpan.Length == 1 && bufferSpan[0] <= int.MaxValue) + { + result = new BigInteger((int)(isNegative ? -bufferSpan[0] : bufferSpan[0]), (uint[]?)null); + } + else + { + result = new BigInteger(isNegative ? -1 : 1, bufferSpan.ToArray()); + } + + 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. // From 24d88c76c42faf21d8993d34e94d21e427dd28c3 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Tue, 30 May 2023 14:59:53 +0800 Subject: [PATCH 09/18] Update FormatProvider.Number.cs to support AllowBinarySpecifier. --- .../Common/src/System/Globalization/FormatProvider.Number.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs b/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs index 0a7a7a4a89536c..9f3ebc72eeff08 100644 --- a/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs +++ b/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs @@ -440,11 +440,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 ((options & NumberStyles.AllowBinarySpecifier) == 0 ? char.IsAsciiDigit(ch) || ((options & NumberStyles.AllowHexSpecifier) != 0 && char.IsBetween((char)(ch | 0x20), 'a', 'f')) : char.IsBetween(ch, '0', '1')) { state |= StateDigits; - if (ch != '0' || (state & StateNonZero) != 0 || (bigNumber && ((options & NumberStyles.AllowHexSpecifier) != 0))) + if (ch != '0' || (state & StateNonZero) != 0 || (bigNumber && (options & (NumberStyles.AllowHexSpecifier | NumberStyles.AllowBinarySpecifier)) != 0)) { if (digCount < maxParseDigits) { From 5a90e15bdca673773894e60ea314617e1852af4c Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Tue, 30 May 2023 15:19:46 +0800 Subject: [PATCH 10/18] Use BinNumberToBigInteger(). --- .../src/System/Numerics/BigNumber.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 310a39e3a3134c..47bc8382b3e183 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -371,10 +371,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 BinNumberToBigInteger(ref bigNumber, out result); } + + return NumberToBigInteger(ref bigNumber, out result); } internal static BigInteger ParseBigInteger(string value, NumberStyles style, NumberFormatInfo info) From 3987f713a989ae3a152c60178b8eedf6373d8a0d Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Tue, 30 May 2023 20:24:38 +0800 Subject: [PATCH 11/18] Add tests of Format. --- .../BigInteger/BigIntegerToStringTests.cs | 75 ++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs index 533d3c623df4d8..690fae74b3bbd8 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, BinFormatter); 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, BinFormatter); + RunStandardFormatToStringTests_Helper(s_random, "B0", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 0, BinFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b1", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 1, BinFormatter); + RunStandardFormatToStringTests_Helper(s_random, "B2", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 2, BinFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b5", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 5, BinFormatter); + RunStandardFormatToStringTests_Helper(s_random, "B33", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 33, BinFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b99", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 99, BinFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b100", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 100, BinFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b" + intMaxPlus1String, CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 101, BinFormatter, 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, BinFormatter); + RunStandardFormatToStringTests_Helper(s_random, "B0", culture.NumberFormat.NegativeSign, 0, BinFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b1", culture.NumberFormat.NegativeSign, 1, BinFormatter); + RunStandardFormatToStringTests_Helper(s_random, "B2", culture.NumberFormat.NegativeSign, 2, BinFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b5", culture.NumberFormat.NegativeSign, 5, BinFormatter); + RunStandardFormatToStringTests_Helper(s_random, "B33", culture.NumberFormat.NegativeSign, 33, BinFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b99", culture.NumberFormat.NegativeSign, 99, BinFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b100", culture.NumberFormat.NegativeSign, 100, BinFormatter); + RunStandardFormatToStringTests_Helper(s_random, "b" + intMaxPlus1String, culture.NumberFormat.NegativeSign, 101, BinFormatter, 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 BinFormatter(string input, int precision, NumberFormatInfo nfi) + { + string output = ConvertDecimalToBin(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 ConvertDecimalToBin(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; From 8b58eb7f1a01411768b055d348e7c0c5c82bb988 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Thu, 1 Jun 2023 14:44:07 +0800 Subject: [PATCH 12/18] Add tests of Parse(). --- .../src/System/Numerics/BigNumber.cs | 3 +- .../tests/BigInteger/parse.cs | 183 +++++++++++++++++- 2 files changed, 180 insertions(+), 6 deletions(-) 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 47bc8382b3e183..6710f3b63ba518 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 }; diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index 785dc209b24e64..b15959fb7c81b6 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(GetBinDigitSequence(1, 10, random), ns, true); + } + + // SimpleNumbers - Large + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(GetBinDigitSequence(100, 1000, random), ns, true); + } + + // Leading White + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString("\u0009\u0009\u0009" + GetBinDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingWhite) != 0)); + VerifyParseToString("\u000A\u000A\u000A" + GetBinDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingWhite) != 0)); + VerifyParseToString("\u000B\u000B\u000B" + GetBinDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingWhite) != 0)); + VerifyParseToString("\u000C\u000C\u000C" + GetBinDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingWhite) != 0)); + VerifyParseToString("\u000D\u000D\u000D" + GetBinDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingWhite) != 0)); + VerifyParseToString("\u0020\u0020\u0020" + GetBinDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingWhite) != 0)); + } + + // Trailing White + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(GetBinDigitSequence(1, 100, random) + "\u0009\u0009\u0009", ns, FailureNotExpectedForTrailingWhite(ns, false)); + VerifyParseToString(GetBinDigitSequence(1, 100, random) + "\u000A\u000A\u000A", ns, FailureNotExpectedForTrailingWhite(ns, false)); + VerifyParseToString(GetBinDigitSequence(1, 100, random) + "\u000B\u000B\u000B", ns, FailureNotExpectedForTrailingWhite(ns, false)); + VerifyParseToString(GetBinDigitSequence(1, 100, random) + "\u000C\u000C\u000C", ns, FailureNotExpectedForTrailingWhite(ns, false)); + VerifyParseToString(GetBinDigitSequence(1, 100, random) + "\u000D\u000D\u000D", ns, FailureNotExpectedForTrailingWhite(ns, false)); + VerifyParseToString(GetBinDigitSequence(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 + GetBinDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingSign) != 0)); + VerifyParseToString(CultureInfo.CurrentCulture.NumberFormat.PositiveSign + GetBinDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingSign) != 0)); + } + + // Trailing Sign + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(GetBinDigitSequence(1, 100, random) + CultureInfo.CurrentCulture.NumberFormat.NegativeSign, ns, ((ns & NumberStyles.AllowTrailingSign) != 0)); + VerifyParseToString(GetBinDigitSequence(1, 100, random) + CultureInfo.CurrentCulture.NumberFormat.PositiveSign, ns, ((ns & NumberStyles.AllowTrailingSign) != 0)); + } + + // Parentheses + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString("(" + GetBinDigitSequence(1, 100, random) + ")", ns, ((ns & NumberStyles.AllowParentheses) != 0)); + } + + // Decimal Point - end + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(GetBinDigitSequence(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 = GetBinDigitSequence(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 = GetBinDigitSequence(1, 100, random); + VerifyParseToString(digits + CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator + GetBinDigitSequence(20, 25, random), ns, false, digits); + } + + // Exponent + for (int i = 0; i < s_samples; i++) + { + string digits = GetBinDigitSequence(1, 100, random); + string exp = GetBinDigitSequence(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 + GetBinDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowCurrencySymbol) != 0)); + } + + // Bin Specifier + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(GetBinDigitSequence(1, 15, random), ns, ((ns & NumberStyles.AllowBinarySpecifier) != 0)); + } + + // Invalid Chars + for (int i = 0; i < s_samples; i++) + { + VerifyParseToString(GetBinDigitSequence(1, 100, random) + GetRandomInvalidChar(random) + GetBinDigitSequence(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 GetBinDigitSequence(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 = ConvertBinToDecimal(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 ConvertBinToDecimal(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(); From 6cea91abae2b9e7675e9f9b652ad2a0b1111ba9b Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Thu, 1 Jun 2023 16:00:56 +0800 Subject: [PATCH 13/18] Improve Format(): use ValueStringBuilder just as wrapper for destination span, so to save extra buffer allocation and copy in ValueStringBuilder. --- .../src/System/Numerics/BigNumber.cs | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) 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 6710f3b63ba518..2c7829017790bf 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -1131,15 +1131,25 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig try { - if (targetSpan && charsIncludeDigits > destination.Length) + scoped ValueStringBuilder sb; + if (targetSpan) { - charsWritten = 0; - spanSuccess = false; - return null; - } + if (charsIncludeDigits > destination.Length) + { + charsWritten = 0; + spanSuccess = false; + return null; + } - // each byte is typically eight chars - ValueStringBuilder sb = charsIncludeDigits > 512 ? new ValueStringBuilder(charsIncludeDigits) : new ValueStringBuilder(stackalloc char[charsIncludeDigits]); + // 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[charsIncludeDigits]); + } if (digits > charsForBits) { @@ -1153,10 +1163,12 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig AppendByte(ref sb, bytes[i]); } + Debug.Assert(sb.Length == charsIncludeDigits); + if (targetSpan) { - spanSuccess = sb.TryCopyTo(destination, out charsWritten); - Debug.Assert(spanSuccess); + charsWritten = charsIncludeDigits; + spanSuccess = true; return null; } From 3007b4871ebf69ef998ed75c5a0a217603e36544 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Fri, 2 Jun 2023 10:41:06 +0800 Subject: [PATCH 14/18] Fix comment: use ch == '0' || ch == '1' --- .../Common/src/System/Globalization/FormatProvider.Number.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs b/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs index 9f3ebc72eeff08..f12ceaa4ee47bd 100644 --- a/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs +++ b/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs @@ -440,7 +440,7 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles int digEnd = 0; while (true) { - if ((options & NumberStyles.AllowBinarySpecifier) == 0 ? char.IsAsciiDigit(ch) || ((options & NumberStyles.AllowHexSpecifier) != 0 && char.IsBetween((char)(ch | 0x20), 'a', 'f')) : char.IsBetween(ch, '0', '1')) + if ((options & NumberStyles.AllowBinarySpecifier) == 0 ? char.IsAsciiDigit(ch) || ((options & NumberStyles.AllowHexSpecifier) != 0 && char.IsBetween((char)(ch | 0x20), 'a', 'f')) : ch == '0' || ch == '1') { state |= StateDigits; From cd0a03d2bb00d21abaed6e731cef5d4194500aa3 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Fri, 25 Aug 2023 22:20:23 +0800 Subject: [PATCH 15/18] Fix comment: refactor ParseNumber() to extract common abstract operations for previous Hex and new Binary. --- .../Globalization/FormatProvider.Number.cs | 51 ++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs b/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs index f12ceaa4ee47bd..fd290df9cf7b5d 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 IDigitValidator + { + static abstract bool IsValidChar(char c); + static abstract bool IsHexBinary(); + } + + private readonly struct IntegerDigitValidator : IDigitValidator + { + public static bool IsValidChar(char c) => char.IsAsciiDigit(c); + + public static bool IsHexBinary() => false; + } + + private readonly struct HexDigitValidator : IDigitValidator + { + public static bool IsValidChar(char c) => char.IsAsciiHexDigit(c); + + public static bool IsHexBinary() => true; + } + + private readonly struct BinaryDigitValidator : IDigitValidator + { + public static bool IsValidChar(char c) + { + return c is '0' or '1'; + } + + public static bool IsHexBinary() => 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 TDigitValidator : struct, IDigitValidator { 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 ((options & NumberStyles.AllowBinarySpecifier) == 0 ? char.IsAsciiDigit(ch) || ((options & NumberStyles.AllowHexSpecifier) != 0 && char.IsBetween((char)(ch | 0x20), 'a', 'f')) : ch == '0' || ch == '1') + if (TDigitValidator.IsValidChar(ch)) { state |= StateDigits; - if (ch != '0' || (state & StateNonZero) != 0 || (bigNumber && (options & (NumberStyles.AllowHexSpecifier | NumberStyles.AllowBinarySpecifier)) != 0)) + if (ch != '0' || (state & StateNonZero) != 0 || (bigNumber && TDigitValidator.IsHexBinary())) { if (digCount < maxParseDigits) { From f22d2e9b6a90530617623ae2c7710dcf5fb0ab17 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Fri, 1 Sep 2023 15:25:38 +0800 Subject: [PATCH 16/18] Fix comment: refine naming; make BinNumberToBigInteger() general pattern similar as HexNumberToBigInteger's --- .../Globalization/FormatProvider.Number.cs | 34 ++++---- .../src/System/Numerics/BigNumber.cs | 82 ++++++++++++------- 2 files changed, 68 insertions(+), 48 deletions(-) diff --git a/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs b/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs index fd290df9cf7b5d..828db72caea4a9 100644 --- a/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs +++ b/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs @@ -356,34 +356,34 @@ private static unsafe bool AllowHyphenDuringParsing(NumberFormatInfo info) return ret; } - private interface IDigitValidator + private interface IDigitParser { static abstract bool IsValidChar(char c); - static abstract bool IsHexBinary(); + static abstract bool IsHexOrBinaryParser(); } - private readonly struct IntegerDigitValidator : IDigitValidator + private readonly struct IntegerDigitParser : IDigitParser { public static bool IsValidChar(char c) => char.IsAsciiDigit(c); - public static bool IsHexBinary() => false; + public static bool IsHexOrBinaryParser() => false; } - private readonly struct HexDigitValidator : IDigitValidator + private readonly struct HexDigitParser : IDigitParser { - public static bool IsValidChar(char c) => char.IsAsciiHexDigit(c); + public static bool IsValidChar(char c) => HexConverter.IsHexChar((int)c); - public static bool IsHexBinary() => true; + public static bool IsHexOrBinaryParser() => true; } - private readonly struct BinaryDigitValidator : IDigitValidator + private readonly struct BinaryDigitParser : IDigitParser { public static bool IsValidChar(char c) { - return c is '0' or '1'; + return (uint)c - '0' <= 1; } - public static bool IsHexBinary() => true; + public static bool IsHexOrBinaryParser() => true; } @@ -391,19 +391,19 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles { if ((options & NumberStyles.AllowHexSpecifier) != 0) { - return ParseNumberStyle(ref str, strEnd, options, ref number, sb, numfmt, parseDecimal); + 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); } - 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 TDigitValidator : struct, IDigitValidator + 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); @@ -487,11 +487,11 @@ private static unsafe bool ParseNumberStyle(ref char* str, char int digEnd = 0; while (true) { - if (TDigitValidator.IsValidChar(ch)) + if (TDigitParser.IsValidChar(ch)) { state |= StateDigits; - if (ch != '0' || (state & StateNonZero) != 0 || (bigNumber && TDigitValidator.IsHexBinary())) + if (ch != '0' || (state & StateNonZero) != 0 || (bigNumber && TDigitParser.IsHexOrBinaryParser())) { if (digCount < maxParseDigits) { 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 2c7829017790bf..243bac508e20e7 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -517,7 +517,7 @@ private static ParsingStatus HexNumberToBigInteger(ref BigNumberBuffer number, o private static ParsingStatus BinNumberToBigInteger(ref BigNumberBuffer number, out BigInteger result) { - if (number.digits is null || number.digits.Length < 2) + if (number.digits is null || number.digits.Length == 0) { result = default; return ParsingStatus.Failed; @@ -526,74 +526,92 @@ private static ParsingStatus BinNumberToBigInteger(ref BigNumberBuffer number, o const int DigitsPerBlock = 32; int totalDigitCount = number.digits.Length - 1; // Ignore trailing '\0' + int partialDigitCount; - int blockCount = Math.DivRem(totalDigitCount, DigitsPerBlock, out int remainingDigitsInBlock); - if (remainingDigitsInBlock == 0) + (int blockCount, int remainder) = int.DivRem(totalDigitCount, DigitsPerBlock); + if (remainder == 0) { - remainingDigitsInBlock = DigitsPerBlock; + partialDigitCount = 0; } else { blockCount++; + partialDigitCount = DigitsPerBlock - 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 bufferSpan = blockCount <= BigIntegerCalculator.StackAllocThreshold ? - stackalloc uint[blockCount] : (arrayFromPool = ArrayPool.Shared.Rent(blockCount)).AsSpan(0, blockCount); + Span buffer = ((uint)blockCount <= BigIntegerCalculator.StackAllocThreshold + ? stackalloc uint[BigIntegerCalculator.StackAllocThreshold] + : arrayFromPool = ArrayPool.Shared.Rent(blockCount)).Slice(0, blockCount); + + int bufferPos = blockCount - 1; try { - uint currentBlock = isNegative ? 0xFF_FF_FF_FF : 0x0; - int bufferPos = blockCount; - foreach (ReadOnlyMemory chunkMem in number.digits.GetChunks()) + foreach (ReadOnlyMemory digitsChunkMem in number.digits.GetChunks()) { - ReadOnlySpan chunk = chunkMem.Span; - foreach (char c in chunk) + ReadOnlySpan chunkDigits = digitsChunkMem.Span; + for (int i = 0; i < chunkDigits.Length; i++) { - if (c == '\0') + char digitChar = chunkDigits[i]; + if (digitChar == '\0') { break; } - Debug.Assert(c is '0' or '1'); - currentBlock = (currentBlock << 1) | (uint)(c - '0'); + Debug.Assert(digitChar is '0' or '1'); + currentBlock = (currentBlock << 1) | (uint)(digitChar - '0'); + partialDigitCount++; - if (--remainingDigitsInBlock == 0) + if (partialDigitCount == DigitsPerBlock) { - bufferSpan[--bufferPos] = currentBlock; - remainingDigitsInBlock = DigitsPerBlock; + 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(bufferPos > 0 || remainingDigitsInBlock == DigitsPerBlock); } - Debug.Assert(bufferPos == 0 && remainingDigitsInBlock == DigitsPerBlock); + Debug.Assert(partialDigitCount == 0 && bufferPos == -1); - if (isNegative) - { - NumericsHelpers.DangerousMakeTwosComplement(bufferSpan); - } + buffer = buffer.TrimEnd(0u); + + int sign; + uint[]? bits; - bufferSpan = bufferSpan.TrimEnd(0u); - if (bufferSpan.IsEmpty) + if (buffer.IsEmpty) { - result = BigInteger.Zero; + sign = 0; + bits = null; } - else if (bufferSpan.Length == 1 && bufferSpan[0] <= int.MaxValue) + else if (buffer.Length == 1) { - result = new BigInteger((int)(isNegative ? -bufferSpan[0] : bufferSpan[0]), (uint[]?)null); + sign = (int)buffer[0]; + bits = null; + + if ((!isNegative && sign < 0) || sign == int.MinValue) + { + bits = new[] { (uint)sign }; + sign = isNegative ? -1 : 1; + } } else { - result = new BigInteger(isNegative ? -1 : 1, bufferSpan.ToArray()); + sign = isNegative ? -1 : 1; + bits = buffer.ToArray(); + + if (isNegative) + { + NumericsHelpers.DangerousMakeTwosComplement(bits); + } } + result = new BigInteger(sign, bits); return ParsingStatus.OK; } finally @@ -1148,7 +1166,9 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig else { // each byte is typically eight chars - sb = charsIncludeDigits > 512 ? new ValueStringBuilder(charsIncludeDigits) : new ValueStringBuilder(stackalloc char[charsIncludeDigits]); + sb = charsIncludeDigits > 512 + ? new ValueStringBuilder(charsIncludeDigits) + : new ValueStringBuilder(stackalloc char[512].Slice(0, charsIncludeDigits)); } if (digits > charsForBits) From 17434aa49a6cfe0fb429ad5b57b785134b12765e Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Fri, 15 Sep 2023 10:10:38 +0800 Subject: [PATCH 17/18] Fix comment: use internal 'kcbitUint'. --- .../src/System/Numerics/BigInteger.cs | 8 ++++---- .../src/System/Numerics/BigNumber.cs | 8 +++----- 2 files changed, 7 insertions(+), 9 deletions(-) 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 243bac508e20e7..03d21426191e73 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -523,12 +523,10 @@ private static ParsingStatus BinNumberToBigInteger(ref BigNumberBuffer number, o return ParsingStatus.Failed; } - const int DigitsPerBlock = 32; - int totalDigitCount = number.digits.Length - 1; // Ignore trailing '\0' int partialDigitCount; - (int blockCount, int remainder) = int.DivRem(totalDigitCount, DigitsPerBlock); + (int blockCount, int remainder) = int.DivRem(totalDigitCount, BigInteger.kcbitUint); if (remainder == 0) { partialDigitCount = 0; @@ -536,7 +534,7 @@ private static ParsingStatus BinNumberToBigInteger(ref BigNumberBuffer number, o else { blockCount++; - partialDigitCount = DigitsPerBlock - remainder; + partialDigitCount = BigInteger.kcbitUint - remainder; } Debug.Assert(number.digits[0] is '0' or '1'); @@ -567,7 +565,7 @@ private static ParsingStatus BinNumberToBigInteger(ref BigNumberBuffer number, o currentBlock = (currentBlock << 1) | (uint)(digitChar - '0'); partialDigitCount++; - if (partialDigitCount == DigitsPerBlock) + if (partialDigitCount == BigInteger.kcbitUint) { buffer[bufferPos--] = currentBlock; partialDigitCount = 0; From bdfddf51e5d66cabb5b1fc955aa8b4965ad94e39 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Thu, 5 Oct 2023 15:52:42 +0800 Subject: [PATCH 18/18] Fix comment: rename 'Bin' method names to 'Binary' ones; remove unnecessary Slice(). --- .../src/System/Numerics/BigNumber.cs | 10 +-- .../BigInteger/BigIntegerToStringTests.cs | 44 ++++++------- .../tests/BigInteger/parse.cs | 62 +++++++++---------- 3 files changed, 58 insertions(+), 58 deletions(-) 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 03d21426191e73..0a05f0dddd2946 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -375,7 +375,7 @@ internal static ParsingStatus TryParseBigInteger(ReadOnlySpan value, Numbe if ((style & NumberStyles.AllowBinarySpecifier) != 0) { - return BinNumberToBigInteger(ref bigNumber, out result); + return BinaryNumberToBigInteger(ref bigNumber, out result); } return NumberToBigInteger(ref bigNumber, out result); @@ -515,7 +515,7 @@ private static ParsingStatus HexNumberToBigInteger(ref BigNumberBuffer number, o } } - private static ParsingStatus BinNumberToBigInteger(ref BigNumberBuffer number, out BigInteger result) + private static ParsingStatus BinaryNumberToBigInteger(ref BigNumberBuffer number, out BigInteger result) { if (number.digits is null || number.digits.Length == 0) { @@ -1112,7 +1112,7 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig } } - private static string? FormatBigIntegerToBin(bool targetSpan, BigInteger value, int digits, Span destination, out int charsWritten, out bool spanSuccess) + 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; @@ -1166,7 +1166,7 @@ internal static char ParseFormatSpecifier(ReadOnlySpan format, out int dig // each byte is typically eight chars sb = charsIncludeDigits > 512 ? new ValueStringBuilder(charsIncludeDigits) - : new ValueStringBuilder(stackalloc char[512].Slice(0, charsIncludeDigits)); + : new ValueStringBuilder(stackalloc char[512]); } if (digits > charsForBits) @@ -1237,7 +1237,7 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan fo } if (fmt == 'b' || fmt == 'B') { - return FormatBigIntegerToBin(targetSpan, value, digits, destination, out charsWritten, out spanSuccess); + 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 690fae74b3bbd8..5ce1bc05abbc24 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs @@ -79,7 +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, BinFormatter); + RunSimpleProviderToStringTests(s_random, "B", nfi, 0, BinaryFormatter); RunSimpleProviderToStringTests(s_random, "R", nfi, 0, DecimalFormatter); } @@ -188,15 +188,15 @@ public static void RunStandardFormatToStringTests() RunStandardFormatToStringTests_Helper(s_random, "x" + intMaxPlus1String, CultureInfo.CurrentCulture.NumberFormat.NegativeSign, -101, HexFormatter, true); // Binary - RunStandardFormatToStringTests_Helper(s_random, "B", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 0, BinFormatter); - RunStandardFormatToStringTests_Helper(s_random, "B0", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 0, BinFormatter); - RunStandardFormatToStringTests_Helper(s_random, "b1", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 1, BinFormatter); - RunStandardFormatToStringTests_Helper(s_random, "B2", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 2, BinFormatter); - RunStandardFormatToStringTests_Helper(s_random, "b5", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 5, BinFormatter); - RunStandardFormatToStringTests_Helper(s_random, "B33", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 33, BinFormatter); - RunStandardFormatToStringTests_Helper(s_random, "b99", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 99, BinFormatter); - RunStandardFormatToStringTests_Helper(s_random, "b100", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 100, BinFormatter); - RunStandardFormatToStringTests_Helper(s_random, "b" + intMaxPlus1String, CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 101, BinFormatter, true); + 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); @@ -321,15 +321,15 @@ public static void RunRegionSpecificStandardFormatToStringTests() RunStandardFormatToStringTests_Helper(s_random, "x" + intMaxPlus1String, culture.NumberFormat.NegativeSign, -101, HexFormatter, true); // Binary - RunStandardFormatToStringTests_Helper(s_random, "B", culture.NumberFormat.NegativeSign, 0, BinFormatter); - RunStandardFormatToStringTests_Helper(s_random, "B0", culture.NumberFormat.NegativeSign, 0, BinFormatter); - RunStandardFormatToStringTests_Helper(s_random, "b1", culture.NumberFormat.NegativeSign, 1, BinFormatter); - RunStandardFormatToStringTests_Helper(s_random, "B2", culture.NumberFormat.NegativeSign, 2, BinFormatter); - RunStandardFormatToStringTests_Helper(s_random, "b5", culture.NumberFormat.NegativeSign, 5, BinFormatter); - RunStandardFormatToStringTests_Helper(s_random, "B33", culture.NumberFormat.NegativeSign, 33, BinFormatter); - RunStandardFormatToStringTests_Helper(s_random, "b99", culture.NumberFormat.NegativeSign, 99, BinFormatter); - RunStandardFormatToStringTests_Helper(s_random, "b100", culture.NumberFormat.NegativeSign, 100, BinFormatter); - RunStandardFormatToStringTests_Helper(s_random, "b" + intMaxPlus1String, culture.NumberFormat.NegativeSign, 101, BinFormatter, true); + 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); @@ -1046,9 +1046,9 @@ private static string PercentFormatter(string input, int precision, NumberFormat return pre + GroupFormatDigits(input, nfi.PercentGroupSeparator, nfi.PercentGroupSizes, nfi.PercentDecimalSeparator, precision) + post; } - private static string BinFormatter(string input, int precision, NumberFormatInfo nfi) + private static string BinaryFormatter(string input, int precision, NumberFormatInfo nfi) { - string output = ConvertDecimalToBin(input, nfi); + string output = ConvertDecimalToBinary(input, nfi); if (output[0] == '1') { @@ -1803,7 +1803,7 @@ private static string ConvertDecimalToHex(string input, bool upper, NumberFormat return output; } - private static string ConvertDecimalToBin(string input, NumberFormatInfo nfi) + private static string ConvertDecimalToBinary(string input, NumberFormatInfo nfi) { string output = string.Empty; BigInteger bi = BigInteger.Parse(input, nfi); diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index b15959fb7c81b6..95ed804cd464ec 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs @@ -309,82 +309,82 @@ private static void VerifyBinaryNumberStyles(NumberStyles ns, Random random) // SimpleNumbers - Small for (int i = 0; i < s_samples; i++) { - VerifyParseToString(GetBinDigitSequence(1, 10, random), ns, true); + VerifyParseToString(GetBinaryDigitSequence(1, 10, random), ns, true); } // SimpleNumbers - Large for (int i = 0; i < s_samples; i++) { - VerifyParseToString(GetBinDigitSequence(100, 1000, random), ns, true); + VerifyParseToString(GetBinaryDigitSequence(100, 1000, random), ns, true); } // Leading White for (int i = 0; i < s_samples; i++) { - VerifyParseToString("\u0009\u0009\u0009" + GetBinDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingWhite) != 0)); - VerifyParseToString("\u000A\u000A\u000A" + GetBinDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingWhite) != 0)); - VerifyParseToString("\u000B\u000B\u000B" + GetBinDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingWhite) != 0)); - VerifyParseToString("\u000C\u000C\u000C" + GetBinDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingWhite) != 0)); - VerifyParseToString("\u000D\u000D\u000D" + GetBinDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingWhite) != 0)); - VerifyParseToString("\u0020\u0020\u0020" + GetBinDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingWhite) != 0)); + 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(GetBinDigitSequence(1, 100, random) + "\u0009\u0009\u0009", ns, FailureNotExpectedForTrailingWhite(ns, false)); - VerifyParseToString(GetBinDigitSequence(1, 100, random) + "\u000A\u000A\u000A", ns, FailureNotExpectedForTrailingWhite(ns, false)); - VerifyParseToString(GetBinDigitSequence(1, 100, random) + "\u000B\u000B\u000B", ns, FailureNotExpectedForTrailingWhite(ns, false)); - VerifyParseToString(GetBinDigitSequence(1, 100, random) + "\u000C\u000C\u000C", ns, FailureNotExpectedForTrailingWhite(ns, false)); - VerifyParseToString(GetBinDigitSequence(1, 100, random) + "\u000D\u000D\u000D", ns, FailureNotExpectedForTrailingWhite(ns, false)); - VerifyParseToString(GetBinDigitSequence(1, 100, random) + "\u0020\u0020\u0020", ns, FailureNotExpectedForTrailingWhite(ns, true)); + 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 + GetBinDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingSign) != 0)); - VerifyParseToString(CultureInfo.CurrentCulture.NumberFormat.PositiveSign + GetBinDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowLeadingSign) != 0)); + 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(GetBinDigitSequence(1, 100, random) + CultureInfo.CurrentCulture.NumberFormat.NegativeSign, ns, ((ns & NumberStyles.AllowTrailingSign) != 0)); - VerifyParseToString(GetBinDigitSequence(1, 100, random) + CultureInfo.CurrentCulture.NumberFormat.PositiveSign, ns, ((ns & NumberStyles.AllowTrailingSign) != 0)); + 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("(" + GetBinDigitSequence(1, 100, random) + ")", ns, ((ns & NumberStyles.AllowParentheses) != 0)); + VerifyParseToString("(" + GetBinaryDigitSequence(1, 100, random) + ")", ns, ((ns & NumberStyles.AllowParentheses) != 0)); } // Decimal Point - end for (int i = 0; i < s_samples; i++) { - VerifyParseToString(GetBinDigitSequence(1, 100, random) + CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator, ns, ((ns & NumberStyles.AllowDecimalPoint) != 0)); + 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 = GetBinDigitSequence(1, 100, random); + 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 = GetBinDigitSequence(1, 100, random); - VerifyParseToString(digits + CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator + GetBinDigitSequence(20, 25, random), ns, false, digits); + 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 = GetBinDigitSequence(1, 100, random); - string exp = GetBinDigitSequence(1, 3, random); + 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 @@ -411,19 +411,19 @@ private static void VerifyBinaryNumberStyles(NumberStyles ns, Random random) // Currency Symbol for (int i = 0; i < s_samples; i++) { - VerifyParseToString(CultureInfo.CurrentCulture.NumberFormat.CurrencySymbol + GetBinDigitSequence(1, 100, random), ns, ((ns & NumberStyles.AllowCurrencySymbol) != 0)); + 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(GetBinDigitSequence(1, 15, random), ns, ((ns & NumberStyles.AllowBinarySpecifier) != 0)); + VerifyParseToString(GetBinaryDigitSequence(1, 15, random), ns, ((ns & NumberStyles.AllowBinarySpecifier) != 0)); } // Invalid Chars for (int i = 0; i < s_samples; i++) { - VerifyParseToString(GetBinDigitSequence(1, 100, random) + GetRandomInvalidChar(random) + GetBinDigitSequence(1, 10, random), ns, false); + VerifyParseToString(GetBinaryDigitSequence(1, 100, random) + GetRandomInvalidChar(random) + GetBinaryDigitSequence(1, 10, random), ns, false); } } @@ -773,7 +773,7 @@ private static string GetHexDigitSequence(int min, int max, Random random) return result; } - private static string GetBinDigitSequence(int min, int max, Random random) + private static string GetBinaryDigitSequence(int min, int max, Random random) { string result = string.Empty; int size = random.Next(min, max); @@ -834,7 +834,7 @@ private static string Fix(string input, bool isHex, bool isBinary, bool failureN } else if (isBinary) { - output = ConvertBinToDecimal(output); + output = ConvertBinaryToDecimal(output); } while (output.StartsWith("0") & (output.Length > 1)) { @@ -854,7 +854,7 @@ private static string Fix(string input, bool isHex, bool isBinary, bool failureN return output; } - private static string ConvertBinToDecimal(string input) + private static string ConvertBinaryToDecimal(string input) { const int HexBlockSize = 4;