Skip to content

[release/9.0] Fix BigInteger.Rotate{Left,Right} for backport #112991

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1694,7 +1694,7 @@ private static BigInteger Add(ReadOnlySpan<uint> leftBits, int leftSign, ReadOnl
}

if (bitsFromPool != null)
ArrayPool<uint>.Shared.Return(bitsFromPool);
ArrayPool<uint>.Shared.Return(bitsFromPool);

return result;
}
Expand Down Expand Up @@ -2629,7 +2629,7 @@ public static implicit operator BigInteger(nuint value)

if (zdFromPool != null)
ArrayPool<uint>.Shared.Return(zdFromPool);
exit:
exit:
if (xdFromPool != null)
ArrayPool<uint>.Shared.Return(xdFromPool);

Expand Down Expand Up @@ -3232,7 +3232,27 @@ public static BigInteger PopCount(BigInteger value)
public static BigInteger RotateLeft(BigInteger value, int rotateAmount)
{
value.AssertValid();
int byteCount = (value._bits is null) ? sizeof(int) : (value._bits.Length * 4);

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)))
{
// 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;
}

int byteCount = xl * 4;

// Normalize the rotate amount to drop full rotations
rotateAmount = (int)(rotateAmount % (byteCount * 8L));
Expand All @@ -3249,14 +3269,13 @@ public static BigInteger RotateLeft(BigInteger value, int rotateAmount)
(int digitShift, int smallShift) = Math.DivRem(rotateAmount, kcbitUint);

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);
xd = xd.Slice(0, xl);
xd[^1] = 0;

bool negx = value.GetPartsForBitManipulation(xd);
bits.CopyTo(xd);

int zl = xl;
uint[]? zdFromPool = null;
Expand Down Expand Up @@ -3367,7 +3386,28 @@ public static BigInteger RotateLeft(BigInteger value, int rotateAmount)
public static BigInteger RotateRight(BigInteger value, int rotateAmount)
{
value.AssertValid();
int byteCount = (value._bits is null) ? sizeof(int) : (value._bits.Length * 4);


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)))
{
// 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;
}

int byteCount = xl * 4;

// Normalize the rotate amount to drop full rotations
rotateAmount = (int)(rotateAmount % (byteCount * 8L));
Expand All @@ -3384,14 +3424,13 @@ public static BigInteger RotateRight(BigInteger value, int rotateAmount)
(int digitShift, int smallShift) = Math.DivRem(rotateAmount, kcbitUint);

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);
xd = xd.Slice(0, xl);
xd[^1] = 0;

bool negx = value.GetPartsForBitManipulation(xd);
bits.CopyTo(xd);

int zl = xl;
uint[]? zdFromPool = null;
Expand Down Expand Up @@ -3438,19 +3477,12 @@ public static BigInteger RotateRight(BigInteger value, int rotateAmount)
{
int carryShift = kcbitUint - smallShift;

int dstIndex = 0;
int srcIndex = digitShift;
int dstIndex = xd.Length - 1;
int srcIndex = digitShift == 0
? xd.Length - 1
: digitShift - 1;

uint carry = 0;

if (digitShift == 0)
{
carry = xd[^1] << carryShift;
}
else
{
carry = xd[srcIndex - 1] << carryShift;
}
uint carry = xd[digitShift] << carryShift;

do
{
Expand All @@ -3459,22 +3491,22 @@ public static BigInteger RotateRight(BigInteger value, int rotateAmount)
zd[dstIndex] = (part >> smallShift) | carry;
carry = part << carryShift;

dstIndex++;
srcIndex++;
dstIndex--;
srcIndex--;
}
while (srcIndex < xd.Length);
while ((uint)srcIndex < (uint)xd.Length); // is equivalent to (srcIndex >= 0 && srcIndex < xd.Length)

srcIndex = 0;
srcIndex = xd.Length - 1;

while (dstIndex < zd.Length)
while ((uint)dstIndex < (uint)zd.Length) // is equivalent to (dstIndex >= 0 && dstIndex < zd.Length)
{
uint part = xd[srcIndex];

zd[dstIndex] = (part >> smallShift) | carry;
carry = part << carryShift;

dstIndex++;
srcIndex++;
dstIndex--;
srcIndex--;
}
}

Expand Down Expand Up @@ -5232,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
162 changes: 162 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,8 +108,14 @@ 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":
return new BigInteger(RotateLeft(bytes1, bytes2).ToArray());
case "bRotateRight":
return new BigInteger(RotateLeft(bytes1, Negate(bytes2)).ToArray());
case "b^":
return new BigInteger(Xor(bytes1, bytes2).ToArray());
case "b|":
Expand Down Expand Up @@ -637,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 Expand Up @@ -774,6 +837,105 @@ public static List<byte> ShiftRight(List<byte> bytes)
return bresult;
}

public static List<byte> RotateRight(List<byte> bytes)
{
List<byte> bresult = new List<byte>();

byte bottom = (byte)(bytes[0] & 0x01);

for (int i = 0; i < bytes.Count; i++)
{
byte newbyte = bytes[i];

newbyte = (byte)(newbyte / 2);
if ((i != (bytes.Count - 1)) && ((bytes[i + 1] & 0x01) == 1))
{
newbyte += 128;
}
if ((i == (bytes.Count - 1)) && (bottom != 0))
{
newbyte += 128;
}
bresult.Add(newbyte);
}

return bresult;
}

public static List<byte> RotateLeft(List<byte> bytes)
{
List<byte> bresult = new List<byte>();

bool prevHead = (bytes[bytes.Count - 1] & 0x80) != 0;

for (int i = 0; i < bytes.Count; i++)
{
byte newbyte = bytes[i];

newbyte = (byte)(newbyte * 2);
if (prevHead)
{
newbyte += 1;
}

bresult.Add(newbyte);

prevHead = (bytes[i] & 0x80) != 0;
}

return bresult;
}


public static List<byte> RotateLeft(List<byte> bytes1, List<byte> bytes2)
{
List<byte> bytes1Copy = Copy(bytes1);
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);

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

if (fill == 0 && bytes1.Count > 1 && bytes1[bytes1.Count - 1] == 0)
bytes1.RemoveAt(bytes1.Count - 1);

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

byteShift %= bytes1.Count;
if (byteShift == 0 && bitShift == 0)
return bytes1Copy;

for (int i = 0; i < Math.Abs(bitShift); i++)
{
if (bitShift < 0)
{
bytes1 = RotateRight(bytes1);
}
else
{
bytes1 = RotateLeft(bytes1);
}
}

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

if (fill == 0)
bytes1.Add(0);

Trim(bytes1);

return bytes1;
}

public static List<byte> SetLength(List<byte> bytes, int size)
{
List<byte> bresult = new List<byte>();
Expand Down
Loading
Loading