Skip to content
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
38 changes: 23 additions & 15 deletions src/Neo.Extensions/BigIntegerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,33 @@ namespace Neo.Extensions
{
public static class BigIntegerExtensions
{
internal static int TrailingZeroCount(byte[] b)
{
var w = 0;
while (b[w] == 0) w++;
for (var x = 0; x < 8; x++)
{
if ((b[w] & 1 << x) > 0)
return x + w * 8; // cannot greater than 2Gib
}
return -1; // unreachable, because returned earlier if value is zero
}

/// <summary>
/// Finds the lowest set bit in the specified value.
/// Finds the lowest set bit in the specified value. If value is zero, returns -1.
/// </summary>
/// <param name="value">The value to find the lowest set bit in.</param>
/// <param name="value">The value to find the lowest set bit in. The value.GetBitLength cannot greater than 2Gib.</param>
/// <returns>The lowest set bit in the specified value.</returns>
/// <exception cref="Exception">Thrown when the value is zero.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetLowestSetBit(this BigInteger value)
{
if (value.Sign == 0)
return -1;
var b = value.ToByteArray();
var w = 0;
while (b[w] == 0)
w++;
for (var x = 0; x < 8; x++)
if ((b[w] & 1 << x) > 0)
return x + w * 8;
throw new Exception("The value is zero.");
if (value.Sign == 0) return -1; // special case for zero. TrailingZeroCount returns 32 in standard library.

#if NET7_0_OR_GREATER
return (int)BigInteger.TrailingZeroCount(value);
#else
return TrailingZeroCount(value.ToByteArray());
#endif
}

/// <summary>
Expand Down Expand Up @@ -97,7 +106,6 @@ public static BigInteger ModInverse(this BigInteger value, BigInteger modulus)
/// <param name="value">The value to test.</param>
/// <param name="index">The index of the bit to test.</param>
/// <returns>True if the specified bit is set in the specified value, otherwise false.</returns>

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TestBit(this BigInteger value, int index)
{
Expand All @@ -117,7 +125,7 @@ public static BigInteger Sum(this IEnumerable<BigInteger> source)
}

/// <summary>
/// Converts a <see cref="BigInteger"/> to byte array and eliminates all the leading zeros.
/// Converts a <see cref="BigInteger"/> to byte array in little-endian and eliminates all the leading zeros.
/// If the value is zero, it returns an empty byte array.
/// </summary>
/// <param name="value">The <see cref="BigInteger"/> to convert.</param>
Expand Down
36 changes: 36 additions & 0 deletions tests/Neo.Extensions.Tests/UT_BigIntegerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

using Neo.Json;
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Numerics;

Expand All @@ -24,21 +25,56 @@ public void TestGetLowestSetBit()
{
var big1 = new BigInteger(0);
Assert.AreEqual(-1, big1.GetLowestSetBit());
Assert.AreEqual(32, BigInteger.TrailingZeroCount(big1)); // NOTE: 32 if zero in standard library

var big2 = new BigInteger(512);
Assert.AreEqual(9, big2.GetLowestSetBit());
Assert.AreEqual(9, BigInteger.TrailingZeroCount(big2));

var big3 = new BigInteger(int.MinValue);
Assert.AreEqual(31, big3.GetLowestSetBit());
Assert.AreEqual(31, BigInteger.TrailingZeroCount(big3));

var big4 = new BigInteger(long.MinValue);
Assert.AreEqual(63, big4.GetLowestSetBit());
Assert.AreEqual(63, BigInteger.TrailingZeroCount(big4));

var big5 = new BigInteger(-18);
Assert.AreEqual(1, big5.GetLowestSetBit());
Assert.AreEqual(1, BigInteger.TrailingZeroCount(big5));

var big6 = BigInteger.Pow(2, 1000);
Assert.AreEqual(1000, big6.GetLowestSetBit());
Assert.AreEqual(1000, BigInteger.TrailingZeroCount(big6));

for (var i = 0; i < 64; i++)
{
var b = new BigInteger(1ul << i);
Assert.AreEqual(i, BigIntegerExtensions.TrailingZeroCount(b.ToByteArray()));
Assert.AreEqual(i, BigInteger.TrailingZeroCount(b));
}

var random = new Random();
for (var i = 0; i < 128; i++)
{
var buffer = new byte[16];
BinaryPrimitives.WriteInt128LittleEndian(buffer, Int128.One << i);

var b = new BigInteger(buffer, isUnsigned: false);
Assert.AreEqual(i, BigIntegerExtensions.TrailingZeroCount(b.ToByteArray()));
Assert.AreEqual(i, BigInteger.TrailingZeroCount(b));

BinaryPrimitives.WriteUInt128LittleEndian(buffer, UInt128.One << i);
b = new BigInteger(buffer, isUnsigned: true);
Assert.AreEqual(i, BigIntegerExtensions.TrailingZeroCount(b.ToByteArray()));
Assert.AreEqual(i, BigInteger.TrailingZeroCount(b));

buffer = new byte[32]; // 256bit
random.NextBytes(buffer);
Comment on lines +72 to +73
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use something like this BigInteger.Pow(10, random.Next(byte.MaxValue)). So we can check really big numbers.

Copy link
Contributor Author

@Wi1l-B0t Wi1l-B0t Jun 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use something like this BigInteger.Pow(10, random.Next(byte.MaxValue)). So we can check really big numbers.

Max size is 256bit in neo.

b = new BigInteger(buffer, isUnsigned: true);
var zeroCount = BigInteger.TrailingZeroCount(b);
if (!b.IsZero) Assert.AreEqual(zeroCount, BigIntegerExtensions.TrailingZeroCount(b.ToByteArray()));
}
}

[TestMethod]
Expand Down