Author: Armin Rigo <ar...@tunes.org> Branch: Changeset: r78425:d369501b8f6d Date: 2015-07-04 11:19 +0200 http://bitbucket.org/pypy/pypy/changeset/d369501b8f6d/
Log: hg merge int-float-list-strategy Add a list strategy for lists that store both floats and 32-bit integers. The latter are encoded as nonstandard NaNs. Benchmarks show that the speed of such lists is now very close to the speed of purely-int or purely-float lists. diff --git a/pypy/objspace/std/listobject.py b/pypy/objspace/std/listobject.py --- a/pypy/objspace/std/listobject.py +++ b/pypy/objspace/std/listobject.py @@ -14,6 +14,7 @@ from rpython.rlib.listsort import make_timsort_class from rpython.rlib.objectmodel import ( import_from_mixin, instantiate, newlist_hint, resizelist_hint, specialize) +from rpython.rlib import longlong2float from rpython.tool.sourcetools import func_with_new_name from pypy.interpreter.baseobjspace import W_Root @@ -73,33 +74,56 @@ return SizeListStrategy(space, sizehint) return space.fromcache(EmptyListStrategy) - # check for ints - for w_obj in list_w: - if not type(w_obj) is W_IntObject: + w_firstobj = list_w[0] + check_int_or_float = False + + if type(w_firstobj) is W_IntObject: + # check for all-ints + for i in range(1, len(list_w)): + w_obj = list_w[i] + if type(w_obj) is not W_IntObject: + check_int_or_float = (type(w_obj) is W_FloatObject) + break + else: + return space.fromcache(IntegerListStrategy) + + elif type(w_firstobj) is W_BytesObject: + # check for all-strings + for i in range(1, len(list_w)): + if type(list_w[i]) is not W_BytesObject: + break + else: + return space.fromcache(BytesListStrategy) + + elif type(w_firstobj) is W_UnicodeObject: + # check for all-unicodes + for i in range(1, len(list_w)): + if type(list_w[i]) is not W_UnicodeObject: + break + else: + return space.fromcache(UnicodeListStrategy) + + elif type(w_firstobj) is W_FloatObject: + # check for all-floats + for i in range(1, len(list_w)): + w_obj = list_w[i] + if type(w_obj) is not W_FloatObject: + check_int_or_float = (type(w_obj) is W_IntObject) + break + else: + return space.fromcache(FloatListStrategy) + + if check_int_or_float: + for w_obj in list_w: + if type(w_obj) is W_IntObject: + if longlong2float.can_encode_int32(space.int_w(w_obj)): + continue # ok + elif type(w_obj) is W_FloatObject: + if longlong2float.can_encode_float(space.float_w(w_obj)): + continue # ok break - else: - return space.fromcache(IntegerListStrategy) - - # check for strings - for w_obj in list_w: - if not type(w_obj) is W_BytesObject: - break - else: - return space.fromcache(BytesListStrategy) - - # check for unicode - for w_obj in list_w: - if not type(w_obj) is W_UnicodeObject: - break - else: - return space.fromcache(UnicodeListStrategy) - - # check for floats - for w_obj in list_w: - if not type(w_obj) is W_FloatObject: - break - else: - return space.fromcache(FloatListStrategy) + else: + return space.fromcache(IntOrFloatListStrategy) return space.fromcache(ObjectListStrategy) @@ -1382,12 +1406,15 @@ return W_ListObject.from_storage_and_strategy( self.space, storage, self) + def switch_to_next_strategy(self, w_list, w_sample_item): + w_list.switch_to_object_strategy() + def append(self, w_list, w_item): if self.is_correct_type(w_item): self.unerase(w_list.lstorage).append(self.unwrap(w_item)) return - w_list.switch_to_object_strategy() + self.switch_to_next_strategy(w_list, w_item) w_list.append(w_item) def insert(self, w_list, index, w_item): @@ -1397,7 +1424,7 @@ l.insert(index, self.unwrap(w_item)) return - w_list.switch_to_object_strategy() + self.switch_to_next_strategy(w_list, w_item) w_list.insert(index, w_item) def _extend_from_list(self, w_list, w_other): @@ -1421,7 +1448,7 @@ except IndexError: raise else: - w_list.switch_to_object_strategy() + self.switch_to_next_strategy(w_list, w_item) w_list.setitem(index, w_item) def setslice(self, w_list, start, step, slicelength, w_other): @@ -1586,6 +1613,9 @@ def getitems(self, w_list): return self.unerase(w_list.lstorage) + # no sort() method here: W_ListObject.descr_sort() handles this + # case explicitly + class IntegerListStrategy(ListStrategy): import_from_mixin(AbstractUnwrappedStrategy) @@ -1628,6 +1658,11 @@ assert other is not None l += other return + if (w_other.strategy is self.space.fromcache(FloatListStrategy) or + w_other.strategy is self.space.fromcache(IntOrFloatListStrategy)): + if self.switch_to_int_or_float_strategy(w_list): + w_list.extend(w_other) + return return self._base_extend_from_list(w_list, w_other) @@ -1638,8 +1673,46 @@ storage = self.erase(w_other.getitems_int()) w_other = W_ListObject.from_storage_and_strategy( self.space, storage, self) + if (w_other.strategy is self.space.fromcache(FloatListStrategy) or + w_other.strategy is self.space.fromcache(IntOrFloatListStrategy)): + if self.switch_to_int_or_float_strategy(w_list): + w_list.setslice(start, step, slicelength, w_other) + return return self._base_setslice(w_list, start, step, slicelength, w_other) + + @staticmethod + def int_2_float_or_int(w_list): + l = IntegerListStrategy.unerase(w_list.lstorage) + if not longlong2float.CAN_ALWAYS_ENCODE_INT32: + for intval in l: + if not longlong2float.can_encode_int32(intval): + raise ValueError + return [longlong2float.encode_int32_into_longlong_nan(intval) + for intval in l] + + def switch_to_int_or_float_strategy(self, w_list): + try: + generalized_list = self.int_2_float_or_int(w_list) + except ValueError: + return False + strategy = self.space.fromcache(IntOrFloatListStrategy) + w_list.strategy = strategy + w_list.lstorage = strategy.erase(generalized_list) + return True + + def switch_to_next_strategy(self, w_list, w_sample_item): + if type(w_sample_item) is W_FloatObject: + if self.switch_to_int_or_float_strategy(w_list): + # yes, we can switch to IntOrFloatListStrategy + # (ignore here the extremely unlikely case where + # w_sample_item is just the wrong nonstandard NaN float; + # it will caught later and yet another switch will occur) + return + # no, fall back to ObjectListStrategy + w_list.switch_to_object_strategy() + + class FloatListStrategy(ListStrategy): import_from_mixin(AbstractUnwrappedStrategy) @@ -1671,9 +1744,34 @@ def getitems_float(self, w_list): return self.unerase(w_list.lstorage) + + _base_extend_from_list = _extend_from_list + + def _extend_from_list(self, w_list, w_other): + if (w_other.strategy is self.space.fromcache(IntegerListStrategy) or + w_other.strategy is self.space.fromcache(IntOrFloatListStrategy)): + # xxx a case that we don't optimize: [3.4].extend([9999999999999]) + # will cause a switch to int-or-float, followed by another + # switch to object + if self.switch_to_int_or_float_strategy(w_list): + w_list.extend(w_other) + return + return self._base_extend_from_list(w_list, w_other) + + + _base_setslice = setslice + + def setslice(self, w_list, start, step, slicelength, w_other): + if (w_other.strategy is self.space.fromcache(IntegerListStrategy) or + w_other.strategy is self.space.fromcache(IntOrFloatListStrategy)): + if self.switch_to_int_or_float_strategy(w_list): + w_list.setslice(start, step, slicelength, w_other) + return + return self._base_setslice(w_list, start, step, slicelength, w_other) + + def _safe_find(self, w_list, obj, start, stop): from rpython.rlib.rfloat import isnan - from rpython.rlib.longlong2float import float2longlong # l = self.unerase(w_list.lstorage) stop = min(stop, len(l)) @@ -1683,13 +1781,156 @@ if val == obj: return i else: - search = float2longlong(obj) + search = longlong2float.float2longlong(obj) for i in range(start, stop): val = l[i] - if float2longlong(val) == search: + if longlong2float.float2longlong(val) == search: return i raise ValueError + @staticmethod + def float_2_float_or_int(w_list): + l = FloatListStrategy.unerase(w_list.lstorage) + generalized_list = [] + for floatval in l: + if not longlong2float.can_encode_float(floatval): + raise ValueError + generalized_list.append( + longlong2float.float2longlong(floatval)) + return generalized_list + + def switch_to_int_or_float_strategy(self, w_list): + # xxx we should be able to use the same lstorage, but + # there is a typing issue (float vs longlong)... + try: + generalized_list = self.float_2_float_or_int(w_list) + except ValueError: + return False + strategy = self.space.fromcache(IntOrFloatListStrategy) + w_list.strategy = strategy + w_list.lstorage = strategy.erase(generalized_list) + return True + + def switch_to_next_strategy(self, w_list, w_sample_item): + if type(w_sample_item) is W_IntObject: + sample_intval = self.space.int_w(w_sample_item) + if longlong2float.can_encode_int32(sample_intval): + if self.switch_to_int_or_float_strategy(w_list): + # yes, we can switch to IntOrFloatListStrategy + return + # no, fall back to ObjectListStrategy + w_list.switch_to_object_strategy() + + +class IntOrFloatListStrategy(ListStrategy): + import_from_mixin(AbstractUnwrappedStrategy) + + _none_value = longlong2float.float2longlong(0.0) + + def wrap(self, llval): + if longlong2float.is_int32_from_longlong_nan(llval): + intval = longlong2float.decode_int32_from_longlong_nan(llval) + return self.space.wrap(intval) + else: + floatval = longlong2float.longlong2float(llval) + return self.space.wrap(floatval) + + def unwrap(self, w_int_or_float): + if type(w_int_or_float) is W_IntObject: + intval = self.space.int_w(w_int_or_float) + return longlong2float.encode_int32_into_longlong_nan(intval) + else: + floatval = self.space.float_w(w_int_or_float) + return longlong2float.float2longlong(floatval) + + erase, unerase = rerased.new_erasing_pair("longlong") + erase = staticmethod(erase) + unerase = staticmethod(unerase) + + def is_correct_type(self, w_obj): + if type(w_obj) is W_IntObject: + intval = self.space.int_w(w_obj) + return longlong2float.can_encode_int32(intval) + elif type(w_obj) is W_FloatObject: + floatval = self.space.float_w(w_obj) + return longlong2float.can_encode_float(floatval) + else: + return False + + def list_is_correct_type(self, w_list): + return w_list.strategy is self.space.fromcache(IntOrFloatListStrategy) + + def sort(self, w_list, reverse): + l = self.unerase(w_list.lstorage) + sorter = IntOrFloatSort(l, len(l)) + # Reverse sort stability achieved by initially reversing the list, + # applying a stable forward sort, then reversing the final result. + if reverse: + l.reverse() + sorter.sort() + if reverse: + l.reverse() + + _base_extend_from_list = _extend_from_list + + def _extend_longlong(self, w_list, longlong_list): + l = self.unerase(w_list.lstorage) + l += longlong_list + + def _extend_from_list(self, w_list, w_other): + if w_other.strategy is self.space.fromcache(IntegerListStrategy): + try: + longlong_list = IntegerListStrategy.int_2_float_or_int(w_other) + except ValueError: + pass + else: + return self._extend_longlong(w_list, longlong_list) + if w_other.strategy is self.space.fromcache(FloatListStrategy): + try: + longlong_list = FloatListStrategy.float_2_float_or_int(w_other) + except ValueError: + pass + else: + return self._extend_longlong(w_list, longlong_list) + return self._base_extend_from_list(w_list, w_other) + + _base_setslice = setslice + + def _temporary_longlong_list(self, longlong_list): + storage = self.erase(longlong_list) + return W_ListObject.from_storage_and_strategy(self.space, storage, self) + + def setslice(self, w_list, start, step, slicelength, w_other): + if w_other.strategy is self.space.fromcache(IntegerListStrategy): + try: + longlong_list = IntegerListStrategy.int_2_float_or_int(w_other) + except ValueError: + pass + else: + w_other = self._temporary_longlong_list(longlong_list) + elif w_other.strategy is self.space.fromcache(FloatListStrategy): + try: + longlong_list = FloatListStrategy.float_2_float_or_int(w_other) + except ValueError: + pass + else: + w_other = self._temporary_longlong_list(longlong_list) + return self._base_setslice(w_list, start, step, slicelength, w_other) + + def _safe_find(self, w_list, obj, start, stop): + l = self.unerase(w_list.lstorage) + # careful: we must consider that 0.0 == -0.0 == 0, but also + # NaN == NaN if they have the same bit pattern. + fobj = longlong2float.maybe_decode_longlong_as_float(obj) + for i in range(start, min(stop, len(l))): + llval = l[i] + if llval == obj: # equal as longlongs: includes NaN == NaN + return i + fval = longlong2float.maybe_decode_longlong_as_float(llval) + if fval == fobj: # cases like 0.0 == -0.0 or 42 == 42.0 + return i + raise ValueError + class BytesListStrategy(ListStrategy): import_from_mixin(AbstractUnwrappedStrategy) @@ -1786,6 +2027,7 @@ TimSort = make_timsort_class() IntBaseTimSort = make_timsort_class() FloatBaseTimSort = make_timsort_class() +IntOrFloatBaseTimSort = make_timsort_class() StringBaseTimSort = make_timsort_class() UnicodeBaseTimSort = make_timsort_class() @@ -1816,6 +2058,13 @@ return a < b +class IntOrFloatSort(IntOrFloatBaseTimSort): + def lt(self, a, b): + fa = longlong2float.maybe_decode_longlong_as_float(a) + fb = longlong2float.maybe_decode_longlong_as_float(b) + return fa < fb + + class StringSort(StringBaseTimSort): def lt(self, a, b): return a < b diff --git a/pypy/objspace/std/test/test_listobject.py b/pypy/objspace/std/test/test_listobject.py --- a/pypy/objspace/std/test/test_listobject.py +++ b/pypy/objspace/std/test/test_listobject.py @@ -1575,7 +1575,19 @@ L2 = [N, 0.0] # float strategy assert N in L2 assert L2.index(N) == 0 + assert L2.index(-0.0) == 1 assert L2 == [N, -0.0] + # same with the int-or-float list strategy + L3 = [N, 0.0, -0.0, 0] + assert N in L3 + assert L3.index(N) == 0 + for i in [1, 2, 3]: + assert L3[i] == 0 + assert L3[i] == 0.0 + assert L3[i] == -0.0 + assert L3.index(0, i) == i + assert L3.index(0.0, i) == i + assert L3.index(-0.0, i) == i class AppTestListObjectWithRangeList(AppTestListObject): diff --git a/pypy/objspace/std/test/test_liststrategies.py b/pypy/objspace/std/test/test_liststrategies.py --- a/pypy/objspace/std/test/test_liststrategies.py +++ b/pypy/objspace/std/test/test_liststrategies.py @@ -1,8 +1,10 @@ import sys +import py from pypy.objspace.std.listobject import ( W_ListObject, EmptyListStrategy, ObjectListStrategy, IntegerListStrategy, FloatListStrategy, BytesListStrategy, RangeListStrategy, - SimpleRangeListStrategy, make_range_list, UnicodeListStrategy) + SimpleRangeListStrategy, make_range_list, UnicodeListStrategy, + IntOrFloatListStrategy) from pypy.objspace.std import listobject from pypy.objspace.std.test.test_listobject import TestW_ListObject @@ -166,7 +168,6 @@ assert isinstance(l.strategy, IntegerListStrategy) def test_list_empty_after_delete(self): - import py py.test.skip("return to emptyliststrategy is not supported anymore") l = W_ListObject(self.space, [self.space.wrap(3)]) assert isinstance(l.strategy, IntegerListStrategy) @@ -317,7 +318,7 @@ l = W_ListObject(space, [w(1.1), w(2.2), w(3.3)]) assert isinstance(l.strategy, FloatListStrategy) - l.extend(W_ListObject(space, [w(4), w(5), w(6)])) + l.extend(W_ListObject(space, [w("abc"), w("def"), w("ghi")])) assert isinstance(l.strategy, ObjectListStrategy) def test_empty_extend_with_any(self): @@ -733,6 +734,306 @@ list_copy[0] = 42 assert list_orig == [1, 2, 3] + def test_int_or_float_special_nan(self): + from rpython.rlib import longlong2float, rarithmetic + space = self.space + ll = rarithmetic.r_longlong(0xfffffffe12345678 - 2**64) + specialnan = longlong2float.longlong2float(ll) + w_l = W_ListObject(space, [space.wrap(1), space.wrap(specialnan)]) + assert isinstance(w_l.strategy, ObjectListStrategy) + + def test_int_or_float_int_overflow(self): + if sys.maxint == 2147483647: + py.test.skip("only on 64-bit") + space = self.space + ok1 = 2**31 - 1 + ok2 = -2**31 + ovf1 = ok1 + 1 + ovf2 = ok2 - 1 + w_l = W_ListObject(space, [space.wrap(1.2), space.wrap(ovf1)]) + assert isinstance(w_l.strategy, ObjectListStrategy) + w_l = W_ListObject(space, [space.wrap(1.2), space.wrap(ovf2)]) + assert isinstance(w_l.strategy, ObjectListStrategy) + w_l = W_ListObject(space, [space.wrap(1.2), space.wrap(ok1)]) + assert isinstance(w_l.strategy, IntOrFloatListStrategy) + w_l = W_ListObject(space, [space.wrap(1.2), space.wrap(ok2)]) + assert isinstance(w_l.strategy, IntOrFloatListStrategy) + + def test_int_or_float_base(self): + from rpython.rlib.rfloat import INFINITY, NAN + space = self.space + w = space.wrap + w_l = W_ListObject(space, [space.wrap(1), space.wrap(2.3)]) + assert isinstance(w_l.strategy, IntOrFloatListStrategy) + w_l.append(w(int(2**31-1))) + assert isinstance(w_l.strategy, IntOrFloatListStrategy) + w_l.append(w(-5.1)) + assert isinstance(w_l.strategy, IntOrFloatListStrategy) + assert space.int_w(w_l.getitem(2)) == 2**31-1 + assert space.float_w(w_l.getitem(3)) == -5.1 + w_l.append(w(INFINITY)) + assert isinstance(w_l.strategy, IntOrFloatListStrategy) + w_l.append(w(NAN)) + assert isinstance(w_l.strategy, IntOrFloatListStrategy) + w_l.append(w(-NAN)) + assert isinstance(w_l.strategy, IntOrFloatListStrategy) + w_l.append(space.newlist([])) + assert isinstance(w_l.strategy, ObjectListStrategy) + + def test_int_or_float_from_integer(self): + space = self.space + w = space.wrap + w_l = W_ListObject(space, [space.wrap(int(-2**31))]) + assert isinstance(w_l.strategy, IntegerListStrategy) + w_l.append(w(-5.1)) + assert isinstance(w_l.strategy, IntOrFloatListStrategy) + assert space.int_w(w_l.getitem(0)) == -2**31 + assert space.float_w(w_l.getitem(1)) == -5.1 + assert space.len_w(w_l) == 2 + + def test_int_or_float_from_integer_overflow(self): + if sys.maxint == 2147483647: + py.test.skip("only on 64-bit") + space = self.space + w = space.wrap + ovf1 = -2**31 - 1 + w_l = W_ListObject(space, [space.wrap(ovf1)]) + assert isinstance(w_l.strategy, IntegerListStrategy) + w_l.append(w(-5.1)) + assert isinstance(w_l.strategy, ObjectListStrategy) + assert space.int_w(w_l.getitem(0)) == ovf1 + assert space.float_w(w_l.getitem(1)) == -5.1 + assert space.len_w(w_l) == 2 + + def test_int_or_float_from_integer_special_nan(self): + from rpython.rlib import longlong2float, rarithmetic + space = self.space + w = space.wrap + w_l = W_ListObject(space, [space.wrap(int(-2**31))]) + assert isinstance(w_l.strategy, IntegerListStrategy) + ll = rarithmetic.r_longlong(0xfffffffe12345678 - 2**64) + specialnan = longlong2float.longlong2float(ll) + w_l.append(w(specialnan)) + assert isinstance(w_l.strategy, ObjectListStrategy) + assert space.int_w(w_l.getitem(0)) == -2**31 + assert space.len_w(w_l) == 2 + + def test_int_or_float_from_float(self): + space = self.space + w = space.wrap + w_l = W_ListObject(space, [space.wrap(-42.5)]) + assert isinstance(w_l.strategy, FloatListStrategy) + w_l.append(w(-15)) + assert isinstance(w_l.strategy, IntOrFloatListStrategy) + assert space.float_w(w_l.getitem(0)) == -42.5 + assert space.int_w(w_l.getitem(1)) == -15 + assert space.len_w(w_l) == 2 + + def test_int_or_float_from_float_int_overflow(self): + if sys.maxint == 2147483647: + py.test.skip("only on 64-bit") + space = self.space + w = space.wrap + ovf1 = 2 ** 31 + w_l = W_ListObject(space, [space.wrap(1.2)]) + assert isinstance(w_l.strategy, FloatListStrategy) + w_l.append(w(ovf1)) + assert isinstance(w_l.strategy, ObjectListStrategy) + assert space.float_w(w_l.getitem(0)) == 1.2 + assert space.int_w(w_l.getitem(1)) == ovf1 + assert space.len_w(w_l) == 2 + + def test_int_or_float_from_float_special_nan(self): + from rpython.rlib import longlong2float, rarithmetic + space = self.space + w = space.wrap + ll = rarithmetic.r_longlong(0xfffffffe12345678 - 2**64) + specialnan = longlong2float.longlong2float(ll) + w_l = W_ListObject(space, [space.wrap(specialnan)]) + assert isinstance(w_l.strategy, FloatListStrategy) + w_l.append(w(42)) + assert isinstance(w_l.strategy, ObjectListStrategy) + assert space.int_w(w_l.getitem(1)) == 42 + assert space.len_w(w_l) == 2 + + def test_int_or_float_extend(self): + space = self.space + w_l1 = W_ListObject(space, [space.wrap(0), space.wrap(1.2)]) + w_l2 = W_ListObject(space, [space.wrap(3), space.wrap(4.5)]) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert isinstance(w_l2.strategy, IntOrFloatListStrategy) + w_l1.extend(w_l2) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert space.unwrap(w_l1) == [0, 1.2, 3, 4.5] + + def test_int_or_float_extend_mixed_1(self): + space = self.space + w_l1 = W_ListObject(space, [space.wrap(0), space.wrap(1.2)]) + w_l2 = W_ListObject(space, [space.wrap(3)]) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert isinstance(w_l2.strategy, IntegerListStrategy) + w_l1.extend(w_l2) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert [(type(x), x) for x in space.unwrap(w_l1)] == [ + (int, 0), (float, 1.2), (int, 3)] + + def test_int_or_float_extend_mixed_2(self): + space = self.space + w_l1 = W_ListObject(space, [space.wrap(0), space.wrap(1.2)]) + w_l2 = W_ListObject(space, [space.wrap(3.4)]) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert isinstance(w_l2.strategy, FloatListStrategy) + w_l1.extend(w_l2) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert space.unwrap(w_l1) == [0, 1.2, 3.4] + + def test_int_or_float_extend_mixed_3(self): + space = self.space + w_l1 = W_ListObject(space, [space.wrap(0)]) + w_l2 = W_ListObject(space, [space.wrap(3.4)]) + assert isinstance(w_l1.strategy, IntegerListStrategy) + assert isinstance(w_l2.strategy, FloatListStrategy) + w_l1.extend(w_l2) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert space.unwrap(w_l1) == [0, 3.4] + + def test_int_or_float_extend_mixed_4(self): + space = self.space + w_l1 = W_ListObject(space, [space.wrap(0)]) + w_l2 = W_ListObject(space, [space.wrap(3.4), space.wrap(-2)]) + assert isinstance(w_l1.strategy, IntegerListStrategy) + assert isinstance(w_l2.strategy, IntOrFloatListStrategy) + w_l1.extend(w_l2) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert space.unwrap(w_l1) == [0, 3.4, -2] + + def test_int_or_float_extend_mixed_5(self): + space = self.space + w_l1 = W_ListObject(space, [space.wrap(-2.5)]) + w_l2 = W_ListObject(space, [space.wrap(42)]) + assert isinstance(w_l1.strategy, FloatListStrategy) + assert isinstance(w_l2.strategy, IntegerListStrategy) + w_l1.extend(w_l2) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert space.unwrap(w_l1) == [-2.5, 42] + + def test_int_or_float_extend_mixed_5_overflow(self): + if sys.maxint == 2147483647: + py.test.skip("only on 64-bit") + space = self.space + ovf1 = 2 ** 35 + w_l1 = W_ListObject(space, [space.wrap(-2.5)]) + w_l2 = W_ListObject(space, [space.wrap(ovf1)]) + assert isinstance(w_l1.strategy, FloatListStrategy) + assert isinstance(w_l2.strategy, IntegerListStrategy) + w_l1.extend(w_l2) + assert isinstance(w_l1.strategy, ObjectListStrategy) + assert space.unwrap(w_l1) == [-2.5, ovf1] + + def test_int_or_float_extend_mixed_6(self): + space = self.space + w_l1 = W_ListObject(space, [space.wrap(-2.5)]) + w_l2 = W_ListObject(space, [space.wrap(3.4), space.wrap(-2)]) + assert isinstance(w_l1.strategy, FloatListStrategy) + assert isinstance(w_l2.strategy, IntOrFloatListStrategy) + w_l1.extend(w_l2) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert space.unwrap(w_l1) == [-2.5, 3.4, -2] + + def test_int_or_float_setslice(self): + space = self.space + w_l1 = W_ListObject(space, [space.wrap(0), space.wrap(1.2)]) + w_l2 = W_ListObject(space, [space.wrap(3), space.wrap(4.5)]) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert isinstance(w_l2.strategy, IntOrFloatListStrategy) + w_l1.setslice(0, 1, 1, w_l2) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert space.unwrap(w_l1) == [3, 4.5, 1.2] + + def test_int_or_float_setslice_mixed_1(self): + space = self.space + w_l1 = W_ListObject(space, [space.wrap(0), space.wrap(12)]) + w_l2 = W_ListObject(space, [space.wrap(3.2), space.wrap(4.5)]) + assert isinstance(w_l1.strategy, IntegerListStrategy) + assert isinstance(w_l2.strategy, FloatListStrategy) + w_l1.setslice(0, 1, 1, w_l2) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert space.unwrap(w_l1) == [3.2, 4.5, 12] + + def test_int_or_float_setslice_mixed_2(self): + space = self.space + w_l1 = W_ListObject(space, [space.wrap(0), space.wrap(12)]) + w_l2 = W_ListObject(space, [space.wrap(3.2), space.wrap(45)]) + assert isinstance(w_l1.strategy, IntegerListStrategy) + assert isinstance(w_l2.strategy, IntOrFloatListStrategy) + w_l1.setslice(0, 1, 1, w_l2) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert space.unwrap(w_l1) == [3.2, 45, 12] + + def test_int_or_float_setslice_mixed_3(self): + space = self.space + w_l1 = W_ListObject(space, [space.wrap(0.1), space.wrap(1.2)]) + w_l2 = W_ListObject(space, [space.wrap(32), space.wrap(45)]) + assert isinstance(w_l1.strategy, FloatListStrategy) + assert isinstance(w_l2.strategy, IntegerListStrategy) + w_l1.setslice(0, 1, 1, w_l2) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert space.unwrap(w_l1) == [32, 45, 1.2] + + def test_int_or_float_setslice_mixed_4(self): + space = self.space + w_l1 = W_ListObject(space, [space.wrap(0.1), space.wrap(1.2)]) + w_l2 = W_ListObject(space, [space.wrap(3.2), space.wrap(45)]) + assert isinstance(w_l1.strategy, FloatListStrategy) + assert isinstance(w_l2.strategy, IntOrFloatListStrategy) + w_l1.setslice(0, 1, 1, w_l2) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert space.unwrap(w_l1) == [3.2, 45, 1.2] + + def test_int_or_float_setslice_mixed_5(self): + space = self.space + w_l1 = W_ListObject(space, [space.wrap(0), space.wrap(1.2)]) + w_l2 = W_ListObject(space, [space.wrap(32), space.wrap(45)]) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert isinstance(w_l2.strategy, IntegerListStrategy) + w_l1.setslice(0, 1, 1, w_l2) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert space.unwrap(w_l1) == [32, 45, 1.2] + + def test_int_or_float_setslice_mixed_5_overflow(self): + if sys.maxint == 2147483647: + py.test.skip("only on 64-bit") + space = self.space + ovf1 = 2 ** 35 + w_l1 = W_ListObject(space, [space.wrap(0), space.wrap(1.2)]) + w_l2 = W_ListObject(space, [space.wrap(32), space.wrap(ovf1)]) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert isinstance(w_l2.strategy, IntegerListStrategy) + w_l1.setslice(0, 1, 1, w_l2) + assert isinstance(w_l1.strategy, ObjectListStrategy) + assert space.unwrap(w_l1) == [32, ovf1, 1.2] + + def test_int_or_float_setslice_mixed_6(self): + space = self.space + w_l1 = W_ListObject(space, [space.wrap(0), space.wrap(1.2)]) + w_l2 = W_ListObject(space, [space.wrap(3.2), space.wrap(4.5)]) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert isinstance(w_l2.strategy, FloatListStrategy) + w_l1.setslice(0, 1, 1, w_l2) + assert isinstance(w_l1.strategy, IntOrFloatListStrategy) + assert space.unwrap(w_l1) == [3.2, 4.5, 1.2] + + def test_int_or_float_sort(self): + space = self.space + w_l = W_ListObject(space, [space.wrap(1.2), space.wrap(1), + space.wrap(1.0), space.wrap(5)]) + w_l.sort(False) + assert [(type(x), x) for x in space.unwrap(w_l)] == [ + (int, 1), (float, 1.0), (float, 1.2), (int, 5)] + w_l.sort(True) + assert [(type(x), x) for x in space.unwrap(w_l)] == [ + (int, 5), (float, 1.2), (int, 1), (float, 1.0)] + class TestW_ListStrategiesDisabled: spaceconfig = {"objspace.std.withliststrategies": False} diff --git a/rpython/rlib/longlong2float.py b/rpython/rlib/longlong2float.py --- a/rpython/rlib/longlong2float.py +++ b/rpython/rlib/longlong2float.py @@ -7,8 +7,9 @@ """ from __future__ import with_statement +import sys from rpython.annotator import model as annmodel -from rpython.rlib.rarithmetic import r_int64 +from rpython.rlib.rarithmetic import r_int64, intmask from rpython.rtyper.lltypesystem import lltype, rffi from rpython.rtyper.extregistry import ExtRegistryEntry from rpython.translator.tool.cbuild import ExternalCompilationInfo @@ -99,3 +100,46 @@ [v_longlong] = hop.inputargs(lltype.SignedLongLong) hop.exception_cannot_occur() return hop.genop("convert_longlong_bytes_to_float", [v_longlong], resulttype=lltype.Float) + +# ____________________________________________________________ + + +# For encoding integers inside nonstandard NaN bit patterns. +# ff ff ff fe xx xx xx xx (signed 32-bit int) +nan_high_word_int32 = -2 # -2 == (int)0xfffffffe +nan_encoded_zero = r_int64(nan_high_word_int32 << 32) + +def encode_int32_into_longlong_nan(value): + return (nan_encoded_zero + + rffi.cast(rffi.LONGLONG, rffi.cast(rffi.UINT, value))) + +def decode_int32_from_longlong_nan(value): + return rffi.cast(lltype.Signed, rffi.cast(rffi.INT, value)) + +def is_int32_from_longlong_nan(value): + return intmask(value >> 32) == nan_high_word_int32 + +CAN_ALWAYS_ENCODE_INT32 = (sys.maxint == 2147483647) + +def can_encode_int32(value): + if CAN_ALWAYS_ENCODE_INT32: + return True + return value == rffi.cast(lltype.Signed, rffi.cast(rffi.INT, value)) + +def can_encode_float(value): + return intmask(float2longlong(value) >> 32) != nan_high_word_int32 + +def maybe_decode_longlong_as_float(value): + # Decode a longlong value. If a float, just return it as a float. + # If an encoded integer, return a float that has the (exact) same + # value. This relies on the fact that casting from a decoded int + # to a float is an exact operation. If we had full 64-bit + # integers, this cast would loose precision. But this works + # because the integers are only 32-bit. This would also work even + # if we encoded larger integers: as long as they are encoded + # inside a subset of the mantissa of a float, then the + # cast-to-float will be exact. + if is_int32_from_longlong_nan(value): + return float(decode_int32_from_longlong_nan(value)) + else: + return longlong2float(value) diff --git a/rpython/rlib/test/test_longlong2float.py b/rpython/rlib/test/test_longlong2float.py --- a/rpython/rlib/test/test_longlong2float.py +++ b/rpython/rlib/test/test_longlong2float.py @@ -1,7 +1,7 @@ from rpython.translator.c.test.test_genc import compile from rpython.rlib.longlong2float import longlong2float, float2longlong from rpython.rlib.longlong2float import uint2singlefloat, singlefloat2uint -from rpython.rlib.rarithmetic import r_singlefloat +from rpython.rlib.rarithmetic import r_singlefloat, r_longlong from rpython.rtyper.test.test_llinterp import interpret @@ -21,6 +21,9 @@ yield -inf yield inf / inf # nan +def test_float2longlong(): + assert float2longlong(0.0) == r_longlong(0) + def test_longlong_as_float(): for x in enum_floats(): res = fn(x) @@ -63,3 +66,32 @@ for x in enum_floats(): res = fn2(x) assert repr(res) == repr(float(r_singlefloat(x))) + +# ____________________________________________________________ + +def fn_encode_nan(f1, i2): + from rpython.rlib.longlong2float import can_encode_float, can_encode_int32 + from rpython.rlib.longlong2float import encode_int32_into_longlong_nan + from rpython.rlib.longlong2float import decode_int32_from_longlong_nan + from rpython.rlib.longlong2float import is_int32_from_longlong_nan + from rpython.rlib.rfloat import isnan + assert can_encode_float(f1) + assert can_encode_int32(i2) + l1 = float2longlong(f1) + l2 = encode_int32_into_longlong_nan(i2) + assert not is_int32_from_longlong_nan(l1) + assert is_int32_from_longlong_nan(l2) + f1b = longlong2float(l1) + assert f1b == f1 or (isnan(f1b) and isnan(f1)) + assert decode_int32_from_longlong_nan(l2) == i2 + return 42 + +def test_compiled_encode_nan(): + fn2 = compile(fn_encode_nan, [float, int]) + ints = [-2**31, 2**31-1, 42] + for x in enum_floats(): + y = ints.pop() + ints.insert(0, y) + fn_encode_nan(x, y) + res = fn2(x, y) + assert res == 42 _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit