Skip to content

[Style]: Move BigInteger extensions from Utility to BigIntegerExtensions #3916

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 3 commits into from
May 6, 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
71 changes: 70 additions & 1 deletion src/Neo.Extensions/BigIntegerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,77 @@ public static BigInteger Sum(this IEnumerable<BigInteger> source)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte[] ToByteArrayStandard(this BigInteger value)
{
if (value.IsZero) return Array.Empty<byte>();
if (value.IsZero) return [];
return value.ToByteArray();
}

public static BigInteger Sqrt(this BigInteger value)
{
if (value < 0) throw new InvalidOperationException($"value {value} can not be negative for '{nameof(Sqrt)}'.");
if (value.IsZero) return BigInteger.Zero;
if (value < 4) return BigInteger.One;

var z = value;
var x = BigInteger.One << (int)(((value - 1).GetBitLength() + 1) >> 1);
while (x < z)
{
z = x;
x = (value / x + x) / 2;
}

return z;
}

/// <summary>
/// Gets the number of bits required for shortest two's complement representation of the current instance without the sign bit.
/// Note: This method is imprecise and might not work as expected with integers larger than 256 bits if less than .NET5.
/// </summary>
/// <returns>The minimum non-negative number of bits in two's complement notation without the sign bit.</returns>
/// <remarks>
/// This method returns 0 if the value of current object is equal to
/// <see cref="BigInteger.Zero"/> or <see cref="BigInteger.MinusOne"/>.
/// For positive integers the return value is equal to the ordinary binary representation string length.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long GetBitLength(this BigInteger value)
{
#if NET5_0_OR_GREATER
return value.GetBitLength();
#else
return BitLength(value);
#endif
}

/// <summary>
/// GetBitLength for earlier than .NET5.0
/// </summary>
internal static long BitLength(this BigInteger value)
{
if (value == 0 || value == BigInteger.MinusOne) return 0;

var b = value.ToByteArray();
if (b.Length == 1 || (b.Length == 2 && b[1] == 0))
{
return BitCount(value.Sign > 0 ? b[0] : (byte)(255 - b[0]));
}
return (b.Length - 1) * 8 + BitCount(value.Sign > 0 ? b[^1] : (byte)(255 - b[^1]));
}

private static int BitCount(int w)
{
return w < 1 << 15 ? (w < 1 << 7
? (w < 1 << 3 ? (w < 1 << 1
? (w < 1 << 0 ? (w < 0 ? 32 : 0) : 1)
: (w < 1 << 2 ? 2 : 3)) : (w < 1 << 5
? (w < 1 << 4 ? 4 : 5)
: (w < 1 << 6 ? 6 : 7)))
: (w < 1 << 11
? (w < 1 << 9 ? (w < 1 << 8 ? 8 : 9) : (w < 1 << 10 ? 10 : 11))
: (w < 1 << 13 ? (w < 1 << 12 ? 12 : 13) : (w < 1 << 14 ? 14 : 15)))) : (w < 1 << 23 ? (w < 1 << 19
? (w < 1 << 17 ? (w < 1 << 16 ? 16 : 17) : (w < 1 << 18 ? 18 : 19))
: (w < 1 << 21 ? (w < 1 << 20 ? 20 : 21) : (w < 1 << 22 ? 22 : 23))) : (w < 1 << 27
? (w < 1 << 25 ? (w < 1 << 24 ? 24 : 25) : (w < 1 << 26 ? 26 : 27))
: (w < 1 << 29 ? (w < 1 << 28 ? 28 : 29) : (w < 1 << 30 ? 30 : 31))));
}
}
}
1 change: 1 addition & 0 deletions src/Neo.Extensions/Neo.Extensions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<InternalsVisibleTo Include="Neo" />
<InternalsVisibleTo Include="Neo.IO" />
<InternalsVisibleTo Include="Neo.UnitTests" />
<InternalsVisibleTo Include="Neo.Extensions.Tests" />
</ItemGroup>

</Project>
80 changes: 0 additions & 80 deletions src/Neo.VM/Utility.cs

This file was deleted.

