Skip to content

Commit 3e38bd8

Browse files
authored
Fix the unsigned right shift operator of BigInteger (#112879)
* Add tests for the shift operator of BigInteger * Fix the unsigned right shift operator of BigInteger * avoid stackalloc * external sign element
1 parent 2959612 commit 3e38bd8

File tree

5 files changed

+324
-91
lines changed

5 files changed

+324
-91
lines changed

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

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5271,13 +5271,32 @@ static bool INumberBase<BigInteger>.TryConvertToTruncating<TOther>(BigInteger va
52715271

52725272
BigInteger result;
52735273

5274+
bool negx = value._sign < 0;
5275+
uint smallBits = NumericsHelpers.Abs(value._sign);
5276+
scoped ReadOnlySpan<uint> bits = value._bits;
5277+
if (bits.IsEmpty)
5278+
{
5279+
bits = new ReadOnlySpan<uint>(in smallBits);
5280+
}
5281+
5282+
int xl = bits.Length;
5283+
if (negx && (bits[^1] >= kuMaskHighBit) && ((bits[^1] != kuMaskHighBit) || bits.IndexOfAnyExcept(0u) != (bits.Length - 1)))
5284+
{
5285+
// For a shift of N x 32 bit,
5286+
// We check for a special case where its sign bit could be outside the uint array after 2's complement conversion.
5287+
// For example given [0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF], its 2's complement is [0x01, 0x00, 0x00]
5288+
// After a 32 bit right shift, it becomes [0x00, 0x00] which is [0x00, 0x00] when converted back.
5289+
// The expected result is [0x00, 0x00, 0xFFFFFFFF] (2's complement) or [0x00, 0x00, 0x01] when converted back
5290+
// If the 2's component's last element is a 0, we will track the sign externally
5291+
++xl;
5292+
}
5293+
52745294
uint[]? xdFromPool = null;
5275-
int xl = value._bits?.Length ?? 1;
52765295
Span<uint> xd = (xl <= BigIntegerCalculator.StackAllocThreshold
52775296
? stackalloc uint[BigIntegerCalculator.StackAllocThreshold]
52785297
: xdFromPool = ArrayPool<uint>.Shared.Rent(xl)).Slice(0, xl);
5279-
5280-
bool negx = value.GetPartsForBitManipulation(xd);
5298+
xd[^1] = 0;
5299+
bits.CopyTo(xd);
52815300

52825301
if (negx)
52835302
{

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

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ public static BigInteger DoBinaryOperatorMine(BigInteger num1, BigInteger num2,
108108
return new BigInteger(Max(bytes1, bytes2).ToArray());
109109
case "b>>":
110110
return new BigInteger(ShiftLeft(bytes1, Negate(bytes2)).ToArray());
111+
case "b>>>":
112+
return new BigInteger(ShiftRightUnsigned(bytes1, bytes2).ToArray());
111113
case "b<<":
112114
return new BigInteger(ShiftLeft(bytes1, bytes2).ToArray());
113115
case "bRotateLeft":
@@ -641,11 +643,68 @@ public static List<byte> Not(List<byte> bytes)
641643
return bnew;
642644
}
643645

646+
public static List<byte> ShiftRightUnsigned(List<byte> bytes1, List<byte> bytes2)
647+
{
648+
int byteShift = (int)new BigInteger(Divide(Copy(bytes2), new List<byte>(new byte[] { 8 })).ToArray());
649+
sbyte bitShift = (sbyte)new BigInteger(Remainder(Copy(bytes2), new List<byte>(new byte[] { 8 })).ToArray());
650+
651+
if (byteShift == 0 && bitShift == 0)
652+
return bytes1;
653+
654+
if (byteShift < 0 || bitShift < 0)
655+
return ShiftLeft(bytes1, Negate(bytes2));
656+
657+
Trim(bytes1);
658+
659+
byte fill = (bytes1[bytes1.Count - 1] & 0x80) != 0 ? byte.MaxValue : (byte)0;
660+
661+
if (fill == byte.MaxValue)
662+
{
663+
while (bytes1.Count % 4 != 0)
664+
{
665+
bytes1.Add(fill);
666+
}
667+
}
668+
669+
if (byteShift >= bytes1.Count)
670+
{
671+
return [fill];
672+
}
673+
674+
if (fill == byte.MaxValue)
675+
{
676+
bytes1.Add(0);
677+
}
678+
679+
for (int i = 0; i < bitShift; i++)
680+
{
681+
bytes1 = ShiftRight(bytes1);
682+
}
683+
684+
List<byte> temp = new List<byte>();
685+
for (int i = byteShift; i < bytes1.Count; i++)
686+
{
687+
temp.Add(bytes1[i]);
688+
}
689+
bytes1 = temp;
690+
691+
if (fill == byte.MaxValue && bytes1.Count % 4 == 1)
692+
{
693+
bytes1.RemoveAt(bytes1.Count - 1);
694+
}
695+
696+
Trim(bytes1);
697+
698+
return bytes1;
699+
}
700+
644701
public static List<byte> ShiftLeft(List<byte> bytes1, List<byte> bytes2)
645702
{
646703
int byteShift = (int)new BigInteger(Divide(Copy(bytes2), new List<byte>(new byte[] { 8 })).ToArray());
647704
sbyte bitShift = (sbyte)new BigInteger(Remainder(bytes2, new List<byte>(new byte[] { 8 })).ToArray());
648705

706+
Trim(bytes1);
707+
649708
for (int i = 0; i < Math.Abs(bitShift); i++)
650709
{
651710
if (bitShift < 0)

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Collections.Generic;
45
using Xunit;
56

67
namespace System.Numerics.Tests
@@ -151,6 +152,56 @@ public static void RunLeftShiftTests()
151152
}
152153
}
153154

155+
[Fact]
156+
public void RunSmallTests()
157+
{
158+
foreach (int i in new int[] {
159+
0,
160+
1,
161+
16,
162+
31,
163+
32,
164+
33,
165+
63,
166+
64,
167+
65,
168+
100,
169+
127,
170+
128,
171+
})
172+
{
173+
foreach (int shift in new int[] {
174+
0,
175+
-1, 1,
176+
-16, 16,
177+
-31, 31,
178+
-32, 32,
179+
-33, 33,
180+
-63, 63,
181+
-64, 64,
182+
-65, 65,
183+
-100, 100,
184+
-127, 127,
185+
-128, 128,
186+
})
187+
{
188+
var num = Int128.One << i;
189+
for (int k = -1; k <= 1; k++)
190+
{
191+
foreach (int sign in new int[] { -1, +1 })
192+
{
193+
Int128 value128 = sign * (num + k);
194+
195+
byte[] tempByteArray1 = GetRandomSmallByteArray(value128);
196+
byte[] tempByteArray2 = GetRandomSmallByteArray(shift);
197+
198+
VerifyLeftShiftString(Print(tempByteArray2) + Print(tempByteArray1) + "b<<");
199+
}
200+
}
201+
}
202+
}
203+
}
204+
154205
private static void VerifyLeftShiftString(string opstring)
155206
{
156207
StackCalc sc = new StackCalc(opstring);
@@ -160,6 +211,19 @@ private static void VerifyLeftShiftString(string opstring)
160211
}
161212
}
162213

214+
private static byte[] GetRandomSmallByteArray(Int128 num)
215+
{
216+
byte[] value = new byte[16];
217+
218+
for (int i = 0; i < value.Length; i++)
219+
{
220+
value[i] = (byte)num;
221+
num >>= 8;
222+
}
223+
224+
return value;
225+
}
226+
163227
private static byte[] GetRandomByteArray(Random random)
164228
{
165229
return GetRandomByteArray(random, random.Next(0, 1024));

0 commit comments

Comments
 (0)