@@ -285,7 +285,8 @@ internal static class BigNumber
285
285
| NumberStyles . AllowLeadingSign | NumberStyles . AllowTrailingSign
286
286
| NumberStyles . AllowParentheses | NumberStyles . AllowDecimalPoint
287
287
| NumberStyles . AllowThousands | NumberStyles . AllowExponent
288
- | NumberStyles . AllowCurrencySymbol | NumberStyles . AllowHexSpecifier ) ;
288
+ | NumberStyles . AllowCurrencySymbol | NumberStyles . AllowHexSpecifier
289
+ | NumberStyles . AllowBinarySpecifier ) ;
289
290
290
291
private static ReadOnlySpan < uint > UInt32PowersOfTen => [ 1 , 10 , 100 , 1000 , 10000 , 100000 , 1000000 , 10000000 , 100000000 , 1000000000 ] ;
291
292
@@ -371,10 +372,13 @@ internal static ParsingStatus TryParseBigInteger(ReadOnlySpan<char> value, Numbe
371
372
{
372
373
return HexNumberToBigInteger ( ref bigNumber , out result ) ;
373
374
}
374
- else
375
+
376
+ if ( ( style & NumberStyles . AllowBinarySpecifier ) != 0 )
375
377
{
376
- return NumberToBigInteger ( ref bigNumber , out result ) ;
378
+ return BinaryNumberToBigInteger ( ref bigNumber , out result ) ;
377
379
}
380
+
381
+ return NumberToBigInteger ( ref bigNumber , out result ) ;
378
382
}
379
383
380
384
internal static BigInteger ParseBigInteger ( string value , NumberStyles style , NumberFormatInfo info )
@@ -505,6 +509,112 @@ private static ParsingStatus HexNumberToBigInteger(ref BigNumberBuffer number, o
505
509
}
506
510
}
507
511
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
+
508
618
//
509
619
// This threshold is for choosing the algorithm to use based on the number of digits.
510
620
//
@@ -996,6 +1106,105 @@ internal static char ParseFormatSpecifier(ReadOnlySpan<char> format, out int dig
996
1106
}
997
1107
}
998
1108
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
+
999
1208
internal static string FormatBigInteger ( BigInteger value , string ? format , NumberFormatInfo info )
1000
1209
{
1001
1210
return FormatBigInteger ( targetSpan : false , value , format , format , info , default , out _ , out _ ) ! ;
@@ -1020,7 +1229,10 @@ internal static bool TryFormatBigInteger(BigInteger value, ReadOnlySpan<char> fo
1020
1229
{
1021
1230
return FormatBigIntegerToHex ( targetSpan , value , fmt , digits , info , destination , out charsWritten , out spanSuccess ) ;
1022
1231
}
1023
-
1232
+ if ( fmt == 'b' || fmt == 'B' )
1233
+ {
1234
+ return FormatBigIntegerToBinary ( targetSpan , value , digits , destination , out charsWritten , out spanSuccess ) ;
1235
+ }
1024
1236
1025
1237
if ( value . _bits == null )
1026
1238
{
0 commit comments