Skip to content

Commit 84f2352

Browse files
authored
Mimic BigInteger members on Int32 (#1399)
* Mimic BigInteger members on Int32 * Add tests * Finish IronPython.test_int.IntTest.test_instance_set * Implement missing methods on .NET Standard 2.0 * Fix up failing tests on Mono * Improve tests
1 parent 1621bbe commit 84f2352

4 files changed

Lines changed: 230 additions & 22 deletions

File tree

Src/IronPython/Lib/iptest/test_env.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
is_net50 = False
2323
is_net60 = False
2424
is_mono = False
25+
is_netstandard = False
2526
if is_ironpython:
2627
import clr
2728
is_netcoreapp = clr.IsNetCoreApp
@@ -30,10 +31,11 @@
3031
is_net50 = clr.FrameworkDescription.startswith(".NET 5.0")
3132
is_net60 = clr.FrameworkDescription.startswith(".NET 6.0")
3233
is_mono = clr.IsMono
34+
is_netstandard = clr.TargetFramework.startswith(".NETStandard")
3335

3436
#--The bittedness of the Python implementation
3537
is_cli32, is_cli64 = False, False
36-
if is_ironpython:
38+
if is_ironpython:
3739
import System
3840
is_cli32, is_cli64 = (System.IntPtr.Size == 4), (System.IntPtr.Size == 8)
3941

Src/IronPython/Runtime/Operations/IntOps.cs

Lines changed: 146 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,7 @@ public static string __format__(CodeContext/*!*/ context, int self, [NotDynamicN
231231
// So use FormattingHelper.ToCultureString for that support.
232232
if (spec.Fill.HasValue && spec.Fill.Value == '0' && width > 1) {
233233
digits = FormattingHelper.ToCultureString(self, culture.NumberFormat, spec);
234-
}
235-
else {
234+
} else {
236235
digits = self.ToString("N0", culture);
237236
}
238237
break;
@@ -246,8 +245,7 @@ public static string __format__(CodeContext/*!*/ context, int self, [NotDynamicN
246245
// so use FormattingHelper.ToCultureString for that support.
247246
if (spec.Fill.HasValue && spec.Fill.Value == '0' && width > 1) {
248247
digits = FormattingHelper.ToCultureString(self, FormattingHelper.InvariantCommaNumberInfo, spec);
249-
}
250-
else {
248+
} else {
251249
digits = self.ToString("#,0", CultureInfo.InvariantCulture);
252250
}
253251
} else {
@@ -299,8 +297,7 @@ public static string __format__(CodeContext/*!*/ context, int self, [NotDynamicN
299297
} else if (spec.ThousandsComma) {
300298
// Handle the common case in 'd'.
301299
goto case 'd';
302-
}
303-
else {
300+
} else {
304301
digits = self.ToString(CultureInfo.InvariantCulture);
305302
}
306303
break;
@@ -391,7 +388,7 @@ internal static string ToBinary(int self) {
391388
if (self == Int32.MinValue) {
392389
return "-0b10000000000000000000000000000000";
393390
}
394-
391+
395392
string res = ToBinary(self, true);
396393
if (self < 0) {
397394
res = "-" + res;
@@ -422,14 +419,155 @@ private static string ToBinary(int self, bool includeType) {
422419
} else {
423420
digits = "10000000000000000000000000000000";
424421
}
425-
422+
426423
if (includeType) {
427424
digits = "0b" + digits;
428425
}
429426
return digits;
430427
}
431428

432429
#endregion
430+
431+
#region Mimic BigInteger members
432+
// Ideally only on instances
433+
434+
#region Properties
435+
436+
[SpecialName, PropertyMethod, PythonHidden]
437+
public static bool GetIsEven(int self) => (self & 1) == 0;
438+
439+
[SpecialName, PropertyMethod, PythonHidden]
440+
public static bool GetIsOne(int self) => self == 1;
441+
442+
[SpecialName, PropertyMethod, PythonHidden]
443+
public static bool GetIsPowerOfTwo(int self) => self > 0 && (self & (self - 1)) == 0;
444+
445+
[SpecialName, PropertyMethod, PythonHidden]
446+
public static bool GetIsZero(int self) => self == 0;
447+
448+
[SpecialName, PropertyMethod, PythonHidden]
449+
public static int GetSign(int self) => self == 0 ? 0 : self > 0 ? 1 : -1;
450+
451+
[PythonHidden]
452+
public static object Zero => ScriptingRuntimeHelpers.Int32ToObject(0);
453+
454+
[PythonHidden]
455+
public static object One => ScriptingRuntimeHelpers.Int32ToObject(1);
456+
457+
[PythonHidden]
458+
public static object MinusOne => ScriptingRuntimeHelpers.Int32ToObject(-1);
459+
460+
#endregion
461+
462+
#region Methods
463+
464+
[PythonHidden]
465+
public static byte[] ToByteArray(int self) => new BigInteger(self).ToByteArray();
466+
467+
#if NETCOREAPP
468+
[PythonHidden]
469+
public static byte[] ToByteArray(int self, bool isUnsigned = false, bool isBigEndian = false) => new BigInteger(self).ToByteArray(isUnsigned, isBigEndian);
470+
471+
[PythonHidden]
472+
public static int GetByteCount(int self, bool isUnsigned = false) => new BigInteger(self).GetByteCount(isUnsigned);
473+
474+
[PythonHidden]
475+
public static bool TryWriteBytes(int self, Span<byte> destination, out int bytesWritten, bool isUnsigned = false, bool isBigEndian = false)
476+
=> new BigInteger(self).TryWriteBytes(destination, out bytesWritten, isUnsigned, isBigEndian);
477+
478+
#elif NETSTANDARD
479+
[PythonHidden]
480+
public static byte[] ToByteArray(int self, bool isUnsigned = false, bool isBigEndian = false) {
481+
if (self < 0 && isUnsigned) throw new OverflowException("Negative values do not have an unsigned representation.");
482+
byte[] bytes = ToByteArray(self);
483+
int count = bytes.Length;
484+
if (isUnsigned && count > 1 && bytes[count - 1] == 0) Array.Resize(ref bytes, count - 1);
485+
if (isBigEndian) Array.Reverse(bytes);
486+
return bytes;
487+
}
488+
489+
[PythonHidden]
490+
public static int GetByteCount(int self, bool isUnsigned = false) => ToByteArray(self, isUnsigned).Length;
491+
492+
[PythonHidden]
493+
public static bool TryWriteBytes(int self, Span<byte> destination, out int bytesWritten, bool isUnsigned = false, bool isBigEndian = false) {
494+
bytesWritten = 0;
495+
byte[] bytes = ToByteArray(self, isUnsigned, isBigEndian);
496+
if (bytes.Length > destination.Length) return false;
497+
498+
bytes.AsSpan().CopyTo(destination);
499+
bytesWritten = bytes.Length;
500+
return true;
501+
}
502+
#endif
503+
504+
#if NET || NETSTANDARD
505+
[PythonHidden]
506+
public static long GetBitLength(int self) {
507+
int length = MathUtils.BitLength(self);
508+
if (self < 0 && (self == int.MinValue || GetIsPowerOfTwo(-self))) length--;
509+
return length;
510+
}
511+
#endif
512+
513+
#endregion
514+
515+
#region Static Extension Methods
516+
517+
[StaticExtensionMethod, PythonHidden]
518+
public static BigInteger Compare(BigInteger left, BigInteger right) => BigInteger.Compare(left, right);
519+
520+
[StaticExtensionMethod, PythonHidden]
521+
public static BigInteger Min(BigInteger left, BigInteger right) => BigInteger.Min(left, right);
522+
523+
[StaticExtensionMethod, PythonHidden]
524+
public static BigInteger Max(BigInteger left, BigInteger right) => BigInteger.Max(left, right);
525+
526+
[StaticExtensionMethod, PythonHidden]
527+
public static double Log(BigInteger value) => BigInteger.Log(value);
528+
529+
[StaticExtensionMethod, PythonHidden]
530+
public static double Log(BigInteger value, double baseValue) => BigInteger.Log(value, baseValue);
531+
532+
[StaticExtensionMethod, PythonHidden]
533+
public static double Log10(BigInteger value) => BigInteger.Log10(value);
534+
535+
[StaticExtensionMethod, PythonHidden]
536+
public static object Pow(BigInteger value, int exponent) => BigInteger.Pow(value, exponent);
537+
538+
[StaticExtensionMethod, PythonHidden]
539+
public static object ModPow(BigInteger value, BigInteger exponent, BigInteger modulus) => BigInteger.ModPow(value, exponent, modulus);
540+
541+
[StaticExtensionMethod, PythonHidden]
542+
public static BigInteger Negate(BigInteger value) => BigInteger.Negate(value);
543+
544+
[StaticExtensionMethod, PythonHidden]
545+
public static BigInteger Abs(BigInteger value) => BigInteger.Abs(value);
546+
547+
[StaticExtensionMethod, PythonHidden]
548+
public static BigInteger Add(BigInteger left, BigInteger right) => BigInteger.Add(left, right);
549+
550+
[StaticExtensionMethod, PythonHidden]
551+
public static BigInteger Subtract(BigInteger left, BigInteger right) => BigInteger.Subtract(left, right);
552+
553+
[StaticExtensionMethod, PythonHidden]
554+
public static BigInteger Multiply(BigInteger left, BigInteger right) => BigInteger.Multiply(left, right);
555+
556+
[StaticExtensionMethod, PythonHidden]
557+
public static BigInteger Divide(BigInteger left, BigInteger right) => BigInteger.Divide(left, right);
558+
559+
[StaticExtensionMethod, PythonHidden]
560+
public static BigInteger Remainder(BigInteger dividend, BigInteger divisor) => BigInteger.Remainder(dividend, divisor);
561+
562+
[StaticExtensionMethod, PythonHidden]
563+
public static BigInteger DivRem(BigInteger dividend, BigInteger divisor, out BigInteger remainder) => BigInteger.DivRem(dividend, divisor, out remainder);
564+
565+
[StaticExtensionMethod, PythonHidden]
566+
public static BigInteger GreatestCommonDivisor(BigInteger left, BigInteger right) => BigInteger.GreatestCommonDivisor(left, right);
567+
568+
#endregion
569+
570+
#endregion
433571
}
434572

435573
public static partial class Int64Ops {

Tests/test_cliclass.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,6 +1420,71 @@ def test_clr_dir(self):
14201420
self.assertTrue('IndexOf' not in clr.Dir('abc'))
14211421
self.assertTrue('IndexOf' in clr.DirClr('abc'))
14221422

1423+
def test_int32_bigint_equivalence(self):
1424+
import math
1425+
1426+
# properties
1427+
for i in range(-10, 10):
1428+
bi = big(i)
1429+
self.assertEqual(i.IsEven, bi.IsEven)
1430+
self.assertEqual(i.IsOne, bi.IsOne)
1431+
self.assertEqual(i.IsPowerOfTwo, bi.IsPowerOfTwo)
1432+
self.assertEqual(i.IsZero, bi.IsZero)
1433+
self.assertEqual(i.Sign, bi.Sign)
1434+
# static properties
1435+
self.assertEqual(i.Zero, bi.Zero)
1436+
self.assertEqual(i.One, bi.One)
1437+
self.assertEqual(i.MinusOne, bi.MinusOne)
1438+
1439+
test_values = [0, 1, 2, 4, 8, 10, 255, 256, 7<<30, 1<<31, (1<<31)-1, (1<<31)+1, 1<<32, (1<<32)-1, (1<<32)+1]
1440+
# methods
1441+
for i in test_values:
1442+
for i2 in [i, -i]:
1443+
ii = int(i2) # convert to Int32 if possible
1444+
bi = big(i2)
1445+
self.assertEqual(ii.ToByteArray(), bi.ToByteArray())
1446+
if hasattr(ii, 'GetByteCount') and hasattr(bi, 'GetByteCount'):
1447+
self.assertEqual(ii.GetByteCount(), bi.GetByteCount())
1448+
if hasattr(ii, 'GetBitLength') and hasattr(bi, 'GetBitLength'):
1449+
self.assertEqual(ii.GetBitLength(), bi.GetBitLength())
1450+
1451+
# static methods
1452+
for i in test_values:
1453+
for i2 in [i, -i]:
1454+
ii = int(i2) # convert to Int32 if possible
1455+
bi = big(i2)
1456+
self.assertEqual((1).Negate(ii), int.Negate(bi))
1457+
self.assertEqual((1).Abs(ii), int.Abs(bi))
1458+
self.assertEqual((1).Pow(ii, 5), int.Pow(bi, 5))
1459+
self.assertEqual((1).ModPow(ii, 5, 3), int.ModPow(bi, 5, 3))
1460+
if ii >= 0:
1461+
self.assertEqual((1).Log(ii), int.Log(bi))
1462+
self.assertEqual((1).Log10(ii), int.Log10(bi))
1463+
self.assertEqual((1).Log(ii, 7.2), int.Log(bi, 7.2))
1464+
else:
1465+
self.assertTrue(math.isnan((1).Log(ii)))
1466+
self.assertTrue(math.isnan(int.Log(bi)))
1467+
self.assertTrue(math.isnan((1).Log10(ii)))
1468+
self.assertTrue(math.isnan(int.Log10(bi)))
1469+
self.assertTrue(math.isnan((1).Log(ii, 7.2)))
1470+
self.assertTrue(math.isnan(int.Log(bi, 7.2)))
1471+
1472+
for j in test_values:
1473+
for j2 in [j, -j]:
1474+
jj = int(j2) # convert to Int32 if possible
1475+
bj = big(j2)
1476+
self.assertEqual((1).Compare(ii, jj), int.Compare(bi, bj))
1477+
self.assertEqual((1).Min(ii, jj), int.Min(bi, bj))
1478+
self.assertEqual((1).Max(ii, jj), int.Max(bi, bj))
1479+
self.assertEqual((1).Add(ii, jj), int.Add(bi, bj))
1480+
self.assertEqual((1).Subtract(ii, jj), int.Subtract(bi, bj))
1481+
self.assertEqual((1).Multiply(ii, jj), int.Multiply(bi, bj))
1482+
self.assertEqual((1).GreatestCommonDivisor(ii, jj), int.GreatestCommonDivisor(bi, bj))
1483+
if jj != 0:
1484+
self.assertEqual((1).Divide(ii, jj), int.Divide(bi, bj))
1485+
self.assertEqual((1).DivRem(ii, jj), int.DivRem(bi, bj))
1486+
self.assertEqual((1).Remainder(ii, jj), int.Remainder(bi, bj))
1487+
14231488
def test_array_contains(self):
14241489
if is_mono: # for whatever reason this is defined on Mono
14251490
System.Array[str].__dict__['__contains__']

Tests/test_int.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
# See the LICENSE file in the project root for more information.
44

55
import sys
6-
import unittest
76

8-
from iptest import IronPythonTestCase, is_cli, big, myint, skipUnlessIronPython, run_test
7+
from iptest import IronPythonTestCase, is_cli, is_netstandard, is_mono, big, myint, skipUnlessIronPython, run_test
98

109
class IntNoClrTest(IronPythonTestCase):
1110
"""Must be run before IntTest because it depends on CLR API not being visible."""
@@ -21,17 +20,21 @@ def test_instance_set(self):
2120
j = big(1)
2221
from System import Int32
2322

24-
self.assertSetEqual(set(dir(i)) - set(dir(j)), {'MaxValue', 'MinValue'})
25-
self.assertSetEqual(set(dir(Int32)) - set(dir(int)), {'MaxValue', 'MinValue'})
26-
27-
@unittest.expectedFailure
28-
def test_instance_set_todo(self):
29-
i = 1
30-
j = big(1)
31-
from System import Int32
32-
33-
self.assertSetEqual(set(dir(j)) - set(dir(i)), set())
34-
self.assertSetEqual(set(dir(int)) - set(dir(Int32)), set())
23+
if not is_mono:
24+
self.assertSetEqual(set(dir(j)) - set(dir(i)), set())
25+
self.assertSetEqual(set(dir(int)) - set(dir(Int32)), set())
26+
else:
27+
self.assertSetEqual(set(dir(j)) - set(dir(i)), {'GetByteCount', 'TryWriteBytes'})
28+
self.assertSetEqual(set(dir(int)) - set(dir(Int32)), {'GetByteCount', 'TryWriteBytes'})
29+
30+
# these two assertions fail on IronPython compiled for .NET Standard
31+
if not is_netstandard:
32+
self.assertSetEqual(set(dir(i)) - set(dir(j)), {'MaxValue', 'MinValue'})
33+
self.assertSetEqual(set(dir(Int32)) - set(dir(int)), {'MaxValue', 'MinValue'})
34+
35+
# weaker assertions that should always hold
36+
self.assertTrue((set(dir(i)) - set(dir(j))).issubset({'MaxValue', 'MinValue', 'GetByteCount', 'TryWriteBytes', 'GetBitLength'}))
37+
self.assertTrue((set(dir(Int32)) - set(dir(int))).issubset({'MaxValue', 'MinValue', 'GetByteCount', 'TryWriteBytes', 'GetBitLength'}))
3538

3639
def test_from_bytes(self):
3740
self.assertEqual(type(int.from_bytes(b"abc", "big")), int)

0 commit comments

Comments
 (0)