diff --git a/Src/IronPython/Lib/iptest/test_env.py b/Src/IronPython/Lib/iptest/test_env.py index 0d37af906..b80b47dad 100644 --- a/Src/IronPython/Lib/iptest/test_env.py +++ b/Src/IronPython/Lib/iptest/test_env.py @@ -22,6 +22,7 @@ is_net50 = False is_net60 = False is_mono = False +is_netstandard = False if is_ironpython: import clr is_netcoreapp = clr.IsNetCoreApp @@ -30,10 +31,11 @@ is_net50 = clr.FrameworkDescription.startswith(".NET 5.0") is_net60 = clr.FrameworkDescription.startswith(".NET 6.0") is_mono = clr.IsMono + is_netstandard = clr.TargetFramework.startswith(".NETStandard") #--The bittedness of the Python implementation is_cli32, is_cli64 = False, False -if is_ironpython: +if is_ironpython: import System is_cli32, is_cli64 = (System.IntPtr.Size == 4), (System.IntPtr.Size == 8) diff --git a/Src/IronPython/Runtime/Operations/IntOps.cs b/Src/IronPython/Runtime/Operations/IntOps.cs index 274056b58..e5e375e52 100644 --- a/Src/IronPython/Runtime/Operations/IntOps.cs +++ b/Src/IronPython/Runtime/Operations/IntOps.cs @@ -231,8 +231,7 @@ public static string __format__(CodeContext/*!*/ context, int self, [NotDynamicN // So use FormattingHelper.ToCultureString for that support. if (spec.Fill.HasValue && spec.Fill.Value == '0' && width > 1) { digits = FormattingHelper.ToCultureString(self, culture.NumberFormat, spec); - } - else { + } else { digits = self.ToString("N0", culture); } break; @@ -246,8 +245,7 @@ public static string __format__(CodeContext/*!*/ context, int self, [NotDynamicN // so use FormattingHelper.ToCultureString for that support. if (spec.Fill.HasValue && spec.Fill.Value == '0' && width > 1) { digits = FormattingHelper.ToCultureString(self, FormattingHelper.InvariantCommaNumberInfo, spec); - } - else { + } else { digits = self.ToString("#,0", CultureInfo.InvariantCulture); } } else { @@ -299,8 +297,7 @@ public static string __format__(CodeContext/*!*/ context, int self, [NotDynamicN } else if (spec.ThousandsComma) { // Handle the common case in 'd'. goto case 'd'; - } - else { + } else { digits = self.ToString(CultureInfo.InvariantCulture); } break; @@ -391,7 +388,7 @@ internal static string ToBinary(int self) { if (self == Int32.MinValue) { return "-0b10000000000000000000000000000000"; } - + string res = ToBinary(self, true); if (self < 0) { res = "-" + res; @@ -422,7 +419,7 @@ private static string ToBinary(int self, bool includeType) { } else { digits = "10000000000000000000000000000000"; } - + if (includeType) { digits = "0b" + digits; } @@ -430,6 +427,147 @@ private static string ToBinary(int self, bool includeType) { } #endregion + + #region Mimic BigInteger members + // Ideally only on instances + + #region Properties + + [SpecialName, PropertyMethod, PythonHidden] + public static bool GetIsEven(int self) => (self & 1) == 0; + + [SpecialName, PropertyMethod, PythonHidden] + public static bool GetIsOne(int self) => self == 1; + + [SpecialName, PropertyMethod, PythonHidden] + public static bool GetIsPowerOfTwo(int self) => self > 0 && (self & (self - 1)) == 0; + + [SpecialName, PropertyMethod, PythonHidden] + public static bool GetIsZero(int self) => self == 0; + + [SpecialName, PropertyMethod, PythonHidden] + public static int GetSign(int self) => self == 0 ? 0 : self > 0 ? 1 : -1; + + [PythonHidden] + public static object Zero => ScriptingRuntimeHelpers.Int32ToObject(0); + + [PythonHidden] + public static object One => ScriptingRuntimeHelpers.Int32ToObject(1); + + [PythonHidden] + public static object MinusOne => ScriptingRuntimeHelpers.Int32ToObject(-1); + + #endregion + + #region Methods + + [PythonHidden] + public static byte[] ToByteArray(int self) => new BigInteger(self).ToByteArray(); + +#if NETCOREAPP + [PythonHidden] + public static byte[] ToByteArray(int self, bool isUnsigned = false, bool isBigEndian = false) => new BigInteger(self).ToByteArray(isUnsigned, isBigEndian); + + [PythonHidden] + public static int GetByteCount(int self, bool isUnsigned = false) => new BigInteger(self).GetByteCount(isUnsigned); + + [PythonHidden] + public static bool TryWriteBytes(int self, Span destination, out int bytesWritten, bool isUnsigned = false, bool isBigEndian = false) + => new BigInteger(self).TryWriteBytes(destination, out bytesWritten, isUnsigned, isBigEndian); + +#elif NETSTANDARD + [PythonHidden] + public static byte[] ToByteArray(int self, bool isUnsigned = false, bool isBigEndian = false) { + if (self < 0 && isUnsigned) throw new OverflowException("Negative values do not have an unsigned representation."); + byte[] bytes = ToByteArray(self); + int count = bytes.Length; + if (isUnsigned && count > 1 && bytes[count - 1] == 0) Array.Resize(ref bytes, count - 1); + if (isBigEndian) Array.Reverse(bytes); + return bytes; + } + + [PythonHidden] + public static int GetByteCount(int self, bool isUnsigned = false) => ToByteArray(self, isUnsigned).Length; + + [PythonHidden] + public static bool TryWriteBytes(int self, Span destination, out int bytesWritten, bool isUnsigned = false, bool isBigEndian = false) { + bytesWritten = 0; + byte[] bytes = ToByteArray(self, isUnsigned, isBigEndian); + if (bytes.Length > destination.Length) return false; + + bytes.AsSpan().CopyTo(destination); + bytesWritten = bytes.Length; + return true; + } +#endif + +#if NET || NETSTANDARD + [PythonHidden] + public static long GetBitLength(int self) { + int length = MathUtils.BitLength(self); + if (self < 0 && (self == int.MinValue || GetIsPowerOfTwo(-self))) length--; + return length; + } +#endif + + #endregion + + #region Static Extension Methods + + [StaticExtensionMethod, PythonHidden] + public static BigInteger Compare(BigInteger left, BigInteger right) => BigInteger.Compare(left, right); + + [StaticExtensionMethod, PythonHidden] + public static BigInteger Min(BigInteger left, BigInteger right) => BigInteger.Min(left, right); + + [StaticExtensionMethod, PythonHidden] + public static BigInteger Max(BigInteger left, BigInteger right) => BigInteger.Max(left, right); + + [StaticExtensionMethod, PythonHidden] + public static double Log(BigInteger value) => BigInteger.Log(value); + + [StaticExtensionMethod, PythonHidden] + public static double Log(BigInteger value, double baseValue) => BigInteger.Log(value, baseValue); + + [StaticExtensionMethod, PythonHidden] + public static double Log10(BigInteger value) => BigInteger.Log10(value); + + [StaticExtensionMethod, PythonHidden] + public static object Pow(BigInteger value, int exponent) => BigInteger.Pow(value, exponent); + + [StaticExtensionMethod, PythonHidden] + public static object ModPow(BigInteger value, BigInteger exponent, BigInteger modulus) => BigInteger.ModPow(value, exponent, modulus); + + [StaticExtensionMethod, PythonHidden] + public static BigInteger Negate(BigInteger value) => BigInteger.Negate(value); + + [StaticExtensionMethod, PythonHidden] + public static BigInteger Abs(BigInteger value) => BigInteger.Abs(value); + + [StaticExtensionMethod, PythonHidden] + public static BigInteger Add(BigInteger left, BigInteger right) => BigInteger.Add(left, right); + + [StaticExtensionMethod, PythonHidden] + public static BigInteger Subtract(BigInteger left, BigInteger right) => BigInteger.Subtract(left, right); + + [StaticExtensionMethod, PythonHidden] + public static BigInteger Multiply(BigInteger left, BigInteger right) => BigInteger.Multiply(left, right); + + [StaticExtensionMethod, PythonHidden] + public static BigInteger Divide(BigInteger left, BigInteger right) => BigInteger.Divide(left, right); + + [StaticExtensionMethod, PythonHidden] + public static BigInteger Remainder(BigInteger dividend, BigInteger divisor) => BigInteger.Remainder(dividend, divisor); + + [StaticExtensionMethod, PythonHidden] + public static BigInteger DivRem(BigInteger dividend, BigInteger divisor, out BigInteger remainder) => BigInteger.DivRem(dividend, divisor, out remainder); + + [StaticExtensionMethod, PythonHidden] + public static BigInteger GreatestCommonDivisor(BigInteger left, BigInteger right) => BigInteger.GreatestCommonDivisor(left, right); + + #endregion + + #endregion } public static partial class Int64Ops { diff --git a/Tests/test_cliclass.py b/Tests/test_cliclass.py index 04ce4e555..271a8ce8f 100644 --- a/Tests/test_cliclass.py +++ b/Tests/test_cliclass.py @@ -1420,6 +1420,71 @@ def test_clr_dir(self): self.assertTrue('IndexOf' not in clr.Dir('abc')) self.assertTrue('IndexOf' in clr.DirClr('abc')) + def test_int32_bigint_equivalence(self): + import math + + # properties + for i in range(-10, 10): + bi = big(i) + self.assertEqual(i.IsEven, bi.IsEven) + self.assertEqual(i.IsOne, bi.IsOne) + self.assertEqual(i.IsPowerOfTwo, bi.IsPowerOfTwo) + self.assertEqual(i.IsZero, bi.IsZero) + self.assertEqual(i.Sign, bi.Sign) + # static properties + self.assertEqual(i.Zero, bi.Zero) + self.assertEqual(i.One, bi.One) + self.assertEqual(i.MinusOne, bi.MinusOne) + + 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] + # methods + for i in test_values: + for i2 in [i, -i]: + ii = int(i2) # convert to Int32 if possible + bi = big(i2) + self.assertEqual(ii.ToByteArray(), bi.ToByteArray()) + if hasattr(ii, 'GetByteCount') and hasattr(bi, 'GetByteCount'): + self.assertEqual(ii.GetByteCount(), bi.GetByteCount()) + if hasattr(ii, 'GetBitLength') and hasattr(bi, 'GetBitLength'): + self.assertEqual(ii.GetBitLength(), bi.GetBitLength()) + + # static methods + for i in test_values: + for i2 in [i, -i]: + ii = int(i2) # convert to Int32 if possible + bi = big(i2) + self.assertEqual((1).Negate(ii), int.Negate(bi)) + self.assertEqual((1).Abs(ii), int.Abs(bi)) + self.assertEqual((1).Pow(ii, 5), int.Pow(bi, 5)) + self.assertEqual((1).ModPow(ii, 5, 3), int.ModPow(bi, 5, 3)) + if ii >= 0: + self.assertEqual((1).Log(ii), int.Log(bi)) + self.assertEqual((1).Log10(ii), int.Log10(bi)) + self.assertEqual((1).Log(ii, 7.2), int.Log(bi, 7.2)) + else: + self.assertTrue(math.isnan((1).Log(ii))) + self.assertTrue(math.isnan(int.Log(bi))) + self.assertTrue(math.isnan((1).Log10(ii))) + self.assertTrue(math.isnan(int.Log10(bi))) + self.assertTrue(math.isnan((1).Log(ii, 7.2))) + self.assertTrue(math.isnan(int.Log(bi, 7.2))) + + for j in test_values: + for j2 in [j, -j]: + jj = int(j2) # convert to Int32 if possible + bj = big(j2) + self.assertEqual((1).Compare(ii, jj), int.Compare(bi, bj)) + self.assertEqual((1).Min(ii, jj), int.Min(bi, bj)) + self.assertEqual((1).Max(ii, jj), int.Max(bi, bj)) + self.assertEqual((1).Add(ii, jj), int.Add(bi, bj)) + self.assertEqual((1).Subtract(ii, jj), int.Subtract(bi, bj)) + self.assertEqual((1).Multiply(ii, jj), int.Multiply(bi, bj)) + self.assertEqual((1).GreatestCommonDivisor(ii, jj), int.GreatestCommonDivisor(bi, bj)) + if jj != 0: + self.assertEqual((1).Divide(ii, jj), int.Divide(bi, bj)) + self.assertEqual((1).DivRem(ii, jj), int.DivRem(bi, bj)) + self.assertEqual((1).Remainder(ii, jj), int.Remainder(bi, bj)) + def test_array_contains(self): if is_mono: # for whatever reason this is defined on Mono System.Array[str].__dict__['__contains__'] diff --git a/Tests/test_int.py b/Tests/test_int.py index ea2936569..8fcbe5404 100644 --- a/Tests/test_int.py +++ b/Tests/test_int.py @@ -3,9 +3,8 @@ # See the LICENSE file in the project root for more information. import sys -import unittest -from iptest import IronPythonTestCase, is_cli, big, myint, skipUnlessIronPython, run_test +from iptest import IronPythonTestCase, is_cli, is_netstandard, is_mono, big, myint, skipUnlessIronPython, run_test class IntNoClrTest(IronPythonTestCase): """Must be run before IntTest because it depends on CLR API not being visible.""" @@ -21,17 +20,21 @@ def test_instance_set(self): j = big(1) from System import Int32 - self.assertSetEqual(set(dir(i)) - set(dir(j)), {'MaxValue', 'MinValue'}) - self.assertSetEqual(set(dir(Int32)) - set(dir(int)), {'MaxValue', 'MinValue'}) - - @unittest.expectedFailure - def test_instance_set_todo(self): - i = 1 - j = big(1) - from System import Int32 - - self.assertSetEqual(set(dir(j)) - set(dir(i)), set()) - self.assertSetEqual(set(dir(int)) - set(dir(Int32)), set()) + if not is_mono: + self.assertSetEqual(set(dir(j)) - set(dir(i)), set()) + self.assertSetEqual(set(dir(int)) - set(dir(Int32)), set()) + else: + self.assertSetEqual(set(dir(j)) - set(dir(i)), {'GetByteCount', 'TryWriteBytes'}) + self.assertSetEqual(set(dir(int)) - set(dir(Int32)), {'GetByteCount', 'TryWriteBytes'}) + + # these two assertions fail on IronPython compiled for .NET Standard + if not is_netstandard: + self.assertSetEqual(set(dir(i)) - set(dir(j)), {'MaxValue', 'MinValue'}) + self.assertSetEqual(set(dir(Int32)) - set(dir(int)), {'MaxValue', 'MinValue'}) + + # weaker assertions that should always hold + self.assertTrue((set(dir(i)) - set(dir(j))).issubset({'MaxValue', 'MinValue', 'GetByteCount', 'TryWriteBytes', 'GetBitLength'})) + self.assertTrue((set(dir(Int32)) - set(dir(int))).issubset({'MaxValue', 'MinValue', 'GetByteCount', 'TryWriteBytes', 'GetBitLength'})) def test_from_bytes(self): self.assertEqual(type(int.from_bytes(b"abc", "big")), int)