Skip to content

Commit 0e57cdd

Browse files
authored
support for BigInteger <-> PyInt (#1710)
fixes #1642 (comment)
1 parent eec30c0 commit 0e57cdd

File tree

7 files changed

+119
-11
lines changed

7 files changed

+119
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
2222
- .NET collection types now implement standard Python collection interfaces from `collections.abc`.
2323
See [Mixins/collections.py](src/runtime/Mixins/collections.py).
2424
- .NET arrays implement Python buffer protocol
25+
- Python integer interoperability with `System.Numerics.BigInteger`
2526
- Python.NET will correctly resolve .NET methods, that accept `PyList`, `PyInt`,
2627
and other `PyObject` derived types when called from Python.
2728
- .NET classes, that have `__call__` method are callable from Python

src/embed_tests/TestConverter.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Numerics;
45

56
using NUnit.Framework;
67

@@ -131,6 +132,25 @@ public void ToNullable()
131132
Assert.AreEqual(Const, ni);
132133
}
133134

135+
[Test]
136+
public void BigIntExplicit()
137+
{
138+
BigInteger val = 42;
139+
var i = new PyInt(val);
140+
var ni = i.As<BigInteger>();
141+
Assert.AreEqual(val, ni);
142+
var nullable = i.As<BigInteger?>();
143+
Assert.AreEqual(val, nullable);
144+
}
145+
146+
[Test]
147+
public void PyIntImplicit()
148+
{
149+
var i = new PyInt(1);
150+
var ni = (PyObject)i.As<object>();
151+
Assert.AreEqual(i.rawPtr, ni.rawPtr);
152+
}
153+
134154
[Test]
135155
public void ToPyList()
136156
{

src/embed_tests/TestPyInt.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
using System;
2+
using System.Globalization;
3+
using System.Linq;
4+
using System.Numerics;
5+
26
using NUnit.Framework;
37
using Python.Runtime;
48

@@ -179,5 +183,35 @@ public void TestConvertToInt64()
179183
Assert.IsInstanceOf(typeof(long), a.ToInt64());
180184
Assert.AreEqual(val, a.ToInt64());
181185
}
186+
187+
[Test]
188+
public void ToBigInteger()
189+
{
190+
int[] simpleValues =
191+
{
192+
0, 1, 2,
193+
0x10,
194+
0x123,
195+
0x1234,
196+
};
197+
simpleValues = simpleValues.Concat(simpleValues.Select(v => -v)).ToArray();
198+
199+
foreach (var val in simpleValues)
200+
{
201+
var pyInt = new PyInt(val);
202+
Assert.AreEqual((BigInteger)val, pyInt.ToBigInteger());
203+
}
204+
}
205+
206+
[Test]
207+
public void ToBigIntegerLarge()
208+
{
209+
BigInteger val = BigInteger.Pow(2, 1024) + 3;
210+
var pyInt = new PyInt(val);
211+
Assert.AreEqual(val, pyInt.ToBigInteger());
212+
val = -val;
213+
pyInt = new PyInt(val);
214+
Assert.AreEqual(val, pyInt.ToBigInteger());
215+
}
182216
}
183217
}

src/runtime/Converter.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,14 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType,
455455
}
456456
}
457457

458+
if (obType == typeof(System.Numerics.BigInteger)
459+
&& Runtime.PyInt_Check(value))
460+
{
461+
using var pyInt = new PyInt(value);
462+
result = pyInt.ToBigInteger();
463+
return true;
464+
}
465+
458466
return ToPrimitive(value, obType, out result, setError);
459467
}
460468

src/runtime/PythonTypes/PyInt.cs

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
using System;
2+
using System.Globalization;
3+
using System.Numerics;
24
using System.Runtime.Serialization;
35