2 changes: 1 addition & 1 deletion src/Neo/Cryptography/ECC/ECCurve.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public class ECCurve
private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G, string curveName)
{
this.Q = Q;
ExpectedECPointLength = ((int)VM.Utility.GetBitLength(Q) + 7) / 8;
ExpectedECPointLength = ((int)Q.GetBitLength() + 7) / 8;
this.A = new ECFieldElement(A, this);
this.B = new ECFieldElement(B, this);
this.N = N;
Expand Down
4 changes: 2 additions & 2 deletions src/Neo/Cryptography/ECC/ECFieldElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public bool Equals(ECFieldElement? other)

private static BigInteger[] FastLucasSequence(BigInteger p, BigInteger P, BigInteger Q, BigInteger k)
{
var n = (int)VM.Utility.GetBitLength(k);
var n = (int)k.GetBitLength();
var s = k.GetLowestSetBit();

BigInteger Uh = 1;
Expand Down Expand Up @@ -125,7 +125,7 @@ public override int GetHashCode()
BigInteger P;
do
{
P = rand.NextBigInteger((int)VM.Utility.GetBitLength(_curve.Q));
P = rand.NextBigInteger((int)_curve.Q.GetBitLength());
}
while (P >= _curve.Q || BigInteger.ModPow(P * P - fourQ, legendreExponent, _curve.Q) != qMinusOne);
var result = FastLucasSequence(_curve.Q, P, Q, k);
Expand Down
4 changes: 2 additions & 2 deletions src/Neo/Cryptography/ECC/ECPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ public override int GetHashCode()
internal static ECPoint Multiply(ECPoint p, BigInteger k)
{
// floor(log2(k))
var m = (int)VM.Utility.GetBitLength(k);
var m = (int)k.GetBitLength();

// width of the Window NAF
sbyte width;
Expand Down Expand Up @@ -397,7 +397,7 @@ internal ECPoint Twice()

private static sbyte[] WindowNaf(sbyte width, BigInteger k)
{
var wnaf = new sbyte[VM.Utility.GetBitLength(k) + 1];
var wnaf = new sbyte[k.GetBitLength() + 1];
var pow2wB = (short)(1 << width);
var i = 0;
var length = 0;
Expand Down
91 changes: 89 additions & 2 deletions tests/Neo.Extensions.Tests/UT_BigIntegerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,14 @@ public void TestToByteArrayStandard_EdgeCases()
{
CollectionAssert.AreEqual(new byte[] { 0xFF }, BigInteger.MinusOne.ToByteArrayStandard());
CollectionAssert.AreEqual(new byte[] { 0xFF, 0x00 }, new BigInteger(byte.MaxValue).ToByteArrayStandard());
CollectionAssert.AreEqual(new byte[] { 0xFF, 0xFF, 0x00 }, new BigInteger(ushort.MaxValue).ToByteArrayStandard());
CollectionAssert.AreEqual(new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0 }, new BigInteger(JNumber.MIN_SAFE_INTEGER).ToByteArrayStandard());
CollectionAssert.AreEqual(
new byte[] { 0xFF, 0xFF, 0x00 },
new BigInteger(ushort.MaxValue).ToByteArrayStandard()
);
CollectionAssert.AreEqual(
new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0 },
new BigInteger(JNumber.MIN_SAFE_INTEGER).ToByteArrayStandard()
);
}

[TestMethod]
Expand Down Expand Up @@ -181,6 +187,87 @@ public void TestSum_EdgeCases()
Assert.AreEqual(JNumber.MAX_SAFE_INTEGER * 2, new List<BigInteger> { JNumber.MAX_SAFE_INTEGER, JNumber.MAX_SAFE_INTEGER }.Sum());
}

[TestMethod]
public void TestSqrtTest()
{
Assert.ThrowsExactly<InvalidOperationException>(() => _ = BigInteger.MinusOne.Sqrt());

Assert.AreEqual(BigInteger.Zero, BigInteger.Zero.Sqrt());
Assert.AreEqual(new BigInteger(1), new BigInteger(1).Sqrt());
Assert.AreEqual(new BigInteger(1), new BigInteger(2).Sqrt());
Assert.AreEqual(new BigInteger(1), new BigInteger(3).Sqrt());
Assert.AreEqual(new BigInteger(2), new BigInteger(4).Sqrt());
Assert.AreEqual(new BigInteger(9), new BigInteger(81).Sqrt());
}

private static byte[] GetRandomByteArray(Random random)
{
var byteValue = random.Next(0, 32);
var value = new byte[byteValue];

random.NextBytes(value);
return value;
}

private void VerifyGetBitLength(BigInteger value, long expected)
{
var result = value.GetBitLength();
Assert.AreEqual(expected, value.GetBitLength(), "Native method has not the expected result");
Assert.AreEqual(result, BigIntegerExtensions.GetBitLength(value), "Result doesn't match");
Assert.AreEqual(result, BigIntegerExtensions.BitLength(value), "Result doesn't match");
}

[TestMethod]
public void TestGetBitLength()
{
var random = new Random();

// Big Number (net standard didn't work)
Assert.ThrowsExactly<OverflowException>(() => VerifyGetBitLength(BigInteger.One << 32 << int.MaxValue, 2147483680));

// Trivial cases
// sign bit|shortest two's complement
// string w/o sign bit
VerifyGetBitLength(0, 0); // 0|
VerifyGetBitLength(1, 1); // 0|1
VerifyGetBitLength(-1, 0); // 1|
VerifyGetBitLength(2, 2); // 0|10
VerifyGetBitLength(-2, 1); // 1|0
VerifyGetBitLength(3, 2); // 0|11
VerifyGetBitLength(-3, 2); // 1|01
VerifyGetBitLength(4, 3); // 0|100
VerifyGetBitLength(-4, 2); // 1|00
VerifyGetBitLength(5, 3); // 0|101
VerifyGetBitLength(-5, 3); // 1|011
VerifyGetBitLength(6, 3); // 0|110
VerifyGetBitLength(-6, 3); // 1|010
VerifyGetBitLength(7, 3); // 0|111
VerifyGetBitLength(-7, 3); // 1|001
VerifyGetBitLength(8, 4); // 0|1000
VerifyGetBitLength(-8, 3); // 1|000

// Random cases
for (uint i = 0; i < 1000; i++)
{
var b = new BigInteger(GetRandomByteArray(random));
Assert.AreEqual(b.GetBitLength(), BigIntegerExtensions.GetBitLength(b), message: $"Error comparing: {b}");
Assert.AreEqual(b.GetBitLength(), BigIntegerExtensions.BitLength(b), message: $"Error comparing: {b}");
}

foreach (var bv in new[] { BigInteger.Zero, BigInteger.One, BigInteger.MinusOne, new(ulong.MaxValue), new(long.MinValue) })
{
Assert.AreEqual(bv.GetBitLength(), BigIntegerExtensions.GetBitLength(bv));
Assert.AreEqual(bv.GetBitLength(), BigIntegerExtensions.BitLength(bv));
}

for (var i = 0; i < 1000; i++)
{
var b = new BigInteger(i);
Assert.AreEqual(b.GetBitLength(), BigIntegerExtensions.GetBitLength(b), message: $"Error comparing: {b}");
Assert.AreEqual(b.GetBitLength(), BigIntegerExtensions.BitLength(b), message: $"Error comparing: {b}");
}
}

[TestMethod]
public void TestModInverseTest()
{
Expand Down
Loading