Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 71d6476

Browse files
Format/Parse binary from/to BigInteger (#85392)
* Initial draft commit: add FormatBigIntegerToBin(). * Fix comment: use '?:' to assign ValueStringBuilder variable to make it 'unsafe to return' so that can assign stackalloced. * Refine FormatBigIntegerToBin(); and consider chars overflow scenario. * Update Format code for final binary format definition. * Refine FormatBigIntegerToBin(). * consider case where output is span * Turn to use try..finally to return array pool. * Initial add method BinNumberToBigInteger(). * Update FormatProvider.Number.cs to support AllowBinarySpecifier. * Use BinNumberToBigInteger(). * Add tests of Format. * Add tests of Parse(). * Improve Format(): use ValueStringBuilder just as wrapper for destination span, so to save extra buffer allocation and copy in ValueStringBuilder. * Fix comment: use ch == '0' || ch == '1' * Fix comment: refactor ParseNumber() to extract common abstract operations for previous Hex and new Binary. * Fix comment: refine naming; make BinNumberToBigInteger() general pattern similar as HexNumberToBigInteger's * Fix comment: use internal 'kcbitUint'. * Fix comment: rename 'Bin' method names to 'Binary' ones; remove unnecessary Slice().
1 parent 529f60e commit 71d6476

File tree

5 files changed

+521
-16
lines changed

5 files changed

+521
-16
lines changed

src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,54 @@ private static unsafe bool AllowHyphenDuringParsing(NumberFormatInfo info)
356356
return ret;
357357
}
358358

