[pypy-commit] pypy default: merge bigint-with-int-ops
cfbolz
noreply at buildbot.pypy.org
Fri Oct 17 08:53:48 CEST 2014
Author: Carl Friedrich Bolz <cfbolz at gmx.de>
Branch:
Changeset: r73985:d5cec19910a1
Date: 2014-10-17 08:53 +0200
http://bitbucket.org/pypy/pypy/changeset/d5cec19910a1/
Log: merge bigint-with-int-ops
this is only for the rbigint changes, leaving the branch open to at
some point be able to do the objspace stuff
diff --git a/rpython/rlib/rbigint.py b/rpython/rlib/rbigint.py
--- a/rpython/rlib/rbigint.py
+++ b/rpython/rlib/rbigint.py
@@ -44,6 +44,25 @@
MASK = int((1 << SHIFT) - 1)
FLOAT_MULTIPLIER = float(1 << SHIFT)
+# For BIGINT and INT mix.
+#
+# The VALID range of an int is different than a valid range of a bigint of length one.
+# -1 << LONG_BIT is actually TWO digits, because they are stored without the sign.
+if SHIFT == LONG_BIT - 1:
+ MIN_INT_VALUE = -1 << SHIFT
+ def int_in_valid_range(x):
+ if x == MIN_INT_VALUE:
+ return False
+ return True
+else:
+ # Means we don't have INT128 on 64bit.
+ def int_in_valid_range(x):
+ if x > MASK or x < -MASK:
+ return False
+ return True
+
+int_in_valid_range._always_inline_ = True
+
# Debugging digit array access.
#
# False == no checking at all
@@ -485,10 +504,27 @@
i += 1
return True
+ @jit.elidable
+ def int_eq(self, other):
+ """ eq with int """
+
+ if not int_in_valid_range(other):
+ # Fallback to Long.
+ return self.eq(rbigint.fromint(other))
+
+ if self.numdigits() > 1:
+ return False
+
+ return (self.sign * self.digit(0)) == other
+
@jit.look_inside
def ne(self, other):
return not self.eq(other)
+ @jit.look_inside
+ def int_ne(self, other):
+ return not self.int_eq(other)
+
@jit.elidable
def lt(self, other):
if self.sign > other.sign:
@@ -524,18 +560,67 @@
i -= 1
return False
+ @jit.elidable
+ def int_lt(self, other):
+ """ lt where other is an int """
+
+ if not int_in_valid_range(other):
+ # Fallback to Long.
+ return self.lt(rbigint.fromint(other))
+
+ osign = 1
+ if other == 0:
+ osign = 0
+ elif other < 0:
+ osign = -1
+
+ if self.sign > osign:
+ return False
+ elif self.sign < osign:
+ return True
+
+ digits = self.numdigits()
+
+ if digits > 1:
+ if osign == 1:
+ return False
+ else:
+ return True
+
+ d1 = self.sign * self.digit(0)
+ if d1 < other:
+ return True
+ return False
+
@jit.look_inside
def le(self, other):
return not other.lt(self)
@jit.look_inside
+ def int_le(self, other):
+ # Alternative that might be faster, reimplant this. as a check with other + 1. But we got to check for overflow
+ # or reduce valid range.
+
+ if self.int_eq(other):
+ return True
+ return self.int_lt(other)
+
+ @jit.look_inside
def gt(self, other):
return other.lt(self)
@jit.look_inside
+ def int_gt(self, other):
+ return not self.int_le(other)
+
+ @jit.look_inside
def ge(self, other):
return not self.lt(other)
+ @jit.look_inside
+ def int_ge(self, other):
+ return not self.int_lt(other)
+
@jit.elidable
def hash(self):
return _hash(self)
@@ -554,12 +639,31 @@
return result
@jit.elidable
+ def int_add(self, other):
+ if not int_in_valid_range(other):
+ # Fallback to long.
+ return self.add(rbigint.fromint(other))
+ elif self.sign == 0:
+ return rbigint.fromint(other)
+ elif other == 0:
+ return self
+
+ sign = -1 if other < 0 else 1
+ if self.sign == sign:
+ result = _x_int_add(self, other)
+ else:
+ result = _x_int_sub(self, other)
+ result.sign *= -1
+ result.sign *= sign
+ return result
+
+ @jit.elidable
def sub(self, other):
if other.sign == 0:
return self
- if self.sign == 0:
+ elif self.sign == 0:
return rbigint(other._digits[:other.size], -other.sign, other.size)
- if self.sign == other.sign:
+ elif self.sign == other.sign:
result = _x_sub(self, other)
else:
result = _x_add(self, other)
@@ -567,6 +671,22 @@
return result
@jit.elidable
+ def int_sub(self, other):
+ if not int_in_valid_range(other):
+ # Fallback to long.
+ return self.sub(rbigint.fromint(other))
+ elif other == 0:
+ return self
+ elif self.sign == 0:
+ return rbigint.fromint(-other)
+ elif self.sign == (-1 if other < 0 else 1):
+ result = _x_int_sub(self, other)
+ else:
+ result = _x_int_add(self, other)
+ result.sign *= self.sign
+ return result
+
+ @jit.elidable
def mul(self, b):
asize = self.numdigits()
bsize = b.numdigits()
@@ -612,6 +732,37 @@
return result
@jit.elidable
+ def int_mul(self, b):
+ if not int_in_valid_range(b):
+ # Fallback to long.
+ return self.mul(rbigint.fromint(b))
+
+ if self.sign == 0 or b == 0:
+ return NULLRBIGINT
+
+ asize = self.numdigits()
+ digit = abs(b)
+ bsign = -1 if b < 0 else 1
+
+ if digit == 1:
+ return rbigint(self._digits[:self.size], self.sign * bsign, asize)
+ elif asize == 1:
+ res = self.widedigit(0) * digit
+ carry = res >> SHIFT
+ if carry:
+ return rbigint([_store_digit(res & MASK), _store_digit(carry)], self.sign * bsign, 2)
+ else:
+ return rbigint([_store_digit(res & MASK)], self.sign * bsign, 1)
+
+ elif digit & (digit - 1) == 0:
+ result = self.lqshift(ptwotable[digit])
+ else:
+ result = _muladd1(self, digit)
+
+ result.sign = self.sign * bsign
+ return result
+
+ @jit.elidable
def truediv(self, other):
div = _bigint_true_divide(self, other)
return div
@@ -629,7 +780,7 @@
if mod.sign * other.sign == -1:
if div.sign == 0:
return ONENEGATIVERBIGINT
- div = div.sub(ONERBIGINT)
+ div = div.int_sub(1)
return div
@@ -652,7 +803,7 @@
return ONENEGATIVERBIGINT if other.sign == -1 else ONERBIGINT
return NULLRBIGINT
elif digit & (digit - 1) == 0:
- mod = self.and_(rbigint([_store_digit(digit - 1)], 1, 1))
+ mod = self.int_and_(digit - 1)
else:
# Perform
size = self.numdigits() - 1
@@ -675,6 +826,48 @@
return mod
@jit.elidable
+ def int_mod(self, other):
+ if self.sign == 0:
+ return NULLRBIGINT
+
+ elif not int_in_valid_range(other):
+ # Fallback to long.
+ return self.mod(rbigint.fromint(other))
+
+ elif other != 0:
+ digit = abs(other)
+ if digit == 1:
+ return NULLRBIGINT
+ elif digit == 2:
+ modm = self.digit(0) & 1
+ if modm:
+ return ONENEGATIVERBIGINT if other < 0 else ONERBIGINT
+ return NULLRBIGINT
+ elif digit & (digit - 1) == 0:
+ mod = self.int_and_(digit - 1)
+ else:
+ # Perform
+ size = self.numdigits() - 1
+ if size > 0:
+ rem = self.widedigit(size)
+ size -= 1
+ while size >= 0:
+ rem = ((rem << SHIFT) + self.widedigit(size)) % digit
+ size -= 1
+ else:
+ rem = self.digit(0) % digit
+
+ if rem == 0:
+ return NULLRBIGINT
+ mod = rbigint([_store_digit(rem)], -1 if self.sign < 0 else 1, 1)
+ else:
+ raise ZeroDivisionError("long division or modulo by zero")
+
+ if mod.sign * (-1 if other < 0 else 1) == -1:
+ mod = mod.int_add(other)
+ return mod
+
+ @jit.elidable
def divmod(v, w):
"""
The / and % operators are now defined in terms of divmod().
@@ -697,7 +890,7 @@
mod = mod.add(w)
if div.sign == 0:
return ONENEGATIVERBIGINT, mod
- div = div.sub(ONERBIGINT)
+ div = div.int_sub(1)
return div, mod
@jit.elidable
@@ -855,7 +1048,7 @@
if self.sign == 0:
return ONENEGATIVERBIGINT
- ret = self.add(ONERBIGINT)
+ ret = self.int_add(1)
ret.sign = -ret.sign
return ret
@@ -1000,14 +1193,26 @@
return _bitwise(self, '&', other)
@jit.elidable
+ def int_and_(self, other):
+ return _int_bitwise(self, '&', other)
+
+ @jit.elidable
def xor(self, other):
return _bitwise(self, '^', other)
@jit.elidable
+ def int_xor(self, other):
+ return _int_bitwise(self, '^', other)
+
+ @jit.elidable
def or_(self, other):
return _bitwise(self, '|', other)
@jit.elidable
+ def int_or_(self, other):
+ return _int_bitwise(self, '|', other)
+
+ @jit.elidable
def oct(self):
if self.sign == 0:
return '0L'
@@ -1190,6 +1395,25 @@
z._normalize()
return z
+def _x_int_add(a, b):
+ """ Add the absolute values of one bigint and one integer. """
+ size_a = a.numdigits()
+
+ z = rbigint([NULLDIGIT] * (size_a + 1), 1)
+ i = UDIGIT_TYPE(1)
+ carry = a.udigit(0) + abs(b)
+ z.setdigit(0, carry)
+ carry >>= SHIFT
+
+ while i < size_a:
+ carry += a.udigit(i)
+ z.setdigit(i, carry)
+ carry >>= SHIFT
+ i += 1
+ z.setdigit(i, carry)
+ z._normalize()
+ return z
+
def _x_sub(a, b):
""" Subtract the absolute values of two integers. """
@@ -1236,6 +1460,42 @@
z._normalize()
return z
+def _x_int_sub(a, b):
+ """ Subtract the absolute values of two integers. """
+
+ size_a = a.numdigits()
+
+ bdigit = abs(b)
+
+ if size_a == 1:
+ # Find highest digit where a and b differ:
+ adigit = a.digit(0)
+
+ if adigit == bdigit:
+ return NULLRBIGINT
+
+ return rbigint.fromint(adigit - bdigit)
+
+ z = rbigint([NULLDIGIT] * size_a, 1, size_a)
+ i = _load_unsigned_digit(1)
+ # The following assumes unsigned arithmetic
+ # works modulo 2**N for some N>SHIFT.
+ borrow = a.udigit(0) - bdigit
+ z.setdigit(0, borrow)
+ borrow >>= SHIFT
+ #borrow &= 1 # Keep only one sign bit
+
+ while i < size_a:
+ borrow = a.udigit(i) - borrow
+ z.setdigit(i, borrow)
+ borrow >>= SHIFT
+ #borrow &= 1
+ i += 1
+
+ assert borrow == 0
+ z._normalize()
+ return z
+
# A neat little table of power of twos.
ptwotable = {}
for x in range(SHIFT-1):
@@ -2343,6 +2603,89 @@
return z.invert()
_bitwise._annspecialcase_ = "specialize:arg(1)"
+def _int_bitwise(a, op, b): # '&', '|', '^'
+ """ Bitwise and/or/xor operations """
+
+ if not int_in_valid_range(b):
+ # Fallback to long.
+ return _bitwise(a, op, rbigint.fromint(b))
+
+ if a.sign < 0:
+ a = a.invert()
+ maska = MASK
+ else:
+ maska = 0
+ if b < 0:
+ b = ~b
+ maskb = MASK
+ else:
+ maskb = 0
+
+ negz = 0
+ if op == '^':
+ if maska != maskb:
+ maska ^= MASK
+ negz = -1
+ elif op == '&':
+ if maska and maskb:
+ op = '|'
+ maska ^= MASK
+ maskb ^= MASK
+ negz = -1
+ elif op == '|':
+ if maska or maskb:
+ op = '&'
+ maska ^= MASK
+ maskb ^= MASK
+ negz = -1
+
+ # JRH: The original logic here was to allocate the result value (z)
+ # as the longer of the two operands. However, there are some cases
+ # where the result is guaranteed to be shorter than that: AND of two
+ # positives, OR of two negatives: use the shorter number. AND with
+ # mixed signs: use the positive number. OR with mixed signs: use the
+ # negative number. After the transformations above, op will be '&'
+ # iff one of these cases applies, and mask will be non-0 for operands
+ # whose length should be ignored.
+
+ size_a = a.numdigits()
+ if op == '&':
+ if maska:
+ size_z = 1
+ else:
+ if maskb:
+ size_z = size_a
+ else:
+ size_z = 1
+ else:
+ size_z = size_a
+
+ z = rbigint([NULLDIGIT] * size_z, 1, size_z)
+ i = 0
+ while i < size_z:
+ if i < size_a:
+ diga = a.digit(i) ^ maska
+ else:
+ diga = maska
+ if i == 0:
+ digb = b ^ maskb
+ else:
+ digb = maskb
+
+ if op == '&':
+ z.setdigit(i, diga & digb)
+ elif op == '|':
+ z.setdigit(i, diga | digb)
+ elif op == '^':
+ z.setdigit(i, diga ^ digb)
+ i += 1
+
+ z._normalize()
+ if negz == 0:
+ return z
+
+ return z.invert()
+_int_bitwise._annspecialcase_ = "specialize:arg(1)"
ULONGLONG_BOUND = r_ulonglong(1L << (r_longlong.BITS-1))
LONGLONG_MIN = r_longlong(-(1L << (r_longlong.BITS-1)))
diff --git a/rpython/rlib/test/test_rbigint.py b/rpython/rlib/test/test_rbigint.py
--- a/rpython/rlib/test/test_rbigint.py
+++ b/rpython/rlib/test/test_rbigint.py
@@ -2,7 +2,8 @@
import operator
import sys
-from random import random, randint, sample
+import math
+from random import random, randint, sample, seed
import py
@@ -14,6 +15,14 @@
from rpython.rtyper.test.test_llinterp import interpret
from rpython.translator.c.test.test_standalone import StandaloneTests
+long_vals_not_too_big = range(17) + [
+ 37, 50,
+ 127, 128, 129, 511, 512, 513, sys.maxint, sys.maxint + 1,
+ 123456789123456789000000L,
+ ]
+
+long_vals = long_vals_not_too_big + [
+ 1 << 100, 3 ** 10000]
class TestRLong(object):
def test_simple(self):
@@ -43,19 +52,23 @@
assert r2.str() == str(-n)
def test_floordiv(self):
- for op1 in [-12, -2, -1, 1, 2, 50]:
- for op2 in [-4, -2, -1, 1, 2, 8]:
- rl_op1 = rbigint.fromint(op1)
- rl_op2 = rbigint.fromint(op2)
+ for op1 in gen_signs(long_vals):
+ for op2 in gen_signs(long_vals):
+ if not op2:
+ continue
+ rl_op1 = rbigint.fromlong(op1)
+ rl_op2 = rbigint.fromlong(op2)
r1 = rl_op1.floordiv(rl_op2)
r2 = op1 // op2
assert r1.tolong() == r2
def test_truediv(self):
- for op1 in [-12, -2, -1, 1, 2, 50]:
- for op2 in [-4, -2, -1, 1, 2, 8]:
- rl_op1 = rbigint.fromint(op1)
- rl_op2 = rbigint.fromint(op2)
+ for op1 in gen_signs(long_vals_not_too_big):
+ for op2 in gen_signs(long_vals):
+ if not op2:
+ continue
+ rl_op1 = rbigint.fromlong(op1)
+ rl_op2 = rbigint.fromlong(op2)
r1 = rl_op1.truediv(rl_op2)
r2 = op1 / op2
assert r1 == r2
@@ -98,19 +111,29 @@
assert f == -1.7976931348623157e+308 # exactly
def test_mod(self):
- for op1 in [-50, -12, -2, -1, 1, 2, 50, 52]:
- for op2 in [-4, -2, -1, 1, 2, 8]:
- rl_op1 = rbigint.fromint(op1)
- rl_op2 = rbigint.fromint(op2)
+ for op1 in gen_signs(long_vals):
+ for op2 in gen_signs(long_vals):
+ if not op2:
+ continue
+ rl_op1 = rbigint.fromlong(op1)
+ rl_op2 = rbigint.fromlong(op2)
r1 = rl_op1.mod(rl_op2)
r2 = op1 % op2
print op1, op2
assert r1.tolong() == r2
+ def test_int_mod(self):
+ for x in gen_signs(long_vals):
+ for y in gen_signs([1, 2, 4, 8, 8888, sys.maxint, 2 ** 19, 2 ** 18 - 1]):
+ op1 = rbigint.fromlong(x)
+ r1 = op1.int_mod(y)
+ r2 = x % y
+ assert r1.tolong() == r2
+
def test_pow(self):
- for op1 in [-50, -12, -2, -1, 1, 2, 50, 52]:
+ for op1 in gen_signs(long_vals_not_too_big):
for op2 in [0, 1, 2, 8, 9, 10, 11]:
- rl_op1 = rbigint.fromint(op1)
+ rl_op1 = rbigint.fromlong(op1)
rl_op2 = rbigint.fromint(op2)
r1 = rl_op1.pow(rl_op2)
r2 = op1 ** op2
@@ -238,6 +261,13 @@
result = f1.add(f2)
assert result.tolong() == x * i + y * j
+ def test_int_add(self):
+ for x in gen_signs(long_vals):
+ for y in gen_signs([0, 1, 9999, sys.maxint, 2 ** 19, 2 ** 18 - 1]):
+ f1 = rbigint.fromlong(x)
+ result = f1.int_add(y)
+ assert result.tolong() == x + y
+
def test_sub(self):
x = 12378959520302182384345L
y = 88961284756491823819191823L
@@ -248,20 +278,34 @@
result = f1.sub(f2)
assert result.tolong() == x * i - y * j
+ def test_int_sub(self):
+ for x in gen_signs([0, 123456789123456789000000L, 1 << 100, 3 ** 10000]):
+ for y in gen_signs([0, 1, 8888, sys.maxint, 2 ** 19, 2 ** 18 - 1]):
+ f1 = rbigint.fromlong(x)
+ result = f1.int_sub(y)
+ assert result.tolong() == x - y
+
def test_subzz(self):
w_l0 = rbigint.fromint(0)
assert w_l0.sub(w_l0).tolong() == 0
def test_mul(self):
- x = -1238585838347L
- y = 585839391919233L
- f1 = rbigint.fromlong(x)
- f2 = rbigint.fromlong(y)
- result = f1.mul(f2)
- assert result.tolong() == x * y
- # also test a * a, it has special code
- result = f1.mul(f1)
- assert result.tolong() == x * x
+ for x in gen_signs(long_vals):
+ f1 = rbigint.fromlong(x)
+ for y in gen_signs(long_vals_not_too_big):
+ f2 = rbigint.fromlong(y)
+ result = f1.mul(f2)
+ assert result.tolong() == x * y
+ # there's a special case for a is b
+ result = f1.mul(f1)
+ assert result.tolong() == x * x
+
+ def test_int_mul(self):
+ for x in gen_signs([39, 128, 111111111, 123456789123456789000000L, 1 << 100, 3 ** 10000]):
+ for y in gen_signs([0, 1, 8888, sys.maxint, 2 ** 19, 2 ** 18 - 1]):
+ f1 = rbigint.fromlong(x)
+ result = f1.int_mul(y)
+ assert result.tolong() == x * y
def test_tofloat(self):
x = 12345678901234567890L ** 10
@@ -309,6 +353,9 @@
f1 = rbigint.fromfloat(9007199254740991.0)
assert f1.tolong() == 9007199254740991
+ null = rbigint.fromfloat(-0.0)
+ assert null.int_eq(0)
+
def test_eq(self):
x = 5858393919192332223L
y = 585839391919233111223311112332L
@@ -336,6 +383,17 @@
f2 = rbigint.fromlong(y)
assert (x < y) == f1.lt(f2)
+ def test_int_comparison(self):
+ for x in gen_signs(long_vals):
+ for y in gen_signs([0, 1, 0x11111111, 0x11111112, 8888, sys.maxint, 2 ** 19, 2 ** 18 - 1]):
+ f1 = rbigint.fromlong(x)
+ assert (x < y) == f1.int_lt(y)
+ assert (x <= y) == f1.int_le(y)
+ assert (x > y) == f1.int_gt(y)
+ assert (x >= y) == f1.int_ge(y)
+ assert (x == y) == f1.int_eq(y)
+ assert (x != y) == f1.int_ne(y)
+
def test_order(self):
f6 = rbigint.fromint(6)
f7 = rbigint.fromint(7)
@@ -344,6 +402,14 @@
assert (f6.gt(f6), f6.gt(f7), f7.gt(f6)) == (0,0,1)
assert (f6.ge(f6), f6.ge(f7), f7.ge(f6)) == (1,0,1)
+ def test_int_order(self):
+ f6 = rbigint.fromint(6)
+ f7 = rbigint.fromint(7)
+ assert (f6.int_lt(6), f6.int_lt(7), f7.int_lt(6)) == (0,1,0)
+ assert (f6.int_le(6), f6.int_le(7), f7.int_le(6)) == (1,1,0)
+ assert (f6.int_gt(6), f6.int_gt(7), f7.int_gt(6)) == (0,0,1)
+ assert (f6.int_ge(6), f6.int_ge(7), f7.int_ge(6)) == (1,0,1)
+
def test_int_conversion(self):
f1 = rbigint.fromlong(12332)
f2 = rbigint.fromint(12332)
@@ -389,7 +455,6 @@
def test_pow_lll(self):
- return
x = 10L
y = 2L
z = 13L
@@ -514,12 +579,21 @@
res2 = getattr(operator, mod)(x, y)
assert res1 == res2
+ def test_int_bitwise(self):
+ for x in gen_signs([0, 1, 5, 11, 42, 43, 2 ** 30]):
+ for y in gen_signs([0, 1, 5, 11, 42, 43, 3 ** 30, 2 ** 31]):
+ lx = rbigint.fromlong(x)
+ for mod in "xor and_ or_".split():
+ res1 = getattr(lx, 'int_' + mod)(y).tolong()
+ res2 = getattr(operator, mod)(x, y)
+ assert res1 == res2
+
def test_mul_eq_shift(self):
p2 = rbigint.fromlong(1).lshift(63)
f1 = rbigint.fromlong(0).lshift(63)
f2 = rbigint.fromlong(0).mul(p2)
assert f1.eq(f2)
-
+
def test_tostring(self):
z = rbigint.fromlong(0)
assert z.str() == '0'
@@ -534,6 +608,11 @@
assert x.format('.!') == (
'-!....!!..!!..!.!!.!......!...!...!!!........!')
assert x.format('abcdefghijkl', '<<', '>>') == '-<<cakdkgdijffjf>>'
+ x = rbigint.fromlong(-18471379832321000000000000000000000000000000000000000000)
+ assert x.str() == '-18471379832321000000000000000000000000000000000000000000'
+ assert x.repr() == '-18471379832321000000000000000000000000000000000000000000L'
+ assert x.hex() == '-0xc0d9a6f41fbcf1718b618443d45516a051e40000000000L'
+ assert x.oct() == '-014033151572037571705614266060420752125055201217100000000000000L'
def test_format_caching(self):
big = rbigint.fromlong(2 ** 1000)
@@ -579,6 +658,18 @@
assert rbigint.fromlong(x).hash() == rbigint.fromlong(y).hash()
assert rbigint.fromlong(-x).hash() == rbigint.fromlong(-y).hash()
+ def test_log(self):
+ from rpython.rlib.rfloat import ulps_check
+ for op in long_vals:
+ if not op:
+ continue
+ for base in [0, 2, 4, 8, 16, 10, math.e]:
+ l = rbigint.fromlong(op).log(base)
+ if base:
+ assert ulps_check(l, math.log(op, base), 1) is None
+ else:
+ assert ulps_check(l, math.log(op), 1) is None
+
class TestInternalFunctions(object):
def test__inplace_divrem1(self):
# signs are not handled in the helpers!
@@ -834,6 +925,8 @@
assert s == bigint.tobytes(16, byteorder="big", signed=False)
py.test.raises(InvalidEndiannessError, bigint.frombytes, '\xFF', 'foo',
signed=True)
+ bigint = rbigint.frombytes('\x82', byteorder='big', signed=True)
+ assert bigint.tolong() == -126
def test_tobytes(self):
assert rbigint.fromint(0).tobytes(1, 'big', signed=True) == '\x00'
More information about the pypy-commit
mailing list