Skip to content

Commit

Permalink
sagemathgh-37343: Add simple methods to convert to and from bytes for…
Browse files Browse the repository at this point in the history
… `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: sagemath#37343
Reported by: Giacomo Pope
Reviewer(s): Giacomo Pope, grhkm21, Travis Scrimshaw
  • Loading branch information
Release Manager committed Feb 21, 2024
2 parents 805c152 + 941311b commit fba0587
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 0 deletions.
52 changes: 52 additions & 0 deletions src/sage/rings/finite_rings/element_base.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,29 @@ cdef class FiniteRingElement(CommutativeRingElement):
else:
raise ValueError("unknown algorithm")

def to_bytes(self, byteorder="big"):
"""
Return an array of bytes representing an integer.
Internally relies on the python ``int.to_bytes()`` method.
Length of byte array is determined from the field's order.
INPUT:
- ``byteorder`` -- str (default: ``"big"``); determines the byte order of
``input_bytes``; can only be ``"big"`` or ``"little"``
EXAMPLES::
sage: F = GF(65537)
sage: a = F(8726)
sage: a.to_bytes()
b'\x00"\x16'
sage: a.to_bytes(byteorder="little")
b'\x16"\x00'
"""
length = (self.parent().order().nbits() + 7) // 8
return int(self).to_bytes(length=length, byteorder=byteorder)

cdef class FinitePolyExtElement(FiniteRingElement):
"""
Expand Down Expand Up @@ -1083,6 +1106,35 @@ cdef class FinitePolyExtElement(FiniteRingElement):

integer_representation = deprecated_function_alias(33941, to_integer)

def to_bytes(self, byteorder="big"):
r"""
Return an array of bytes representing an integer.
Internally relies on the python ``int.to_bytes()`` method.
Length of byte array is determined from the field's order.
INPUT:
- ``byteorder`` -- str (default: ``"big"``); determines the byte order of
the output; can only be ``"big"`` or ``"little"``
EXAMPLES::
sage: F.<z5> = GF(3^5)
sage: a = z5^4 + 2*z5^3 + 1
sage: a.to_bytes()
b'\x88'
::
sage: F.<z3> = GF(163^3)
sage: a = 136*z3^2 + 10*z3 + 125
sage: a.to_bytes()
b'7)\xa3'
"""
length = (self.parent().order().nbits() + 7) // 8
return self.to_integer().to_bytes(length=length, byteorder=byteorder)

cdef class Cache_base(SageObject):
cpdef FinitePolyExtElement fetch_int(self, number) noexcept:
r"""
Expand Down
44 changes: 44 additions & 0 deletions src/sage/rings/finite_rings/finite_field_base.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -2117,6 +2117,50 @@ cdef class FiniteField(Field):
return [sum(x * y for x, y in zip(col, basis))
for col in B.columns()]

def from_bytes(self, input_bytes, byteorder="big"):
"""
Return the integer represented by the given array of bytes.
Internally relies on the python ``int.from_bytes()`` method.
INPUT:
- ``input_bytes`` -- a bytes-like object or iterable producing bytes
- ``byteorder`` -- str (default: ``"big"``); determines the byte order of
``input_bytes``; can only be ``"big"`` or ``"little"``
EXAMPLES::
sage: input_bytes = b"some_bytes"
sage: F = GF(2**127 - 1)
sage: F.from_bytes(input_bytes)
545127616933790290830707
sage: a = F.from_bytes(input_bytes, byteorder="little"); a
544943659528996309004147
sage: type(a)
<class 'sage.rings.finite_rings.integer_mod.IntegerMod_gmp'>
::
sage: input_bytes = b"some_bytes"
sage: F_ext = GF(65537**5)
sage: F_ext.from_bytes(input_bytes)
29549*z5^4 + 40876*z5^3 + 52171*z5^2 + 13604*z5 + 20843
sage: F_ext.from_bytes(input_bytes, byteorder="little")
29539*z5^4 + 42728*z5^3 + 47440*z5^2 + 12423*z5 + 27473
TESTS::
sage: fields = [GF(2), GF(3), GF(65537), GF(2^10), GF(163^5)]
sage: for F in fields:
....: for _ in range(1000):
....: a = F.random_element()
....: order = choice(["little", "big"])
....: a_bytes = a.to_bytes(byteorder=order)
....: assert F.from_bytes(a_bytes, byteorder=order) == a
"""
python_int = int.from_bytes(input_bytes, byteorder=byteorder)
return self.from_integer(python_int)

def unpickle_FiniteField_ext(_type, order, variable_name, modulus, kwargs):
r"""
Expand Down
34 changes: 34 additions & 0 deletions src/sage/rings/integer.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -7163,6 +7163,40 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement):
else:
raise ValueError("algorithm must be one of: 'pari' or 'gmp' (alias: 'mpir')")

def to_bytes(self, length=1, byteorder="big", is_signed=False):
r"""
Return an array of bytes representing an integer.
Internally relies on the python ``int.to_bytes()`` method.
INPUT:
- ``length`` -- positive integer (default: ``1``); integer is represented in
``length`` bytes
- ``byteorder`` -- str (default: ``"big"``); determines the byte order of
the output; can only be ``"big"`` or ``"little"``
- ``is_signed`` -- boolean (default: ``False``); determines whether to use two's
compliment to represent the integer
.. TODO::
It should be possible to convert straight from the gmp type in cython.
This could be significantly faster, but I am unsure of the fastest and cleanest
way to do this.
EXAMPLES::
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'
"""
return int(self).to_bytes(length=length, byteorder=byteorder, signed=is_signed)

cdef int mpz_set_str_python(mpz_ptr z, char* s, int base) except -1:
"""
Expand Down
32 changes: 32 additions & 0 deletions src/sage/rings/integer_ring.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1574,6 +1574,38 @@ cdef class IntegerRing_class(PrincipalIdealDomain):
from sage.rings.padics.padic_valuation import pAdicValuation
return pAdicValuation(self, p)

def from_bytes(self, input_bytes, byteorder="big", is_signed=False):
"""
Return the integer represented by the given array of bytes.
Internally relies on the python ``int.from_bytes()`` method.
INPUT:
- ``input_bytes`` -- a bytes-like object or iterable producing bytes
- ``byteorder`` -- str (default: ``"big"``); determines the byte order of
``input_bytes``; can only be ``"big"`` or ``"little"``
- ``is_signed`` -- boolean (default: ``False``); determines whether to use two's
compliment to represent the integer
EXAMPLES::
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'>
"""
python_int = int.from_bytes(input_bytes, byteorder=byteorder, signed=is_signed)
return self(python_int)

ZZ = IntegerRing_class()
Z = ZZ

Expand Down

0 comments on commit fba0587

Please sign in to comment.