Skip to content

Commit e53a894

Browse files
authored
style: move BigInteger extensions from Utility to BigIntegerExtensions (#3916)
1 parent 0a60b93 commit e53a894

File tree

8 files changed

+165
-181
lines changed

8 files changed

+165
-181
lines changed

src/Neo.Extensions/BigIntegerExtensions.cs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,77 @@ public static BigInteger Sum(this IEnumerable<BigInteger> source)
117117
[MethodImpl(MethodImplOptions.AggressiveInlining)]
118118
public static byte[] ToByteArrayStandard(this BigInteger value)
119119
{
120-
if (value.IsZero) return Array.Empty<byte>();
120+
if (value.IsZero) return [];
121121
return value.ToByteArray();
122122
}
123+
124+
public static BigInteger Sqrt(this BigInteger value)
125+
{
126+
if (value < 0) throw new InvalidOperationException($"value {value} can not be negative for '{nameof(Sqrt)}'.");
127+
if (value.IsZero) return BigInteger.Zero;
128+
if (value < 4) return BigInteger.One;
129+
130+
var z = value;
131+
var x = BigInteger.One << (int)(((value - 1).GetBitLength() + 1) >> 1);
132+
while (x < z)
133+
{
134+
z = x;
135+
x = (value / x + x) / 2;
136+
}
137+
138+
return z;
139+
}
140+
141+
/// <summary>
142+
/// Gets the number of bits required for shortest two's complement representation of the current instance without the sign bit.
143+
/// Note: This method is imprecise and might not work as expected with integers larger than 256 bits if less than .NET5.
144+
/// </summary>
145+
/// <returns>The minimum non-negative number of bits in two's complement notation without the sign bit.</returns>
146+
/// <remarks>
147+
/// This method returns 0 if the value of current object is equal to
148+
/// <see cref="BigInteger.Zero"/> or <see cref="BigInteger.MinusOne"/>.
149+
/// For positive integers the return value is equal to the ordinary binary representation string length.
150+
/// </remarks>
151+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
152+
public static long GetBitLength(this BigInteger value)
153+
{
154+
#if NET5_0_OR_GREATER
155+
return value.GetBitLength();
156+
#else
157+
return BitLength(value);
158+
#endif
159+
}
160+
161+
/// <summary>
162+
/// GetBitLength for earlier than .NET5.0
163+
/// </summary>
164+
internal static long BitLength(this BigInteger value)
165+
{
166+
if (value == 0 || value == BigInteger.MinusOne) return 0;
167+
168+
var b = value.ToByteArray();
169+
if (b.Length == 1 || (b.Length == 2 && b[1] == 0))
170+
{
171+
return BitCount(value.Sign > 0 ? b[0] : (byte)(255 - b[0]));
172+
}
173+
return (b.Length - 1) * 8 + BitCount(value.Sign > 0 ? b[^1] : (byte)(255 - b[^1]));
174+
}
175+
176+
private static int BitCount(int w)
177+
{
178+
return w < 1 << 15 ? (w < 1 << 7
179+
? (w < 1 << 3 ? (w < 1 << 1
180+
? (w < 1 << 0 ? (w < 0 ? 32 : 0) : 1)
181+
: (w < 1 << 2 ? 2 : 3)) : (w < 1 << 5
182+
? (w < 1 << 4 ? 4 : 5)
183+
: (w < 1 << 6 ? 6 : 7)))
184+
: (w < 1 << 11
185+
? (w < 1 << 9 ? (w < 1 << 8 ? 8 : 9) : (w < 1 << 10 ? 10 : 11))
186+
: (w < 1 << 13 ? (w < 1 << 12 ? 12 : 13) : (w < 1 << 14 ? 14 : 15)))) : (w < 1 << 23 ? (w < 1 << 19
187+
? (w < 1 << 17 ? (w < 1 << 16 ? 16 : 17) : (w < 1 << 18 ? 18 : 19))
188+
: (w < 1 << 21 ? (w < 1 << 20 ? 20 : 21) : (w < 1 << 22 ? 22 : 23))) : (w < 1 << 27
189+
? (w < 1 << 25 ? (w < 1 << 24 ? 24 : 25) : (w < 1 << 26 ? 26 : 27))
190+
: (w < 1 << 29 ? (w < 1 << 28 ? 28 : 29) : (w < 1 << 30 ? 30 : 31))));
191+
}
123192
}
124193
}