359+
private interface IDigitParser
360+
{
361+
static abstract bool IsValidChar(char c);
362+
static abstract bool IsHexOrBinaryParser();
363+
}
364+
365+
private readonly struct IntegerDigitParser : IDigitParser
366+
{
367+
public static bool IsValidChar(char c) => char.IsAsciiDigit(c);
368+
369+
public static bool IsHexOrBinaryParser() => false;
370+
}
371+
372+
private readonly struct HexDigitParser : IDigitParser
373+
{
374+
public static bool IsValidChar(char c) => HexConverter.IsHexChar((int)c);
375+
376+
public static bool IsHexOrBinaryParser() => true;
377+
}
378+
379+
private readonly struct BinaryDigitParser : IDigitParser
380+
{
381+
public static bool IsValidChar(char c)
382+
{
383+
return (uint)c - '0' <= 1;
384+
}
385+
386+
public static bool IsHexOrBinaryParser() => true;
387+
}
388+
389+
359390
private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles options, scoped ref NumberBuffer number, StringBuilder? sb, NumberFormatInfo numfmt, bool parseDecimal)
391+
{
392+
if ((options & NumberStyles.AllowHexSpecifier) != 0)
393+
{
394+
return ParseNumberStyle<HexDigitParser>(ref str, strEnd, options, ref number, sb, numfmt, parseDecimal);
395+
}
396+
397+
if ((options & NumberStyles.AllowBinarySpecifier) != 0)
398+
{
399+
return ParseNumberStyle<BinaryDigitParser>(ref str, strEnd, options, ref number, sb, numfmt, parseDecimal);
400+
}
401+
402+
return ParseNumberStyle<IntegerDigitParser>(ref str, strEnd, options, ref number, sb, numfmt, parseDecimal);
403+
}
404+
405+
private static unsafe bool ParseNumberStyle<TDigitParser>(ref char* str, char* strEnd, NumberStyles options, scoped ref NumberBuffer number, StringBuilder? sb, NumberFormatInfo numfmt, bool parseDecimal)
406+
where TDigitParser : struct, IDigitParser
360407
{
361408
Debug.Assert(str != null);
362409
Debug.Assert(strEnd != null);
@@ -440,11 +487,11 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles
440487
int digEnd = 0;
441488
while (true)
442489
{
443-
if (char.IsAsciiDigit(ch) || (((options & NumberStyles.AllowHexSpecifier) != 0) && char.IsBetween((char)(ch | 0x20), 'a', 'f')))
490+
if (TDigitParser.IsValidChar(ch))
444491
{
445492
state |= StateDigits;
446493

447-
if (ch != '0' || (state & StateNonZero) != 0 || (bigNumber && ((options & NumberStyles.AllowHexSpecifier) != 0)))
494+
if (ch != '0' || (state & StateNonZero) != 0 || (bigNumber && TDigitParser.IsHexOrBinaryParser()))
448495
{
449496
if (digCount < maxParseDigits)
450497
{

src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ public readonly struct BigInteger
2222
IBinaryInteger<BigInteger>,
2323
ISignedNumber<BigInteger>
2424
{
25-
private const uint kuMaskHighBit = unchecked((uint)int.MinValue);
26-
private const int kcbitUint = 32;
27-
private const int kcbitUlong = 64;
28-
private const int DecimalScaleFactorMask = 0x00FF0000;
25+
internal const uint kuMaskHighBit = unchecked((uint)int.MinValue);
26+
internal const int kcbitUint = 32;
27+
internal const int kcbitUlong = 64;
28+
internal const int DecimalScaleFactorMask = 0x00FF0000;
2929

3030
// For values int.MinValue < n <= int.MaxValue, the value is stored in sign
3131
// and _bits is null. For all other values, sign is +1 or -1 and the bits are in _bits

src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs

Lines changed: 216 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,8 @@ internal static class BigNumber
285285
| NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingSign
286286
| NumberStyles.AllowParentheses | NumberStyles.AllowDecimalPoint
287287
| NumberStyles.AllowThousands | NumberStyles.AllowExponent
288-
| NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier);
288+
| NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier
289+
| NumberStyles.AllowBinarySpecifier);
289290

290291
private static ReadOnlySpan<uint> UInt32PowersOfTen => [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000];
291292

@@ -371,10 +372,13 @@ internal static ParsingStatus TryParseBigInteger(ReadOnlySpan<char> value, Numbe
371372
{
372373
return HexNumberToBigInteger(ref bigNumber, out result);
373374
}
374-
else
375+
376+
if ((style & NumberStyles.AllowBinarySpecifier) != 0)
375377
{
376-
return NumberToBigInteger(ref bigNumber, out result);
378+
return BinaryNumberToBigInteger(ref bigNumber, out result);
377379
}
380+
381+
return NumberToBigInteger(ref bigNumber, out result);
378382
}
379383

380384
internal static BigInteger ParseBigInteger(string value, NumberStyles style, NumberFormatInfo info)
@@ -505,6 +509,112 @@ private static ParsingStatus HexNumberToBigInteger(ref BigNumberBuffer number, o
505509
}
506510
}
507511

512+
private static ParsingStatus BinaryNumberToBigInteger(ref BigNumberBuffer number, out BigInteger result)
513+
{
514+
if (number.digits is null || number.digits.Length == 0)
515+
{
516+
result = default;
517+
return ParsingStatus.Failed;
518+
}
519+
520+
int totalDigitCount = number.digits.Length - 1; // Ignore trailing '\0'
521+
int partialDigitCount;
522+
523+
(int blockCount, int remainder) = int.DivRem(totalDigitCount, BigInteger.kcbitUint);
524+
if (remainder == 0)
525+
{
526+
partialDigitCount = 0;
527+
}
528+
else
529+
{
530+
blockCount++;
531+
partialDigitCount = BigInteger.kcbitUint - remainder;
532+
}
533+
534+
Debug.Assert(number.digits[0] is '0' or '1');
535+
bool isNegative = number.digits[0] == '1';
536+
uint currentBlock = isNegative ? 0xFF_FF_FF_FFu : 0x0;
537+
538+
uint[]? arrayFromPool = null;
539+
Span<uint> buffer = ((uint)blockCount <= BigIntegerCalculator.StackAllocThreshold
540+
? stackalloc uint[BigIntegerCalculator.StackAllocThreshold]
541+
: arrayFromPool = ArrayPool<uint>.Shared.Rent(blockCount)).Slice(0, blockCount);
542+
543+
int bufferPos = blockCount - 1;
544+
545+
try
546+
{
547+
foreach (ReadOnlyMemory<char> digitsChunkMem in number.digits.GetChunks())
548+
{
549+
ReadOnlySpan<char> chunkDigits = digitsChunkMem.Span;
550+
for (int i = 0; i < chunkDigits.Length; i++)
551+
{
552+
char digitChar = chunkDigits[i];
553+
if (digitChar == '\0')
554+
{
555+
break;
556+
}
557+
558+
Debug.Assert(digitChar is '0' or '1');
559+
currentBlock = (currentBlock << 1) | (uint)(digitChar - '0');
560+
partialDigitCount++;
561+
562+
if (partialDigitCount == BigInteger.kcbitUint)
563+
{
564+
buffer[bufferPos--] = currentBlock;
565+
partialDigitCount = 0;
566+
567+
// we do not need to reset currentBlock now, because it should always set all its bits by left shift in subsequent iterations
568+
}
569+
}
570+
}
571+
572+
Debug.Assert(partialDigitCount == 0 && bufferPos == -1);
573+
574+
buffer = buffer.TrimEnd(0u);
575+
576+
int sign;
577+
uint[]? bits;
578+
579+
if (buffer.IsEmpty)
580+
{
581+
sign = 0;
582+
bits = null;
583+
}
584+
else if (buffer.Length == 1)
585+
{
586+
sign = (int)buffer[0];
587+
bits = null;
588+
589+
if ((!isNegative && sign < 0) || sign == int.MinValue)
590+
{
591+
bits = new[] { (uint)sign };
592+
sign = isNegative ? -1 : 1;
593+
}
594+
}
595+
else
596+
{
597+
sign = isNegative ? -1 : 1;
598+
bits = buffer.ToArray();
599+
600+
if (isNegative)
601+
{
602+
NumericsHelpers.DangerousMakeTwosComplement(bits);
603+
}
604+
}
605+
606+
result = new BigInteger(sign, bits);
607+
return ParsingStatus.OK;
608+
}
609+
finally
610+
{
611+
if (arrayFromPool is not null)
612+
{
613+
ArrayPool<uint>.Shared.Return(arrayFromPool);
614+
}
615+
}
616+
}
617+
508618
//
509619
// This threshold is for choosing the algorithm to use based on the number of digits.
510620
//
@@ -996,6 +1106,105 @@ internal static char ParseFormatSpecifier(ReadOnlySpan<char> format, out int dig
9961106
}
9971107
}
9981108

1109+
private static string? FormatBigIntegerToBinary(bool targetSpan, BigInteger value, int digits, Span<char> destination, out int charsWritten, out bool spanSuccess)
1110+
{
1111+
// Get the bytes that make up the BigInteger.
1112+
byte[]? arrayToReturnToPool = null;
1113+
Span<byte> bytes = stackalloc byte[64]; // arbitrary threshold
1114+
if (!value.TryWriteOrCountBytes(bytes, out int bytesWrittenOrNeeded))
1115+
{
1116+
bytes = arrayToReturnToPool = ArrayPool<byte>.Shared.Rent(bytesWrittenOrNeeded);
1117+
bool success = value.TryWriteBytes(bytes, out _);
1118+
Debug.Assert(success);
1119+
}
1120+
bytes = bytes.Slice(0, bytesWrittenOrNeeded);
1121+
1122+
Debug.Assert(!bytes.IsEmpty);
1123+
1124+
byte highByte = bytes[^1];
1125+
1126+
int charsInHighByte = 9 - byte.LeadingZeroCount(value._sign >= 0 ? highByte : (byte)~highByte);
1127+
long tmpCharCount = charsInHighByte + ((long)(bytes.Length - 1) << 3);
1128+
1129+
if (tmpCharCount > Array.MaxLength)
1130+
{
1131+
Debug.Assert(arrayToReturnToPool is not null);
1132+
ArrayPool<byte>.Shared.Return(arrayToReturnToPool);
1133+
1134+
throw new FormatException(SR.Format_TooLarge);
1135+
}
1136+
1137+
int charsForBits = (int)tmpCharCount;
1138+
1139+
Debug.Assert(digits < Array.MaxLength);
1140+
int charsIncludeDigits = Math.Max(digits, charsForBits);
1141+
1142+
try
1143+
{
1144+
scoped ValueStringBuilder sb;
1145+
if (targetSpan)
1146+
{
1147+
if (charsIncludeDigits > destination.Length)
1148+
{
1149+
charsWritten = 0;
1150+
spanSuccess = false;
1151+
return null;
1152+
}
1153+
1154+
// 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);
1155+
// meanwhile there is no need to copy to destination again after format data for targetSpan flow.
1156+
sb = new ValueStringBuilder(destination);
1157+
}
1158+
else
1159+
{
1160+
// each byte is typically eight chars
1161+
sb = charsIncludeDigits > 512
1162+
? new ValueStringBuilder(charsIncludeDigits)
1163+
: new ValueStringBuilder(stackalloc char[512]);
1164+
}
1165+
1166+
if (digits > charsForBits)
1167+
{
1168+
sb.Append(value._sign >= 0 ? '0' : '1', digits - charsForBits);
1169+
}
1170+
1171+
AppendByte(ref sb, highByte, charsInHighByte - 1);
1172+
1173+
for (int i = bytes.Length - 2; i >= 0; i--)
1174+
{
1175+
AppendByte(ref sb, bytes[i]);
1176+
}
1177+
1178+
Debug.Assert(sb.Length == charsIncludeDigits);
1179+
1180+
if (targetSpan)
1181+
{
1182+
charsWritten = charsIncludeDigits;
1183+
spanSuccess = true;
1184+
return null;
1185+
}
1186+
1187+
charsWritten = 0;
1188+
spanSuccess = false;
1189+
return sb.ToString();
1190+
}
1191+
finally
1192+
{
1193+
if (arrayToReturnToPool is not null)
1194+
{
1195+
ArrayPool<byte>.Shared.Return(arrayToReturnToPool);
1196+
}
1197+
}
1198+
1199+
static void AppendByte(ref ValueStringBuilder sb, byte b, int startHighBit = 7)
1200+
{
1201+
for (int i = startHighBit; i >= 0; i--)
1202+
{
1203+
sb.Append((char)('0' + ((b >> i) & 0x1)));
1204+
}
1205+
}
1206+
}
1207+
9991208
internal static string FormatBigInteger(BigInteger value, string? format, NumberFormatInfo info)
10001209
{
10011210
return FormatBigInteger(targetSpan: false, value, format, format, info, default, out _, out _)!;
@@ -1020,7 +1229,10 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan<char> fo
10201229
{
10211230
return FormatBigIntegerToHex(targetSpan, value, fmt, digits, info, destination, out charsWritten, out spanSuccess);
10221231
}
1023-
1232+
if (fmt == 'b' || fmt == 'B')
1233+
{
1234+
return FormatBigIntegerToBinary(targetSpan, value, digits, destination, out charsWritten, out spanSuccess);
1235+
}
10241236

10251237
if (value._bits == null)
10261238
{

0 commit comments

Comments
 (0)