Commits

Amaury Forgeot d'Arc committed 5e18178

Backport frombytes() and tobytes() from the py3k branch.

Comments (0)

Files changed (2)

pypy/rlib/rbigint.py

 from pypy.rlib.rarithmetic import ovfcheck, r_longlong, widen, is_valid_int
 from pypy.rlib.rarithmetic import most_neg_value_of_same_type
 from pypy.rlib.rfloat import isinf, isnan
+from pypy.rlib.rstring import StringBuilder
 from pypy.rlib.debug import make_sure_not_resized, check_regular_int
 from pypy.rlib.objectmodel import we_are_translated, specialize
 from pypy.rlib import jit
 import math, sys
 
 SUPPORT_INT128 = hasattr(rffi, '__INT128_T')
+BYTEORDER = sys.byteorder
 
 # note about digit sizes:
 # In division, the native integer type must be able to hold
         assert type(x) is type(NULLDIGIT)
         assert UDIGIT_MASK(x) & MASK == UDIGIT_MASK(x)
             
+class InvalidEndiannessError(Exception):
+    pass
+
+class InvalidSignednessError(Exception):
+    pass
+
 class Entry(extregistry.ExtRegistryEntry):
     _about_ = _check_digits
     def compute_result_annotation(self, s_list):
         # then modify the result.
         return _decimalstr_to_bigint(s)
 
+    @staticmethod
+    def frombytes(s, byteorder, signed):
+        if byteorder not in ('big', 'little'):
+            raise InvalidEndiannessError()
+
+        if byteorder != BYTEORDER:
+            msb = ord(s[0])
+            itr = range(len(s)-1, -1, -1)
+        else:
+            msb = ord(s[-1])
+            itr = range(0, len(s))
+
+        sign = -1 if msb >= 0x80 and signed else 1
+        accum = _widen_digit(0)
+        accumbits = 0
+        digits = []
+        carry = 1
+
+        for i in itr:
+            c = _widen_digit(ord(s[i]))
+            if sign == -1:
+                c = (0xFF ^ c) + carry
+                carry = c >> 8
+                c &= 0xFF
+
+            accum |= c << accumbits
+            accumbits += 8
+            if accumbits >= SHIFT:
+                digits.append(_store_digit(intmask(accum & MASK)))
+                accum >>= SHIFT
+                accumbits -= SHIFT
+
+        if accumbits:
+            digits.append(_store_digit(intmask(accum)))
+        result = rbigint(digits[:], sign)
+        result._normalize()
+        return result
+
+    @jit.elidable
+    def tobytes(self, nbytes, byteorder, signed):
+        if byteorder not in ('big', 'little'):
+            raise InvalidEndiannessError()
+        if not signed and self.sign == -1:
+            raise InvalidSignednessError()
+
+        bswap = byteorder != BYTEORDER
+        d = _widen_digit(0)
+        j = 0
+        imax = self.numdigits()
+        accum = _widen_digit(0)
+        accumbits = 0
+        result = StringBuilder(nbytes)
+        carry = 1
+
+        for i in range(0, imax):
+            d = self.widedigit(i)
+            if self.sign == -1:
+                d = (d ^ MASK) + carry
+                carry = d >> SHIFT
+                d &= MASK
+
+            accum |= d << accumbits
+            if i == imax - 1:
+                # Avoid bogus 0's
+                s = d ^ MASK if self.sign == -1 else d
+                while s:
+                    s >>=1
+                    accumbits += 1
+            else:
+                accumbits += SHIFT
+
+            while accumbits >= 8:
+                if j >= nbytes:
+                    raise OverflowError()
+                j += 1
+
+                result.append(chr(accum & 0xFF))
+                accum >>= 8
+                accumbits -= 8
+
+        if accumbits:
+            if j >= nbytes:
+                raise OverflowError()
+            j += 1
+
+            if self.sign == -1:
+                # Add a sign bit
+                accum |= (~_widen_digit(0)) << accumbits;
+
+            result.append(chr(accum & 0xFF))
+
+        if j < nbytes:
+            signbyte = 0xFF if self.sign == -1 else 0
+            result.append_multiple_char(chr(signbyte), nbytes - j)
+
+        digits = result.build()
+
+        if j == nbytes and nbytes > 0 and signed:
+            # If not already set, we cannot contain the sign bit
+            msb = digits[-1]
+            if (self.sign == -1) != (ord(msb) >= 0x80):
+                raise OverflowError()
+
+        if bswap:
+            # Bah, this is very inefficient. At least it's not
+            # quadratic.
+            length = len(digits)
+            if length >= 0:
+                digits = ''.join([digits[i] for i in range(length-1, -1, -1)])
+        return digits
+
     @jit.elidable
     def toint(self):
         """

pypy/rlib/test/test_rbigint.py

 from pypy.rlib import rbigint as lobj
 from pypy.rlib.rarithmetic import r_uint, r_longlong, r_ulonglong, intmask
 from pypy.rlib.rbigint import (rbigint, SHIFT, MASK, KARATSUBA_CUTOFF,
-    _store_digit, _mask_digit)
+    _store_digit, _mask_digit, InvalidEndiannessError, InvalidSignednessError)
 from pypy.rlib.rfloat import NAN
 from pypy.rpython.test.test_llinterp import interpret
 
 
         res = interpret(fn, [])
         assert res == -42.0
+
+    def test_frombytes(self):
+        s = "\xFF\x12\x34\x56"
+        bigint = rbigint.frombytes(s, byteorder="big", signed=False)
+        assert bigint.tolong() == 0xFF123456
+        bigint = rbigint.frombytes(s, byteorder="little", signed=False)
+        assert bigint.tolong() == 0x563412FF
+        s = "\xFF\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\xFF"
+        bigint = rbigint.frombytes(s, byteorder="big", signed=False)
+        assert s == bigint.tobytes(16, byteorder="big", signed=False)
+        raises(InvalidEndiannessError, bigint.frombytes, '\xFF', 'foo',
+               signed=True)
+
+    def test_tobytes(self):
+        assert rbigint.fromint(0).tobytes(1, 'big', signed=True) == '\x00'
+        assert rbigint.fromint(1).tobytes(2, 'big', signed=True) == '\x00\x01'
+        raises(OverflowError, rbigint.fromint(255).tobytes, 1, 'big', signed=True)
+        assert rbigint.fromint(-129).tobytes(2, 'big', signed=True) == '\xff\x7f'
+        assert rbigint.fromint(-129).tobytes(2, 'little', signed=True) == '\x7f\xff'
+        assert rbigint.fromint(65535).tobytes(3, 'big', signed=True) == '\x00\xff\xff'
+        assert rbigint.fromint(-65536).tobytes(3, 'little', signed=True) == '\x00\x00\xff'
+        assert rbigint.fromint(65535).tobytes(2, 'big', signed=False) == '\xff\xff'
+        assert rbigint.fromint(-8388608).tobytes(3, 'little', signed=True) == '\x00\x00\x80'
+        i = rbigint.fromint(-8388608)
+        raises(InvalidEndiannessError, i.tobytes, 3, 'foo', signed=True)
+        raises(InvalidSignednessError, i.tobytes, 3, 'little', signed=False)
+        raises(OverflowError, i.tobytes, 2, 'little', signed=True)
+