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

Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
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
  • Loading branch information
kzrnm authored and tannergooding committed Feb 27, 2025
commit 660fa1c8a0f72567a0e6e530c7dbf9eb038f0c52
Original file line number Diff line number Diff line change
Expand Up @@ -5284,13 +5284,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
Loading