From 0eff192d47e37fbdb06f0b63726d89dfd4425805 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 3 Mar 2022 14:07:15 -0800 Subject: [PATCH] support for BigInteger <-> PyInt fixes https://github.com/pythonnet/pythonnet/issues/1642#issuecomment-1058412759 --- CHANGELOG.md | 1 + src/embed_tests/TestConverter.cs | 20 +++++++++++++++ src/embed_tests/TestPyInt.cs | 34 ++++++++++++++++++++++++ src/runtime/Converter.cs | 8 ++++++ src/runtime/PythonTypes/PyInt.cs | 44 +++++++++++++++++++++++++++----- src/runtime/Runtime.cs | 16 ++++++++---- src/runtime/Util/Util.cs | 7 +++++ 7 files changed, 119 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 382f9ab57..c45a031b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - .NET collection types now implement standard Python collection interfaces from `collections.abc`. See [Mixins/collections.py](src/runtime/Mixins/collections.py). - .NET arrays implement Python buffer protocol +- Python integer interoperability with `System.Numerics.BigInteger` - Python.NET will correctly resolve .NET methods, that accept `PyList`, `PyInt`, and other `PyObject` derived types when called from Python. - .NET classes, that have `__call__` method are callable from Python diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index 8f7cd381d..e586eda1b 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Numerics; using NUnit.Framework; @@ -131,6 +132,25 @@ public void ToNullable() Assert.AreEqual(Const, ni); } + [Test] + public void BigIntExplicit() + { + BigInteger val = 42; + var i = new PyInt(val); + var ni = i.As(); + Assert.AreEqual(val, ni); + var nullable = i.As(); + Assert.AreEqual(val, nullable); + } + + [Test] + public void PyIntImplicit() + { + var i = new PyInt(1); + var ni = (PyObject)i.As(); + Assert.AreEqual(i.rawPtr, ni.rawPtr); + } + [Test] public void ToPyList() { diff --git a/src/embed_tests/TestPyInt.cs b/src/embed_tests/TestPyInt.cs index 03a368ed8..822fe0715 100644 --- a/src/embed_tests/TestPyInt.cs +++ b/src/embed_tests/TestPyInt.cs @@ -1,4 +1,8 @@ using System; +using System.Globalization; +using System.Linq; +using System.Numerics; + using NUnit.Framework; using Python.Runtime; @@ -179,5 +183,35 @@ public void TestConvertToInt64() Assert.IsInstanceOf(typeof(long), a.ToInt64()); Assert.AreEqual(val, a.ToInt64()); } + + [Test] + public void ToBigInteger() + { + int[] simpleValues = + { + 0, 1, 2, + 0x10, + 0x123, + 0x1234, + }; + simpleValues = simpleValues.Concat(simpleValues.Select(v => -v)).ToArray(); + + foreach (var val in simpleValues) + { + var pyInt = new PyInt(val); + Assert.AreEqual((BigInteger)val, pyInt.ToBigInteger()); + } + } + + [Test] + public void ToBigIntegerLarge() + { + BigInteger val = BigInteger.Pow(2, 1024) + 3; + var pyInt = new PyInt(val); + Assert.AreEqual(val, pyInt.ToBigInteger()); + val = -val; + pyInt = new PyInt(val); + Assert.AreEqual(val, pyInt.ToBigInteger()); + } } } diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index ff1f01a64..a90f31513 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -455,6 +455,14 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType, } } + if (obType == typeof(System.Numerics.BigInteger) + && Runtime.PyInt_Check(value)) + { + using var pyInt = new PyInt(value); + result = pyInt.ToBigInteger(); + return true; + } + return ToPrimitive(value, obType, out result, setError); } diff --git a/src/runtime/PythonTypes/PyInt.cs b/src/runtime/PythonTypes/PyInt.cs index d503c15f3..3dcc6ddb2 100644 --- a/src/runtime/PythonTypes/PyInt.cs +++ b/src/runtime/PythonTypes/PyInt.cs @@ -1,21 +1,21 @@ using System; +using System.Globalization; +using System.Numerics; using System.Runtime.Serialization; namespace Python.Runtime { /// - /// Represents a Python integer object. See the documentation at - /// PY2: https://docs.python.org/2/c-api/int.html - /// PY3: No equivalent - /// for details. + /// Represents a Python integer object. + /// See the documentation at https://docs.python.org/3/c-api/long.html /// - public class PyInt : PyNumber + public class PyInt : PyNumber, IFormattable { internal PyInt(in StolenReference ptr) : base(ptr) { } - internal PyInt(BorrowedReference reference): base(reference) + internal PyInt(BorrowedReference reference) : base(reference) { if (!Runtime.PyInt_Check(reference)) throw new ArgumentException("object is not an int"); } @@ -135,6 +135,8 @@ public PyInt(string value) : base(Runtime.PyLong_FromString(value, 0).StealOrThr { } + public PyInt(BigInteger value) : this(value.ToString(CultureInfo.InvariantCulture)) { } + protected PyInt(SerializationInfo info, StreamingContext context) : base(info, context) { } @@ -198,5 +200,35 @@ public long ToInt64() } return val.Value; } + + public BigInteger ToBigInteger() + { + using var pyHex = Runtime.HexCallable.Invoke(this); + string hex = pyHex.As(); + int offset = 0; + bool neg = false; + if (hex[0] == '-') + { + offset++; + neg = true; + } + byte[] littleEndianBytes = new byte[(hex.Length - offset + 1) / 2]; + for (; offset < hex.Length; offset++) + { + int littleEndianHexIndex = hex.Length - 1 - offset; + int byteIndex = littleEndianHexIndex / 2; + int isByteTopHalf = littleEndianHexIndex & 1; + int valueShift = isByteTopHalf * 4; + littleEndianBytes[byteIndex] += (byte)(Util.HexToInt(hex[offset]) << valueShift); + } + var result = new BigInteger(littleEndianBytes); + return neg ? -result : result; + } + + public string ToString(string format, IFormatProvider formatProvider) + { + using var _ = Py.GIL(); + return ToBigInteger().ToString(format, formatProvider); + } } } diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index e33c4624c..b48ba92e3 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -179,6 +179,7 @@ internal static void Initialize(bool initSigs = false) clrInterop = GetModuleLazy("clr.interop"); inspect = GetModuleLazy("inspect"); + hexCallable = new(() => new PyString("%x").GetAttr("__mod__")); } static void NewRun() @@ -279,8 +280,9 @@ internal static void Shutdown() Exceptions.Shutdown(); PythonEngine.InteropConfiguration.Dispose(); - DisposeLazyModule(clrInterop); - DisposeLazyModule(inspect); + DisposeLazyObject(clrInterop); + DisposeLazyObject(inspect); + DisposeLazyObject(hexCallable); PyObjectConversions.Reset(); PyGC_Collect(); @@ -352,11 +354,11 @@ static bool TryCollectingGarbage(int runs, bool forceBreakLoops) public static bool TryCollectingGarbage(int runs) => TryCollectingGarbage(runs, forceBreakLoops: false); - static void DisposeLazyModule(Lazy module) + static void DisposeLazyObject(Lazy pyObject) { - if (module.IsValueCreated) + if (pyObject.IsValueCreated) { - module.Value.Dispose(); + pyObject.Value.Dispose(); } } @@ -489,8 +491,12 @@ private static void NullGCHandles(IEnumerable objects) private static Lazy inspect; internal static PyObject InspectModule => inspect.Value; + private static Lazy clrInterop; internal static PyObject InteropModule => clrInterop.Value; + + private static Lazy hexCallable; + internal static PyObject HexCallable => hexCallable.Value; #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. internal static BorrowedReference CLRMetaType => PyCLRMetaType; diff --git a/src/runtime/Util/Util.cs b/src/runtime/Util/Util.cs index f5f0d2957..89f5bdf4c 100644 --- a/src/runtime/Util/Util.cs +++ b/src/runtime/Util/Util.cs @@ -141,6 +141,13 @@ internal static string ReadStringResource(this System.Reflection.Assembly assemb return reader.ReadToEnd(); } + public static int HexToInt(char hex) => hex switch + { + >= '0' and <= '9' => hex - '0', + >= 'a' and <= 'f' => hex - 'a' + 10, + _ => throw new ArgumentOutOfRangeException(nameof(hex)), + }; + public static IEnumerator GetEnumerator(this IEnumerator enumerator) => enumerator; public static IEnumerable WhereNotNull(this IEnumerable source) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy