Skip to content

Commit fba0587

Browse files
author
Release Manager
committed
gh-37343: Add simple methods to convert to and from bytes for `ZZ` and finite fields I often have to work with the conversion of bytes / integers and currently do this by working with `int` types and then casting things to `ZZ` or elements of finite fields. This PR is a small addition to `Integer` and `FiniteField` types which allow this with simply functions which internally convert to / from `int` for the user so that things can be done naively from SageMath types. There's a TODO in the code which acknowledges that a faster method for `to_bytes` might be to work straight from the `gmp` object in cython for the integers, but it wasn't obvious to me how to do this. ### Examples ```py sage: ZZ.from_bytes(b'\x00\x10', byteorder='big') 16 sage: ZZ.from_bytes(b'\x00\x10', byteorder='little') 4096 sage: ZZ.from_bytes(b'\xfc\x00', byteorder='big', is_signed=True) -1024 sage: ZZ.from_bytes(b'\xfc\x00', byteorder='big', is_signed=False) 64512 sage: ZZ.from_bytes([255, 0, 0], byteorder='big') 16711680 sage: type(_) <class 'sage.rings.integer.Integer'> ``` ```py sage: (1024).to_bytes(2, byteorder='big') b'\x04\x00' sage: (1024).to_bytes(10, byteorder='big') b'\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00' sage: (-1024).to_bytes(10, byteorder='big', is_signed=True) b'\xff\xff\xff\xff\xff\xff\xff\xff\xfc\x00' sage: x = 1000 sage: x.to_bytes((x.bit_length() + 7) // 8, byteorder='little') b'\xe8\x03' ``` ```py sage: F = GF(65537) sage: a = F.random_element() sage: a.to_bytes() b'\x00\n\x86' sage: F.from_bytes(_) 2694 sage: a 2694 ``` ```py sage: F = GF(3^10) sage: a = F.random_element(); a z10^8 + z10^7 + 2*z10^5 + 2*z10^4 + 2*z10^3 + z10^2 + z10 + 1 sage: a.to_bytes() b'$\xf7' sage: F.from_bytes(_) z10^8 + z10^7 + 2*z10^5 + 2*z10^4 + 2*z10^3 + z10^2 + z10 + 1 ``` ### 📝 Checklist - [x] The title is concise, informative, and self-explanatory. - [x] The description explains in detail what this PR is about. - [x] I have created tests covering the changes. - [x] I have updated the documentation accordingly. URL: #37343 Reported by: Giacomo Pope Reviewer(s): Giacomo Pope, grhkm21, Travis Scrimshaw
2 parents 805c152 + 941311b commit fba0587

File tree

4 files changed

+162
-0
lines changed

4 files changed

+162
-0
lines changed

src/sage/rings/finite_rings/element_base.pyx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,29 @@ cdef class FiniteRingElement(CommutativeRingElement):
142142
else:
143143
raise ValueError("unknown algorithm")
144144

145+
def to_bytes(self, byteorder="big"):
146+
"""
147+
Return an array of bytes representing an integer.
148+
149+
Internally relies on the python ``int.to_bytes()`` method.
150+
Length of byte array is determined from the field's order.
151+
152+
INPUT:
153+
154+
- ``byteorder`` -- str (default: ``"big"``); determines the byte order of
155+
``input_bytes``; can only be ``"big"`` or ``"little"``
156+
157+
EXAMPLES::
158+
159+
sage: F = GF(65537)
160+
sage: a = F(8726)
161+
sage: a.to_bytes()
162+
b'\x00"\x16'
163+
sage: a.to_bytes(byteorder="little")
164+
b'\x16"\x00'
165+
"""
166+
length = (self.parent().order().nbits() + 7) // 8
167+
return int(self).to_bytes(length=length, byteorder=byteorder)
145168

146169
cdef class FinitePolyExtElement(FiniteRingElement):
147170
"""
@@ -1083,6 +1106,35 @@ cdef class FinitePolyExtElement(FiniteRingElement):
10831106

10841107
integer_representation = deprecated_function_alias(33941, to_integer)
10851108

1109+
def to_bytes(self, byteorder="big"):
1110+
r"""
1111+
Return an array of bytes representing an integer.
1112+
1113+
Internally relies on the python ``int.to_bytes()`` method.
1114+
Length of byte array is determined from the field's order.
1115+
1116+
INPUT:
1117+
1118+
- ``byteorder`` -- str (default: ``"big"``); determines the byte order of
1119+
the output; can only be ``"big"`` or ``"little"``
1120+
1121+
EXAMPLES::
1122+
1123+
sage: F.<z5> = GF(3^5)
1124+
sage: a = z5^4 + 2*z5^3 + 1
1125+
sage: a.to_bytes()
1126+
b'\x88'
1127+
1128+
::
1129+
1130+
sage: F.<z3> = GF(163^3)
1131+
sage: a = 136*z3^2 + 10*z3 + 125
1132+
sage: a.to_bytes()
1133+
b'7)\xa3'
1134+
"""
1135+
length = (self.parent().order().nbits() + 7) // 8
1136+
return self.to_integer().to_bytes(length=length, byteorder=byteorder)
1137+
10861138
cdef class Cache_base(SageObject):
10871139
cpdef FinitePolyExtElement fetch_int(self, number) noexcept:
10881140
r"""

src/sage/rings/finite_rings/finite_field_base.pyx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2117,6 +2117,50 @@ cdef class FiniteField(Field):
21172117
return [sum(x * y for x, y in zip(col, basis))
21182118
for col in B.columns()]
21192119

2120+
def from_bytes(self, input_bytes, byteorder="big"):
2121+
"""
2122+
Return the integer represented by the given array of bytes.
2123+
2124+
Internally relies on the python ``int.from_bytes()`` method.
2125+
2126+
INPUT:
2127+
2128+
- ``input_bytes`` -- a bytes-like object or iterable producing bytes
2129+
- ``byteorder`` -- str (default: ``"big"``); determines the byte order of
2130+
``input_bytes``; can only be ``"big"`` or ``"little"``
2131+
2132+
EXAMPLES::
2133+
2134+
sage: input_bytes = b"some_bytes"
2135+
sage: F = GF(2**127 - 1)
2136+
sage: F.from_bytes(input_bytes)
2137+
545127616933790290830707
2138+
sage: a = F.from_bytes(input_bytes, byteorder="little"); a
2139+
544943659528996309004147
2140+
sage: type(a)
2141+
<class 'sage.rings.finite_rings.integer_mod.IntegerMod_gmp'>
2142+
2143+
::
2144+
2145+
sage: input_bytes = b"some_bytes"
2146+
sage: F_ext = GF(65537**5)
2147+
sage: F_ext.from_bytes(input_bytes)
2148+
29549*z5^4 + 40876*z5^3 + 52171*z5^2 + 13604*z5 + 20843
2149+
sage: F_ext.from_bytes(input_bytes, byteorder="little")
2150+
29539*z5^4 + 42728*z5^3 + 47440*z5^2 + 12423*z5 + 27473
2151+
2152+
TESTS::
2153+
2154+
sage: fields = [GF(2), GF(3), GF(65537), GF(2^10), GF(163^5)]
2155+
sage: for F in fields:
2156+
....: for _ in range(1000):
2157+
....: a = F.random_element()
2158+
....: order = choice(["little", "big"])
2159+
....: a_bytes = a.to_bytes(byteorder=order)
2160+
....: assert F.from_bytes(a_bytes, byteorder=order) == a
2161+
"""
2162+
python_int = int.from_bytes(input_bytes, byteorder=byteorder)
2163+
return self.from_integer(python_int)
21202164