46
namespace Python.Runtime
57
{
68
/// <summary>
7-
/// Represents a Python integer object. See the documentation at
8-
/// PY2: https://docs.python.org/2/c-api/int.html
9-
/// PY3: No equivalent
10-
/// for details.
9+
/// Represents a Python integer object.
10+
/// See the documentation at https://docs.python.org/3/c-api/long.html
1111
/// </summary>
12-
public class PyInt : PyNumber
12+
public class PyInt : PyNumber, IFormattable
1313
{
1414
internal PyInt(in StolenReference ptr) : base(ptr)
1515
{
1616
}
1717

18-
internal PyInt(BorrowedReference reference): base(reference)
18+
internal PyInt(BorrowedReference reference) : base(reference)
1919
{
2020
if (!Runtime.PyInt_Check(reference)) throw new ArgumentException("object is not an int");
2121
}
@@ -135,6 +135,8 @@ public PyInt(string value) : base(Runtime.PyLong_FromString(value, 0).StealOrThr
135135
{
136136
}
137137

138+
public PyInt(BigInteger value) : this(value.ToString(CultureInfo.InvariantCulture)) { }
139+
138140
protected PyInt(SerializationInfo info, StreamingContext context)
139141
: base(info, context) { }
140142

@@ -198,5 +200,35 @@ public long ToInt64()
198200
}
199201
return val.Value;
200202
}
203+
204+
public BigInteger ToBigInteger()
205+
{
206+
using var pyHex = Runtime.HexCallable.Invoke(this);
207+
string hex = pyHex.As<string>();
208+
int offset = 0;
209+
bool neg = false;
210+
if (hex[0] == '-')
211+
{
212+
offset++;
213+
neg = true;
214+
}
215+
byte[] littleEndianBytes = new byte[(hex.Length - offset + 1) / 2];
216+
for (; offset < hex.Length; offset++)
217+
{
218+
int littleEndianHexIndex = hex.Length - 1 - offset;
219+
int byteIndex = littleEndianHexIndex / 2;
220+
int isByteTopHalf = littleEndianHexIndex & 1;
221+
int valueShift = isByteTopHalf * 4;
222+
littleEndianBytes[byteIndex] += (byte)(Util.HexToInt(hex[offset]) << valueShift);
223+
}
224+
var result = new BigInteger(littleEndianBytes);
225+
return neg ? -result : result;
226+
}
227+
228+
public string ToString(string format, IFormatProvider formatProvider)
229+
{
230+
using var _ = Py.GIL();
231+
return ToBigInteger().ToString(format, formatProvider);
232+
}
201233
}
202234
}

src/runtime/Runtime.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ internal static void Initialize(bool initSigs = false)
179179

180180
clrInterop = GetModuleLazy("clr.interop");
181181
inspect = GetModuleLazy("inspect");
182+
hexCallable = new(() => new PyString("%x").GetAttr("__mod__"));
182183
}
183184

184185
static void NewRun()
@@ -279,8 +280,9 @@ internal static void Shutdown()
279280

280281
Exceptions.Shutdown();
281282
PythonEngine.InteropConfiguration.Dispose();
282-
DisposeLazyModule(clrInterop);
283-
DisposeLazyModule(inspect);
283+
DisposeLazyObject(clrInterop);
284+
DisposeLazyObject(inspect);
285+
DisposeLazyObject(hexCallable);
284286
PyObjectConversions.Reset();
285287

286288
PyGC_Collect();
@@ -352,11 +354,11 @@ static bool TryCollectingGarbage(int runs, bool forceBreakLoops)
352354
public static bool TryCollectingGarbage(int runs)
353355
=> TryCollectingGarbage(runs, forceBreakLoops: false);
354356

355-
static void DisposeLazyModule(Lazy<PyObject> module)
357+
static void DisposeLazyObject(Lazy<PyObject> pyObject)
356358
{
357-
if (module.IsValueCreated)
359+
if (pyObject.IsValueCreated)
358360
{
359-
module.Value.Dispose();
361+
pyObject.Value.Dispose();
360362
}
361363
}
362364

@@ -489,8 +491,12 @@ private static void NullGCHandles(IEnumerable<IntPtr> objects)
489491

490492
private static Lazy<PyObject> inspect;
491493
internal static PyObject InspectModule => inspect.Value;
494+
492495
private static Lazy<PyObject> clrInterop;
493496
internal static PyObject InteropModule => clrInterop.Value;
497+
498+
private static Lazy<PyObject> hexCallable;
499+
internal static PyObject HexCallable => hexCallable.Value;
494500
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
495501

496502
internal static BorrowedReference CLRMetaType => PyCLRMetaType;

src/runtime/Util/Util.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,13 @@ internal static string ReadStringResource(this System.Reflection.Assembly assemb
141141
return reader.ReadToEnd();
142142
}
143143

144+
public static int HexToInt(char hex) => hex switch
145+
{
146+
>= '0' and <= '9' => hex - '0',
147+
>= 'a' and <= 'f' => hex - 'a' + 10,
148+
_ => throw new ArgumentOutOfRangeException(nameof(hex)),
149+
};
150+
144151
public static IEnumerator<T> GetEnumerator<T>(this IEnumerator<T> enumerator) => enumerator;
145152

146153
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source)

0 commit comments

Comments
 (0)