Skip to content

Commit

Permalink
Fix the unsigned right shift operator of BigInteger (#112879)
Browse files Browse the repository at this point in the history
* Add tests for the shift operator of BigInteger

* Fix the unsigned right shift operator of BigInteger

* avoid stackalloc

* external sign element
  • Loading branch information
kzrnm authored and tannergooding committed Feb 27, 2025
1 parent f7e3881 commit 68fefbc
Show file tree
Hide file tree
Showing 5 changed files with 324 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5264,13 +5264,32 @@ static bool INumberBase<BigInteger>.TryConvertToTruncating<TOther>(BigInteger va

BigInteger result;

bool negx = value._sign < 0;
uint smallBits = NumericsHelpers.Abs(value._sign);
scoped ReadOnlySpan<uint> bits = value._bits;
if (bits.IsEmpty)
{
bits = new ReadOnlySpan<uint>(in smallBits);
}

int xl = bits.Length;
if (negx && (bits[^1] >= kuMaskHighBit) && ((bits[^1] != kuMaskHighBit) || bits.IndexOfAnyExcept(0u) != (bits.Length - 1)))
{
// For a shift of N x 32 bit,
// We check for a special case where its sign bit could be outside the uint array after 2's complement conversion.
// For example given [0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF], its 2's complement is [0x01, 0x00, 0x00]
// After a 32 bit right shift, it becomes [0x00, 0x00] which is [0x00, 0x00] when converted back.
// The expected result is [0x00, 0x00, 0xFFFFFFFF] (2's complement) or [0x00, 0x00, 0x01] when converted back
// If the 2's component's last element is a 0, we will track the sign externally
++xl;
}

uint[]? xdFromPool = null;
int xl = value._bits?.Length ?? 1;
Span<uint> xd = (xl <= BigIntegerCalculator.StackAllocThreshold
? stackalloc uint[BigIntegerCalculator.StackAllocThreshold]
: xdFromPool = ArrayPool<uint>.Shared.Rent(xl)).Slice(0, xl);

bool negx = value.GetPartsForBitManipulation(xd);
xd[^1] = 0;
bits.CopyTo(xd);

if (negx)
{
Expand Down
59 changes: 59 additions & 0 deletions src/libraries/System.Runtime.Numerics/tests/BigInteger/MyBigInt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ public static BigInteger DoBinaryOperatorMine(BigInteger num1, BigInteger num2,
return new BigInteger(Max(bytes1, bytes2).ToArray());
case "b>>":
return new BigInteger(ShiftLeft(bytes1, Negate(bytes2)).ToArray());
case "b>>>":
return new BigInteger(ShiftRightUnsigned(bytes1, bytes2).ToArray());
case "b<<":
return new BigInteger(ShiftLeft(bytes1, bytes2).ToArray());
case "bRotateLeft":
Expand Down Expand Up @@ -641,11 +643,68 @@ public static List<byte> Not(List<byte> bytes)
return bnew;
}

public static List<byte> ShiftRightUnsigned(List<byte> bytes1, List<byte> bytes2)
{
int byteShift = (int)new BigInteger(Divide(Copy(bytes2), new List<byte>(new byte[] { 8 })).ToArray());
sbyte bitShift = (sbyte)new BigInteger(Remainder(Copy(bytes2), new List<byte>(new byte[] { 8 })).ToArray());

if (byteShift == 0 && bitShift == 0)
return bytes1;

if (byteShift < 0 || bitShift < 0)
return ShiftLeft(bytes1, Negate(bytes2));

Trim(bytes1);

byte fill = (bytes1[bytes1.Count - 1] & 0x80) != 0 ? byte.MaxValue : (byte)0;

if (fill == byte.MaxValue)
{
while (bytes1.Count % 4 != 0)
{
bytes1.Add(fill);
}
}

if (byteShift >= bytes1.Count)
{
return [fill];
}

if (fill == byte.MaxValue)
{
bytes1.Add(0);
}

for (int i = 0; i < bitShift; i++)
{
bytes1 = ShiftRight(bytes1);
}

List<byte> temp = new List<byte>();
for (int i = byteShift; i < bytes1.Count; i++)
{
temp.Add(bytes1[i]);
}
bytes1 = temp;

if (fill == byte.MaxValue && bytes1.Count % 4 == 1)
{
bytes1.RemoveAt(bytes1.Count - 1);
}

Trim(bytes1);

return bytes1;
}

public static List<byte> ShiftLeft(List<byte> bytes1, List<byte> bytes2)
{
int byteShift = (int)new BigInteger(Divide(Copy(bytes2), new List<byte>(new byte[] { 8 })).ToArray());
sbyte bitShift = (sbyte)new BigInteger(Remainder(bytes2, new List<byte>(new byte[] { 8 })).ToArray());

Trim(bytes1);

for (int i = 0; i < Math.Abs(bitShift); i++)
{
if (bitShift < 0)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using Xunit;

namespace System.Numerics.Tests
Expand Down Expand Up @@ -151,6 +152,56 @@ public static void RunLeftShiftTests()
}
}

[Fact]
public void RunSmallTests()
{
foreach (int i in new int[] {
0,
1,
16,
31,
32,
33,
63,
64,
65,
100,
127,
128,
})
{
foreach (int shift in new int[] {
0,
-1, 1,
-16, 16,
-31, 31,
-32, 32,
-33, 33,
-63, 63,
-64, 64,
-65, 65,
-100, 100,
-127, 127,
-128, 128,
})
{
var num = Int128.One << i;
for (int k = -1; k <= 1; k++)
{
foreach (int sign in new int[] { -1, +1 })
{
Int128 value128 = sign * (num + k);

byte[] tempByteArray1 = GetRandomSmallByteArray(value128);
byte[] tempByteArray2 = GetRandomSmallByteArray(shift);

VerifyLeftShiftString(Print(tempByteArray2) + Print(tempByteArray1) + "b<<");
}
}
}
}
}

private static void VerifyLeftShiftString(string opstring)
{
StackCalc sc = new StackCalc(opstring);
Expand All @@ -160,6 +211,19 @@ private static void VerifyLeftShiftString(string opstring)
}
}

private static byte[] GetRandomSmallByteArray(Int128 num)
{
byte[] value = new byte[16];

for (int i = 0; i < value.Length; i++)
{
value[i] = (byte)num;
num >>= 8;
}

return value;
}

private static byte[] GetRandomByteArray(Random random)
{
return GetRandomByteArray(random, random.Next(0, 1024));
Expand Down
Loading

0 comments on commit 68fefbc

Please sign in to comment.