21212165
def unpickle_FiniteField_ext(_type, order, variable_name, modulus, kwargs):
21222166
r"""

src/sage/rings/integer.pyx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7163,6 +7163,40 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement):
71637163
else:
71647164
raise ValueError("algorithm must be one of: 'pari' or 'gmp' (alias: 'mpir')")
71657165

7166+
def to_bytes(self, length=1, byteorder="big", is_signed=False):
7167+
r"""
7168+
Return an array of bytes representing an integer.
7169+
7170+
Internally relies on the python ``int.to_bytes()`` method.
7171+
7172+
INPUT:
7173+
7174+
- ``length`` -- positive integer (default: ``1``); integer is represented in
7175+
``length`` bytes
7176+
- ``byteorder`` -- str (default: ``"big"``); determines the byte order of
7177+
the output; can only be ``"big"`` or ``"little"``
7178+
- ``is_signed`` -- boolean (default: ``False``); determines whether to use two's
7179+
compliment to represent the integer
7180+
7181+
.. TODO::
7182+
7183+
It should be possible to convert straight from the gmp type in cython.
7184+
This could be significantly faster, but I am unsure of the fastest and cleanest
7185+
way to do this.
7186+
7187+
EXAMPLES::
7188+
7189+
sage: (1024).to_bytes(2, byteorder='big')
7190+
b'\x04\x00'
7191+
sage: (1024).to_bytes(10, byteorder='big')
7192+
b'\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00'
7193+
sage: (-1024).to_bytes(10, byteorder='big', is_signed=True)
7194+
b'\xff\xff\xff\xff\xff\xff\xff\xff\xfc\x00'
7195+
sage: x = 1000
7196+
sage: x.to_bytes((x.bit_length() + 7) // 8, byteorder='little')
7197+
b'\xe8\x03'
7198+
"""
7199+
return int(self).to_bytes(length=length, byteorder=byteorder, signed=is_signed)
71667200

71677201
cdef int mpz_set_str_python(mpz_ptr z, char* s, int base) except -1:
71687202
"""

src/sage/rings/integer_ring.pyx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,6 +1574,38 @@ cdef class IntegerRing_class(PrincipalIdealDomain):
15741574
from sage.rings.padics.padic_valuation import pAdicValuation
15751575
return pAdicValuation(self, p)
15761576

1577+
def from_bytes(self, input_bytes, byteorder="big", is_signed=False):
1578+
"""
1579+
Return the integer represented by the given array of bytes.
1580+
1581+
Internally relies on the python ``int.from_bytes()`` method.
1582+
1583+
INPUT:
1584+
1585+
- ``input_bytes`` -- a bytes-like object or iterable producing bytes
1586+
- ``byteorder`` -- str (default: ``"big"``); determines the byte order of
1587+
``input_bytes``; can only be ``"big"`` or ``"little"``
1588+
- ``is_signed`` -- boolean (default: ``False``); determines whether to use two's
1589+
compliment to represent the integer
1590+
1591+
EXAMPLES::
1592+
1593+
sage: ZZ.from_bytes(b'\x00\x10', byteorder='big')
1594+
16
1595+
sage: ZZ.from_bytes(b'\x00\x10', byteorder='little')
1596+
4096
1597+
sage: ZZ.from_bytes(b'\xfc\x00', byteorder='big', is_signed=True)
1598+
-1024
1599+
sage: ZZ.from_bytes(b'\xfc\x00', byteorder='big', is_signed=False)
1600+
64512
1601+
sage: ZZ.from_bytes([255, 0, 0], byteorder='big')
1602+
16711680
1603+
sage: type(_)
1604+
<class 'sage.rings.integer.Integer'>
1605+
"""
1606+
python_int = int.from_bytes(input_bytes, byteorder=byteorder, signed=is_signed)
1607+
return self(python_int)
1608+
15771609
ZZ = IntegerRing_class()
15781610
Z = ZZ
15791611

0 commit comments

Comments
 (0)