src/Neo.Extensions/Neo.Extensions.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<InternalsVisibleTo Include="Neo" />
1717
<InternalsVisibleTo Include="Neo.IO" />
1818
<InternalsVisibleTo Include="Neo.UnitTests" />
19+
<InternalsVisibleTo Include="Neo.Extensions.Tests" />
1920
</ItemGroup>
2021

2122
</Project>

src/Neo.VM/Utility.cs

Lines changed: 0 additions & 80 deletions
This file was deleted.

src/Neo/Cryptography/ECC/ECCurve.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public class ECCurve
4646
private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G, string curveName)
4747
{
4848
this.Q = Q;
49-
ExpectedECPointLength = ((int)VM.Utility.GetBitLength(Q) + 7) / 8;
49+
ExpectedECPointLength = ((int)Q.GetBitLength() + 7) / 8;
5050
this.A = new ECFieldElement(A, this);
5151
this.B = new ECFieldElement(B, this);
5252
this.N = N;

src/Neo/Cryptography/ECC/ECFieldElement.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public bool Equals(ECFieldElement? other)
5353

5454
private static BigInteger[] FastLucasSequence(BigInteger p, BigInteger P, BigInteger Q, BigInteger k)
5555
{
56-
var n = (int)VM.Utility.GetBitLength(k);
56+
var n = (int)k.GetBitLength();
5757
var s = k.GetLowestSetBit();
5858

5959
BigInteger Uh = 1;
@@ -125,7 +125,7 @@ public override int GetHashCode()
125125
BigInteger P;
126126
do
127127
{
128-
P = rand.NextBigInteger((int)VM.Utility.GetBitLength(_curve.Q));
128+
P = rand.NextBigInteger((int)_curve.Q.GetBitLength());
129129
}
130130
while (P >= _curve.Q || BigInteger.ModPow(P * P - fourQ, legendreExponent, _curve.Q) != qMinusOne);
131131
var result = FastLucasSequence(_curve.Q, P, Q, k);

src/Neo/Cryptography/ECC/ECPoint.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ public override int GetHashCode()
234234
internal static ECPoint Multiply(ECPoint p, BigInteger k)
235235
{
236236
// floor(log2(k))
237-
var m = (int)VM.Utility.GetBitLength(k);
237+
var m = (int)k.GetBitLength();
238238

239239
// width of the Window NAF
240240
sbyte width;
@@ -397,7 +397,7 @@ internal ECPoint Twice()
397397

398398
private static sbyte[] WindowNaf(sbyte width, BigInteger k)
399399
{
400-
var wnaf = new sbyte[VM.Utility.GetBitLength(k) + 1];
400+
var wnaf = new sbyte[k.GetBitLength() + 1];
401401
var pow2wB = (short)(1 << width);
402402
var i = 0;
403403
var length = 0;

tests/Neo.Extensions.Tests/UT_BigIntegerExtensions.cs

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,14 @@ public void TestToByteArrayStandard_EdgeCases()
6868
{
6969
CollectionAssert.AreEqual(new byte[] { 0xFF }, BigInteger.MinusOne.ToByteArrayStandard());
7070
CollectionAssert.AreEqual(new byte[] { 0xFF, 0x00 }, new BigInteger(byte.MaxValue).ToByteArrayStandard());
71-
CollectionAssert.AreEqual(new byte[] { 0xFF, 0xFF, 0x00 }, new BigInteger(ushort.MaxValue).ToByteArrayStandard());
72-
CollectionAssert.AreEqual(new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0 }, new BigInteger(JNumber.MIN_SAFE_INTEGER).ToByteArrayStandard());
71+
CollectionAssert.AreEqual(
72+
new byte[] { 0xFF, 0xFF, 0x00 },
73+
new BigInteger(ushort.MaxValue).ToByteArrayStandard()
74+
);
75+
CollectionAssert.AreEqual(
76+
new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0 },
77+
new BigInteger(JNumber.MIN_SAFE_INTEGER).ToByteArrayStandard()
78+
);
7379
}
7480

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

190+
[TestMethod]
191+
public void TestSqrtTest()
192+
{
193+
Assert.ThrowsExactly<InvalidOperationException>(() => _ = BigInteger.MinusOne.Sqrt());
194+
195+
Assert.AreEqual(BigInteger.Zero, BigInteger.Zero.Sqrt());
196+
Assert.AreEqual(new BigInteger(1), new BigInteger(1).Sqrt());
197+
Assert.AreEqual(new BigInteger(1), new BigInteger(2).Sqrt());
198+
Assert.AreEqual(new BigInteger(1), new BigInteger(3).Sqrt());
199+
Assert.AreEqual(new BigInteger(2), new BigInteger(4).Sqrt());
200+
Assert.AreEqual(new BigInteger(9), new BigInteger(81).Sqrt());
201+
}
202+
203+
private static byte[] GetRandomByteArray(Random random)
204+
{
205+
var byteValue = random.Next(0, 32);
206+
var value = new byte[byteValue];
207+
208+
random.NextBytes(value);
209+
return value;
210+
}
211+
212+
private void VerifyGetBitLength(BigInteger value, long expected)
213+
{
214+
var result = value.GetBitLength();
215+
Assert.AreEqual(expected, value.GetBitLength(), "Native method has not the expected result");
216+
Assert.AreEqual(result, BigIntegerExtensions.GetBitLength(value), "Result doesn't match");
217+
Assert.AreEqual(result, BigIntegerExtensions.BitLength(value), "Result doesn't match");
218+
}
219+
220+
[TestMethod]
221+
public void TestGetBitLength()
222+
{
223+
var random = new Random();
224+
225+
// Big Number (net standard didn't work)
226+
Assert.ThrowsExactly<OverflowException>(() => VerifyGetBitLength(BigInteger.One << 32 << int.MaxValue, 2147483680));
227+
228+
// Trivial cases
229+
// sign bit|shortest two's complement
230+
// string w/o sign bit
231+
VerifyGetBitLength(0, 0); // 0|
232+
VerifyGetBitLength(1, 1); // 0|1
233+
VerifyGetBitLength(-1, 0); // 1|
234+
VerifyGetBitLength(2, 2); // 0|10
235+
VerifyGetBitLength(-2, 1); // 1|0
236+
VerifyGetBitLength(3, 2); // 0|11
237+
VerifyGetBitLength(-3, 2); // 1|01
238+
VerifyGetBitLength(4, 3); // 0|100
239+
VerifyGetBitLength(-4, 2); // 1|00
240+
VerifyGetBitLength(5, 3); // 0|101
241+
VerifyGetBitLength(-5, 3); // 1|011
242+
VerifyGetBitLength(6, 3); // 0|110
243+
VerifyGetBitLength(-6, 3); // 1|010
244+
VerifyGetBitLength(7, 3); // 0|111
245+
VerifyGetBitLength(-7, 3); // 1|001
246+
VerifyGetBitLength(8, 4); // 0|1000
247+
VerifyGetBitLength(-8, 3); // 1|000
248+
249+
// Random cases
250+
for (uint i = 0; i < 1000; i++)
251+
{
252+
var b = new BigInteger(GetRandomByteArray(random));
253+
Assert.AreEqual(b.GetBitLength(), BigIntegerExtensions.GetBitLength(b), message: $"Error comparing: {b}");
254+
Assert.AreEqual(b.GetBitLength(), BigIntegerExtensions.BitLength(b), message: $"Error comparing: {b}");
255+
}
256+
257+
foreach (var bv in new[] { BigInteger.Zero, BigInteger.One, BigInteger.MinusOne, new(ulong.MaxValue), new(long.MinValue) })
258+
{
259+
Assert.AreEqual(bv.GetBitLength(), BigIntegerExtensions.GetBitLength(bv));
260+
Assert.AreEqual(bv.GetBitLength(), BigIntegerExtensions.BitLength(bv));
261+
}
262+
263+
for (var i = 0; i < 1000; i++)
264+
{
265+
var b = new BigInteger(i);
266+
Assert.AreEqual(b.GetBitLength(), BigIntegerExtensions.GetBitLength(b), message: $"Error comparing: {b}");
267+
Assert.AreEqual(b.GetBitLength(), BigIntegerExtensions.BitLength(b), message: $"Error comparing: {b}");
268+
}
269+
}
270+
184271
[TestMethod]
185272
public void TestModInverseTest()
186273
{

0 commit comments

Comments
 (0)