Issue #1023290: Added API for the conversion of longs to bytes and vice-versa.

This commit is contained in:
Alexandre Vassalotti 2010-01-09 20:35:09 +00:00
parent a28e7028f9
commit c36c3789de
4 changed files with 497 additions and 0 deletions

View File

@ -457,6 +457,69 @@ Additional Methods on Integer Types
.. versionadded:: 3.1 .. versionadded:: 3.1
.. method:: int.to_bytes(length, byteorder, [\*, signed=False])
Return an array of bytes representing an integer.
>>> (1024).to_bytes(2, byteorder='big')
b'\x04\x00'
>>> (1024).to_bytes(10, byteorder='big')
b'\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00'
>>> (-1024).to_bytes(10, byteorder='big', signed=True)
b'\xff\xff\xff\xff\xff\xff\xff\xff\xfc\x00'
>>> x = 1000
>>> x.to_bytes((x.bit_length() // 8) + 1, byteorder='little')
b'\xe8\x03'
The integer is represented using *length* bytes. An :exc:`OverflowError`
is raised if the integer is not representable with the given number of
bytes.
The *byteorder* argument determines the byte order used to represent the
integer. If *byteorder* is ``"big"``, the most significant byte is at the
beginning of the byte array. If *byteorder* is ``"little"``, the most
significant byte is at the end of the byte array. To request the native
byte order of the host system, use :data:`sys.byteorder` as the byte order
value.
The *signed* argument determines whether two's complement is used to
represent the integer. If *signed* is ``False`` and a negative integer is
given, an :exc:`OverflowError` is raised. The default value for *signed*
is ``False``.
.. versionadded:: 3.2
.. classmethod:: int.from_bytes(bytes, byteorder, [\*, signed=False]])
Return the integer represented by the given array of bytes.
>>> int.from_bytes(b'\x00\x10', byteorder='big')
16
>>> int.from_bytes(b'\x00\x10', byteorder='little')
4096
>>> int.from_bytes(b'\xfc\x00', byteorder='big', signed=True)
-1024
>>> int.from_bytes(b'\xfc\x00', byteorder='big', signed=False)
64512
>>> int.from_bytes([255, 0, 0], byteorder='big')
16711680
The argument *bytes* must either support the buffer protocol or be an
iterable producing bytes. :class:`bytes` and :class:`bytearray` are
examples of built-in objects that support the buffer protocol.
The *byteorder* argument determines the byte order used to represent the
integer. If *byteorder* is ``"big"``, the most significant byte is at the
beginning of the byte array. If *byteorder* is ``"little"``, the most
significant byte is at the end of the byte array. To request the native
byte order of the host system, use :data:`sys.byteorder` as the byte order
value.
The *signed* argument indicates whether two's complement is used to
represent the integer.
.. versionadded:: 3.2
Additional Methods on Float Additional Methods on Float
--------------------------- ---------------------------

View File

@ -4,6 +4,7 @@ import sys
import random import random
import math import math
import array
# Used for lazy formatting of failure messages # Used for lazy formatting of failure messages
class Frm(object): class Frm(object):
@ -900,6 +901,237 @@ class LongTest(unittest.TestCase):
for e in bad_exponents: for e in bad_exponents:
self.assertRaises(TypeError, round, 3, e) self.assertRaises(TypeError, round, 3, e)
def test_to_bytes(self):
def check(tests, byteorder, signed=False):
for test, expected in tests.items():
try:
self.assertEqual(
test.to_bytes(len(expected), byteorder, signed=signed),
expected)
except Exception as err:
raise AssertionError(
"failed to convert {0} with byteorder={1} and signed={2}"
.format(test, byteorder, signed)) from err
# Convert integers to signed big-endian byte arrays.
tests1 = {
0: b'\x00',
1: b'\x01',
-1: b'\xff',
-127: b'\x81',
-128: b'\x80',
-129: b'\xff\x7f',
127: b'\x7f',
129: b'\x00\x81',
-255: b'\xff\x01',
-256: b'\xff\x00',
255: b'\x00\xff',
256: b'\x01\x00',
32767: b'\x7f\xff',
-32768: b'\xff\x80\x00',
65535: b'\x00\xff\xff',
-65536: b'\xff\x00\x00',
-8388608: b'\x80\x00\x00'
}
check(tests1, 'big', signed=True)
# Convert integers to signed little-endian byte arrays.
tests2 = {
0: b'\x00',
1: b'\x01',
-1: b'\xff',
-127: b'\x81',
-128: b'\x80',
-129: b'\x7f\xff',
127: b'\x7f',
129: b'\x81\x00',
-255: b'\x01\xff',
-256: b'\x00\xff',
255: b'\xff\x00',
256: b'\x00\x01',
32767: b'\xff\x7f',
-32768: b'\x00\x80',
65535: b'\xff\xff\x00',
-65536: b'\x00\x00\xff',
-8388608: b'\x00\x00\x80'
}
check(tests2, 'little', signed=True)
# Convert integers to unsigned big-endian byte arrays.
tests3 = {
0: b'\x00',
1: b'\x01',
127: b'\x7f',
128: b'\x80',
255: b'\xff',
256: b'\x01\x00',
32767: b'\x7f\xff',
32768: b'\x80\x00',
65535: b'\xff\xff',
65536: b'\x01\x00\x00'
}
check(tests3, 'big', signed=False)
# Convert integers to unsigned little-endian byte arrays.
tests4 = {
0: b'\x00',
1: b'\x01',
127: b'\x7f',
128: b'\x80',
255: b'\xff',
256: b'\x00\x01',
32767: b'\xff\x7f',
32768: b'\x00\x80',
65535: b'\xff\xff',
65536: b'\x00\x00\x01'
}
check(tests4, 'little', signed=False)
self.assertRaises(OverflowError, (256).to_bytes, 1, 'big', signed=False)
self.assertRaises(OverflowError, (256).to_bytes, 1, 'big', signed=True)
self.assertRaises(OverflowError, (256).to_bytes, 1, 'little', signed=False)
self.assertRaises(OverflowError, (256).to_bytes, 1, 'little', signed=True)
self.assertRaises(OverflowError, (-1).to_bytes, 2, 'big', signed=False),
self.assertRaises(OverflowError, (-1).to_bytes, 2, 'little', signed=False)
self.assertEqual((0).to_bytes(0, 'big'), b'')
self.assertEqual((1).to_bytes(5, 'big'), b'\x00\x00\x00\x00\x01')
self.assertEqual((0).to_bytes(5, 'big'), b'\x00\x00\x00\x00\x00')
self.assertEqual((-1).to_bytes(5, 'big', signed=True),
b'\xff\xff\xff\xff\xff')
self.assertRaises(OverflowError, (1).to_bytes, 0, 'big')
def test_from_bytes(self):
def check(tests, byteorder, signed=False):
for test, expected in tests.items():
try:
self.assertEqual(
int.from_bytes(test, byteorder, signed=signed),
expected)
except Exception as err:
raise AssertionError(
"failed to convert {0} with byteorder={1!r} and signed={2}"
.format(test, byteorder, signed)) from err
# Convert signed big-endian byte arrays to integers.
tests1 = {
b'': 0,
b'\x00': 0,
b'\x00\x00': 0,
b'\x01': 1,
b'\x00\x01': 1,
b'\xff': -1,
b'\xff\xff': -1,
b'\x81': -127,
b'\x80': -128,
b'\xff\x7f': -129,
b'\x7f': 127,
b'\x00\x81': 129,
b'\xff\x01': -255,
b'\xff\x00': -256,
b'\x00\xff': 255,
b'\x01\x00': 256,
b'\x7f\xff': 32767,
b'\x80\x00': -32768,
b'\x00\xff\xff': 65535,
b'\xff\x00\x00': -65536,
b'\x80\x00\x00': -8388608
}
check(tests1, 'big', signed=True)
# Convert signed little-endian byte arrays to integers.
tests2 = {
b'': 0,
b'\x00': 0,
b'\x00\x00': 0,
b'\x01': 1,
b'\x00\x01': 256,
b'\xff': -1,
b'\xff\xff': -1,
b'\x81': -127,
b'\x80': -128,
b'\x7f\xff': -129,
b'\x7f': 127,
b'\x81\x00': 129,
b'\x01\xff': -255,
b'\x00\xff': -256,
b'\xff\x00': 255,
b'\x00\x01': 256,
b'\xff\x7f': 32767,
b'\x00\x80': -32768,
b'\xff\xff\x00': 65535,
b'\x00\x00\xff': -65536,
b'\x00\x00\x80': -8388608
}
check(tests2, 'little', signed=True)
# Convert unsigned big-endian byte arrays to integers.
tests3 = {
b'': 0,
b'\x00': 0,
b'\x01': 1,
b'\x7f': 127,
b'\x80': 128,
b'\xff': 255,
b'\x01\x00': 256,
b'\x7f\xff': 32767,
b'\x80\x00': 32768,
b'\xff\xff': 65535,
b'\x01\x00\x00': 65536,
}
check(tests3, 'big', signed=False)
# Convert integers to unsigned little-endian byte arrays.
tests4 = {
b'': 0,
b'\x00': 0,
b'\x01': 1,
b'\x7f': 127,
b'\x80': 128,
b'\xff': 255,
b'\x00\x01': 256,
b'\xff\x7f': 32767,
b'\x00\x80': 32768,
b'\xff\xff': 65535,
b'\x00\x00\x01': 65536,
}
check(tests4, 'little', signed=False)
class myint(int):
pass
self.assertTrue(type(myint.from_bytes(b'\x00', 'big')) is myint)
self.assertEqual(myint.from_bytes(b'\x01', 'big'), 1)
self.assertTrue(
type(myint.from_bytes(b'\x00', 'big', signed=False)) is myint)
self.assertEqual(myint.from_bytes(b'\x01', 'big', signed=False), 1)
self.assertTrue(type(myint.from_bytes(b'\x00', 'little')) is myint)
self.assertEqual(myint.from_bytes(b'\x01', 'little'), 1)
self.assertTrue(type(myint.from_bytes(
b'\x00', 'little', signed=False)) is myint)
self.assertEqual(myint.from_bytes(b'\x01', 'little', signed=False), 1)
self.assertEqual(
int.from_bytes([255, 0, 0], 'big', signed=True), -65536)
self.assertEqual(
int.from_bytes((255, 0, 0), 'big', signed=True), -65536)
self.assertEqual(int.from_bytes(
bytearray(b'\xff\x00\x00'), 'big', signed=True), -65536)
self.assertEqual(int.from_bytes(
bytearray(b'\xff\x00\x00'), 'big', signed=True), -65536)
self.assertEqual(int.from_bytes(
array.array('B', b'\xff\x00\x00'), 'big', signed=True), -65536)
self.assertEqual(int.from_bytes(
memoryview(b'\xff\x00\x00'), 'big', signed=True), -65536)
self.assertRaises(ValueError, int.from_bytes, [256], 'big')
self.assertRaises(ValueError, int.from_bytes, [0], 'big\x00')
self.assertRaises(ValueError, int.from_bytes, [0], 'little\x00')
self.assertRaises(TypeError, int.from_bytes, "", 'big')
self.assertRaises(TypeError, int.from_bytes, "\x00", 'big')
self.assertRaises(TypeError, int.from_bytes, 0, 'big')
self.assertRaises(TypeError, int.from_bytes, 0, 'big', True)
self.assertRaises(TypeError, myint.from_bytes, "", 'big')
self.assertRaises(TypeError, myint.from_bytes, "\x00", 'big')
self.assertRaises(TypeError, myint.from_bytes, 0, 'big')
self.assertRaises(TypeError, int.from_bytes, 0, 'big', True)
def test_main(): def test_main():

View File

@ -149,6 +149,9 @@ Core and Builtins
- Issue #6687: PyBytes_FromObject() no longer accepts an integer as its - Issue #6687: PyBytes_FromObject() no longer accepts an integer as its
argument to construct a null-initialized bytes object. argument to construct a null-initialized bytes object.
- Issue #1023290: Add from_bytes() and to_bytes() methods to integers.
These methods allow the conversion of integers to bytes, and vice-versa.
C-API C-API
----- -----

View File

@ -4296,6 +4296,201 @@ long_is_finite(PyObject *v)
} }
#endif #endif
PyDoc_STRVAR(long_to_bytes_doc,
"int.to_bytes(length, byteorder, *, signed=False) -> bytes\n\
\n\
Return an array of bytes representing an integer.\n\
\n\
The integer is represented using length bytes. An OverflowError is\n\
raised if the integer is not representable with the given number of\n\
bytes.\n\
\n\
The byteorder argument determines the byte order used to represent the\n\
integer. If byteorder is 'big', the most significant byte is at the\n\
beginning of the byte array. If byteorder is 'little', the most\n\
significant byte is at the end of the byte array. To request the native\n\
byte order of the host system, use `sys.byteorder' as the byte order value.\n\
\n\
The signed keyword-only argument determines whether two's complement is\n\
used to represent the integer. If signed is False and a negative integer\n\
is given, an OverflowError is raised.");
static PyObject *
long_to_bytes(PyLongObject *v, PyObject *args, PyObject *kwds)
{
PyObject *byteorder_str;
PyObject *is_signed_obj = NULL;
Py_ssize_t length;
int little_endian;
int is_signed;
PyObject *bytes;
static PyObject *little_str = NULL, *big_str = NULL;
static char *kwlist[] = {"length", "byteorder", "signed", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "nO|O:to_bytes", kwlist,
&length, &byteorder_str,
&is_signed_obj))
return NULL;
if (args != NULL && Py_SIZE(args) > 2) {
PyErr_SetString(PyExc_TypeError,
"'signed' is a keyword-only argument");
return NULL;
}
if (little_str == NULL) {
little_str = PyUnicode_InternFromString("little");
big_str = PyUnicode_InternFromString("big");
if (little_str == NULL || big_str == NULL)
return NULL;
}
if (PyObject_RichCompareBool(byteorder_str, little_str, Py_EQ))
little_endian = 1;
else if (PyObject_RichCompareBool(byteorder_str, big_str, Py_EQ))
little_endian = 0;
else {
PyErr_SetString(PyExc_ValueError,
"byteorder must be either 'little' or 'big'");
return NULL;
}
if (is_signed_obj != NULL) {
int cmp = PyObject_IsTrue(is_signed_obj);
if (cmp < 0)
return NULL;
is_signed = cmp ? 1 : 0;
}
else {
/* If the signed argument was omitted, use False as the
default. */
is_signed = 0;
}
if (length < 0) {
PyErr_SetString(PyExc_ValueError,
"length argument must be non-negative");
return NULL;
}
bytes = PyBytes_FromStringAndSize(NULL, length);
if (bytes == NULL)
return NULL;
if (_PyLong_AsByteArray(v, (unsigned char *)PyBytes_AS_STRING(bytes),
length, little_endian, is_signed) < 0) {
Py_DECREF(bytes);
return NULL;
}
return bytes;
}
PyDoc_STRVAR(long_from_bytes_doc,
"int.from_bytes(bytes, byteorder, *, signed=False) -> int\n\
\n\
Return the integer represented by the given array of bytes.\n\
\n\
The bytes argument must either support the buffer protocol or be an\n\
iterable object producing bytes. Bytes and bytearray are examples of\n\
built-in objects that support the buffer protocol.\n\
\n\
The byteorder argument determines the byte order used to represent the\n\
integer. If byteorder is 'big', the most significant byte is at the\n\
beginning of the byte array. If byteorder is 'little', the most\n\
significant byte is at the end of the byte array. To request the native\n\
byte order of the host system, use `sys.byteorder' as the byte order value.\n\
\n\
The signed keyword-only argument indicates whether two's complement is\n\
used to represent the integer.");
static PyObject *
long_from_bytes(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
PyObject *byteorder_str;
PyObject *is_signed_obj = NULL;
int little_endian;
int is_signed;
PyObject *obj;
PyObject *bytes;
PyObject *long_obj;
static PyObject *little_str = NULL, *big_str = NULL;
static char *kwlist[] = {"bytes", "byteorder", "signed", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|O:from_bytes", kwlist,
&obj, &byteorder_str,
&is_signed_obj))
return NULL;
if (args != NULL && Py_SIZE(args) > 2) {
PyErr_SetString(PyExc_TypeError,
"'signed' is a keyword-only argument");
return NULL;
}
if (little_str == NULL) {
little_str = PyUnicode_InternFromString("little");
big_str = PyUnicode_InternFromString("big");
if (little_str == NULL || big_str == NULL)
return NULL;
}
if (PyObject_RichCompareBool(byteorder_str, little_str, Py_EQ))
little_endian = 1;
else if (PyObject_RichCompareBool(byteorder_str, big_str, Py_EQ))
little_endian = 0;
else {
PyErr_SetString(PyExc_ValueError,
"byteorder must be either 'little' or 'big'");
return NULL;
}
if (is_signed_obj != NULL) {
int cmp = PyObject_IsTrue(is_signed_obj);
if (cmp < 0)
return NULL;
is_signed = cmp ? 1 : 0;
}
else {
/* If the signed argument was omitted, use False as the
default. */
is_signed = 0;
}
bytes = PyObject_Bytes(obj);
if (bytes == NULL)
return NULL;
long_obj = _PyLong_FromByteArray(
(unsigned char *)PyBytes_AS_STRING(bytes), Py_SIZE(bytes),
little_endian, is_signed);
Py_DECREF(bytes);
/* If from_bytes() was used on subclass, allocate new subclass
* instance, initialize it with decoded long value and return it.
*/
if (type != &PyLong_Type && PyType_IsSubtype(type, &PyLong_Type)) {
PyLongObject *newobj;
int i;
Py_ssize_t n = ABS(Py_SIZE(long_obj));
newobj = (PyLongObject *)type->tp_alloc(type, n);
if (newobj == NULL) {
Py_DECREF(long_obj);
return NULL;
}
assert(PyLong_Check(newobj));
Py_SIZE(newobj) = Py_SIZE(long_obj);
for (i = 0; i < n; i++) {
newobj->ob_digit[i] =
((PyLongObject *)long_obj)->ob_digit[i];
}
Py_DECREF(long_obj);
return (PyObject *)newobj;
}
return long_obj;
}
static PyMethodDef long_methods[] = { static PyMethodDef long_methods[] = {
{"conjugate", (PyCFunction)long_long, METH_NOARGS, {"conjugate", (PyCFunction)long_long, METH_NOARGS,
"Returns self, the complex conjugate of any int."}, "Returns self, the complex conjugate of any int."},
@ -4305,6 +4500,10 @@ static PyMethodDef long_methods[] = {
{"is_finite", (PyCFunction)long_is_finite, METH_NOARGS, {"is_finite", (PyCFunction)long_is_finite, METH_NOARGS,
"Returns always True."}, "Returns always True."},
#endif #endif
{"to_bytes", (PyCFunction)long_to_bytes,
METH_VARARGS|METH_KEYWORDS, long_to_bytes_doc},
{"from_bytes", (PyCFunction)long_from_bytes,
METH_VARARGS|METH_KEYWORDS|METH_CLASS, long_from_bytes_doc},
{"__trunc__", (PyCFunction)long_long, METH_NOARGS, {"__trunc__", (PyCFunction)long_long, METH_NOARGS,
"Truncating an Integral returns itself."}, "Truncating an Integral returns itself."},
{"__floor__", (PyCFunction)long_long, METH_NOARGS, {"__floor__", (PyCFunction)long_long, METH_NOARGS,