Commits

Amaury Forgeot d'Arc committed 46b6229

More constructors to Decimal, add __str__

  • Participants
  • Parent commits 563cb51
  • Branches decimal-libmpdec

Comments (0)

Files changed (7)

File pypy/interpreter/error.py

         self._value = value
         self.setup(w_type)
 
-    def get_w_value(self, space):
-        w_value = self._w_value
-        if w_value is None:
-            self._w_value = w_value = space.wrap(self._value)
-        return w_value
+    def _compute_value(self, space):
+        return self._value
 
 @specialize.memo()
 def get_operr_class(valuefmt):

File pypy/module/_decimal/__init__.py

         'Context': 'interp_context.W_Context',
         'getcontext': 'interp_context.getcontext',
         'setcontext': 'interp_context.setcontext',
+        'DecimalException': 'interp_signals.get(space).w_DecimalException',
 
         'IEEE_CONTEXT_MAX_BITS': 'space.wrap(interp_decimal.IEEE_CONTEXT_MAX_BITS)',
         'MAX_PREC': 'space.wrap(interp_decimal.MAX_PREC)',
         }
     for name in rmpdec.ROUND_CONSTANTS:
         interpleveldefs[name] = 'space.wrap(%r)' % name
-    for name in interp_signals.SIGNAL_NAMES:
+    for name, flag in interp_signals.SIGNAL_MAP:
         interpleveldefs[name] = 'interp_signals.get(space).w_%s' % name
         

File pypy/module/_decimal/interp_context.py

 from rpython.rlib import rmpdec
 from rpython.rlib.unroll import unrolling_iterable
 from rpython.rtyper.lltypesystem import rffi, lltype
-from pypy.interpreter.error import oefmt
+from pypy.interpreter.error import oefmt, OperationError
 from pypy.interpreter.baseobjspace import W_Root
 from pypy.interpreter.gateway import interp2app, unwrap_spec
 from pypy.interpreter.typedef import (
                                  zero=True,
                                  track_allocation=False)
         self.w_flags = space.call_function(state_get(space).W_SignalDict)
+        self.capitals = 0
 
     def __del__(self):
         if self.ctx:
 
     def addstatus(self, space, status):
         "Add resulting status to context, and eventually raise an exception."
-        self.ctx.c_status |= status
-        if status & rmpdec.MPD_Malloc_error:
+        new_status = (rffi.cast(lltype.Signed, status) |
+                      rffi.cast(lltype.Signed, self.ctx.c_status))
+        self.ctx.c_status = rffi.cast(rffi.UINT, new_status)
+        if new_status & rmpdec.MPD_Malloc_error:
             raise OperationError(space.w_MemoryError, space.w_None)
-        if status & self.ctx.c_traps:
-            raise interp_signals.flags_as_exception(
-                space, self.ctx.c_traps & status)
+        to_trap = (rffi.cast(lltype.Signed, status) &
+                   rffi.cast(lltype.Signed, self.ctx.c_traps))
+        if to_trap:
+            raise interp_signals.flags_as_exception(space, to_trap)
 
     def copy_w(self, space):
         w_copy = W_Context(space)
 
 def setcontext(space, w_context):
     ec = space.getexecutioncontext()
-    ec.decimal_context = w_context
+    ec.decimal_context = space.interp_w(W_Context, w_context)
+
+def ensure_context(space, w_context):
+    context = space.interp_w(W_Context, w_context,
+                             can_be_None=True)
+    if context is None:
+        context = getcontext(space)
+    return context
 
 class ConvContext:
-    def __init__(self, context, exact):
+    def __init__(self, space, mpd, context, exact):
+        self.space = space
+        self.mpd = mpd
+        self.context = context
         self.exact = exact
+
+    def __enter__(self):
         if self.exact:
             self.ctx = lltype.malloc(rmpdec.MPD_CONTEXT_PTR.TO, flavor='raw',
                                      zero=True)
             rmpdec.mpd_maxcontext(self.ctx)
         else:
-            self.ctx = context.ctx
-
-    def __enter__(self):
-        return self.ctx
+            self.ctx = self.context.ctx
+        self.status_ptr = lltype.malloc(rffi.CArrayPtr(rffi.UINT).TO, 1,
+                                        flavor='raw', zero=True)
+        return self.ctx, self.status_ptr
 
     def __exit__(self, *args):
         if self.exact:
             lltype.free(self.ctx, flavor='raw')
+            # we want exact results
+            status = rffi.cast(lltype.Signed, self.status_ptr[0])
+            if status & (rmpdec.MPD_Inexact |
+                         rmpdec.MPD_Rounded |
+                         rmpdec.MPD_Clamped):
+                rmpdec.seterror(self.mpd,
+                                rmpdec.MPD_Invalid_operation, status_ptr)
+        status = rffi.cast(lltype.Signed, self.status_ptr[0])
+        lltype.free(self.status_ptr, flavor='raw')
+        status &= rmpdec.MPD_Errors
+        # May raise a DecimalException
+        self.context.addstatus(self.space, status)

File pypy/module/_decimal/interp_decimal.py

-from rpython.rlib import rmpdec
+from rpython.rlib import rmpdec, rarithmetic, rbigint
 from rpython.rtyper.lltypesystem import rffi, lltype
 from pypy.interpreter.baseobjspace import W_Root
+from pypy.interpreter.error import oefmt, OperationError
 from pypy.interpreter.gateway import interp2app, unwrap_spec, WrappedDefault
 from pypy.interpreter.typedef import (TypeDef, GetSetProperty, descr_get_dict,
     descr_set_dict, descr_del_dict)
 # DEC_MINALLOC >= MPD_MINALLOC
 DEC_MINALLOC = 4
 
-def ensure_context(space, w_context):
-    context = space.interp_w(interp_context.W_Context, w_context,
-                             can_be_None=True)
-    if context is None:
-        context = interp_context.getcontext(space)
-    return context
-
 class W_Decimal(W_Root):
     hash = -1
 
         if self.data:
             lltype.free(self.data, flavor='raw')
 
+    def descr_str(self, space):
+        context = interp_context.getcontext(space)
+        with lltype.scoped_alloc(rffi.CCHARPP.TO, 1) as cp_ptr:
+            size = rmpdec.mpd_to_sci_size(cp_ptr, self.mpd, context.capitals)
+            if size < 0:
+                raise OperationError(space.w_MemoryError, space.w_None)
+            cp = cp_ptr[0]
+            try:
+                result = rffi.charpsize2str(cp, size)
+            finally:
+                rmpdec.mpd_free(cp)
+        return space.wrap(result)  # Convert bytes to unicode
+
     def compare(self, space, w_other, op):
         if not isinstance(w_other, W_Decimal):  # So far
             return space.w_NotImplemented
 def decimal_from_ssize(space, w_subtype, value, context, exact=True):
     w_result = space.allocate_instance(W_Decimal, w_subtype)
     W_Decimal.__init__(w_result, space)
-    with lltype.scoped_alloc(rffi.CArrayPtr(rffi.UINT).TO, 1) as status_ptr:
-        with interp_context.ConvContext(context, exact) as ctx:
-            rmpdec.mpd_qset_ssize(w_result.mpd, value, ctx, status_ptr)
-        context.addstatus(space, status_ptr[0])
-    
+    with interp_context.ConvContext(
+            space, w_result.mpd, context, exact) as (ctx, status_ptr):
+        rmpdec.mpd_qset_ssize(w_result.mpd, value, ctx, status_ptr)
     return w_result
 
 def decimal_from_cstring(space, w_subtype, value, context, exact=True):
     w_result = space.allocate_instance(W_Decimal, w_subtype)
     W_Decimal.__init__(w_result, space)
 
-    with lltype.scoped_alloc(rffi.CArrayPtr(rffi.UINT).TO, 1) as status_ptr:
-        with interp_context.ConvContext(context, exact) as ctx:
-            rmpdec.mpd_qset_string(w_result.mpd, value, ctx, status_ptr)
-        context.addstatus(space, status_ptr[0])
+    with interp_context.ConvContext(
+            space, w_result.mpd, context, exact) as (ctx, status_ptr):
+        rmpdec.mpd_qset_string(w_result.mpd, value, ctx, status_ptr)
     return w_result
 
 def decimal_from_unicode(space, w_subtype, w_value, context, exact=True,
         s = s.strip()
     return decimal_from_cstring(space, w_subtype, s, context, exact=exact)
 
+def decimal_from_long(space, w_subtype, w_value, context, exact=True):
+    w_result = space.allocate_instance(W_Decimal, w_subtype)
+    W_Decimal.__init__(w_result, space)
+
+    value = space.bigint_w(w_value)
+
+    with interp_context.ConvContext(
+            space, w_result.mpd, context, exact) as (ctx, status_ptr):
+        if value.sign == -1:
+            size = value.numdigits()
+            sign = rmpdec.MPD_NEG
+        else:
+            size = value.numdigits()
+            sign = rmpdec.MPD_POS
+        if rbigint.UDIGIT_TYPE.BITS == 32:
+            with lltype.scoped_alloc(rffi.CArrayPtr(rffi.UINT).TO, size) as digits:
+                for i in range(size):
+                    digits[i] = value.udigit(i)
+                rmpdec.mpd_qimport_u32(
+                    w_result.mpd, digits, size, sign, PyLong_BASE,
+                    ctx, status_ptr)
+        elif rbigint.UDIGIT_TYPE.BITS == 64:
+            # No mpd_qimport_u64, so we convert to a string.
+            return decimal_from_cstring(space, w_subtype, value.str(),
+                                        context, exact=exact)
+                                       
+        else:
+            raise ValueError("Bad rbigint size")
+    return w_result
+
 def decimal_from_object(space, w_subtype, w_value, context, exact=True):
     if w_value is None:
         return decimal_from_ssize(space, w_subtype, 0, context, exact=exact)
     elif space.isinstance_w(w_value, space.w_unicode):
         return decimal_from_unicode(space, w_subtype, w_value, context,
-                                    exact=True, strip_whitespace=True)
+                                    exact=exact, strip_whitespace=exact)
+    elif space.isinstance_w(w_value, space.w_int):
+        return decimal_from_long(space, w_subtype, w_value, context,
+                                 exact=exact)
+    raise oefmt(space.w_TypeError,
+                "conversion from %N to Decimal is not supported",
+                space.type(w_value))
 
 @unwrap_spec(w_context=WrappedDefault(None))
 def descr_new_decimal(space, w_subtype, w_value=None, w_context=None):
-    context = ensure_context(space, w_context)
+    context = interp_context.ensure_context(space, w_context)
     return decimal_from_object(space, w_subtype, w_value, context,
                                exact=True)
 
 W_Decimal.typedef = TypeDef(
     'Decimal',
     __new__ = interp2app(descr_new_decimal),
+    __str__ = interp2app(W_Decimal.descr_str),
     __eq__ = interp2app(W_Decimal.descr_eq),
     )

File pypy/module/_decimal/interp_signals.py

-SIGNAL_NAMES = (
-    'DecimalException', 'Clamped', 'Rounded', 'Inexact',
-    'Subnormal', 'Underflow', 'Overflow', 'DivisionByZero',
-    'InvalidOperation', 'FloatOperation')
+from rpython.rlib import rmpdec
+from rpython.rlib.unroll import unrolling_iterable
+
+SIGNAL_MAP = unrolling_iterable([
+    ('InvalidOperation', rmpdec.MPD_IEEE_Invalid_operation),
+    ('FloatOperation', rmpdec.MPD_Float_operation),
+    ('DivisionByZero', rmpdec.MPD_Division_by_zero),
+    ('Overflow', rmpdec.MPD_Overflow),
+    ('Underflow', rmpdec.MPD_Underflow),
+    ('Subnormal', rmpdec.MPD_Subnormal),
+    ('Inexact', rmpdec.MPD_Inexact),
+    ('Rounded', rmpdec.MPD_Rounded),
+    ('Clamped', rmpdec.MPD_Clamped),
+    ])
+# Exceptions that inherit from InvalidOperation
+COND_MAP = unrolling_iterable([
+    ('InvalidOperation', rmpdec.MPD_Invalid_operation),
+    ('ConversionSyntax', rmpdec.MPD_Conversion_syntax),
+    ('DivisionImpossible', rmpdec.MPD_Division_impossible),
+    ('DivisionUndefined', rmpdec.MPD_Division_undefined),
+    ('InvalidContext', rmpdec.MPD_Invalid_context),
+    ])
 
 def flags_as_exception(space, flags):
+    w_exc = None
+    err_list = []
+    for name, flag in SIGNAL_MAP:
+        if flags & flag:
+            w_exc = getattr(get(space), 'w_' + name)
+    if w_exc is None:
+        raise oefmt(space.w_RuntimeError,
+                    "invalid error flag")
+    
+        
     raise ValueError(hex(flags))
 
 

File pypy/module/_decimal/test/test_decimal.py

 class AppTestExplicitConstruction:
     spaceconfig = dict(usemodules=('_decimal',))
 
+    def setup_class(cls):
+        space = cls.space
+        cls.w_decimal = space.call_function(space.builtin.get('__import__'),
+                                            space.wrap("_decimal"))
+        cls.w_Decimal = space.getattr(cls.w_decimal, space.wrap("Decimal"))
+
     def test_explicit_empty(self):
-        import _decimal
-        Decimal = _decimal.Decimal
+        Decimal = self.Decimal
         assert Decimal() == Decimal("0")
 
+    def test_explicit_from_None(self):
+        Decimal = self.Decimal
+        raises(TypeError, Decimal, None)
+
+    def test_explicit_from_int(self):
+        Decimal = self.decimal.Decimal
+
+        #positive
+        d = Decimal(45)
+        assert str(d) == '45'
+
+        #very large positive
+        d = Decimal(500000123)
+        assert str(d) == '500000123'
+
+        #negative
+        d = Decimal(-45)
+        assert str(d) == '-45'
+
+        #zero
+        d = Decimal(0)
+        assert str(d) == '0'
+
+        # single word longs
+        for n in range(0, 32):
+            for sign in (-1, 1):
+                for x in range(-5, 5):
+                    i = sign * (2**n + x)
+                    d = Decimal(i)
+                    assert str(d) == str(i)
+

File rpython/rlib/rmpdec.py

         "mpd_getprec", "mpd_getemin",  "mpd_getemax", "mpd_getround", "mpd_getclamp",
         "mpd_qsetprec", "mpd_qsetemin",  "mpd_qsetemax", "mpd_qsetround", "mpd_qsetclamp",
         "mpd_maxcontext",
+        "mpd_to_sci_size",
         "mpd_qcmp",
         ],
     compile_extra=compile_extra,
     'ROUND_HALF_UP', 'ROUND_HALF_DOWN', 'ROUND_HALF_EVEN',
     'ROUND_05UP', 'ROUND_TRUNC')
 
+STATUS_FLAGS_CONSTANTS = (
+    'MPD_Clamped',  'MPD_Conversion_syntax', 'MPD_Division_by_zero', 
+    'MPD_Division_impossible', 'MPD_Division_undefined', 'MPD_Fpu_error',
+    'MPD_Inexact', 'MPD_Invalid_context', 'MPD_Invalid_operation', 
+    'MPD_Malloc_error', 'MPD_Not_implemented', 'MPD_Overflow', 
+    'MPD_Rounded', 'MPD_Subnormal', 'MPD_Underflow', 'MPD_Max_status',
+    'MPD_IEEE_Invalid_operation', 'MPD_Errors')
+
 class CConfig:
     _compilation_info_ = eci
 
     MPD_IEEE_CONTEXT_MAX_BITS = platform.ConstantInteger(
         'MPD_IEEE_CONTEXT_MAX_BITS')
     MPD_MAX_PREC = platform.ConstantInteger('MPD_MAX_PREC')
+
+    # Flags
+    MPD_POS = platform.ConstantInteger('MPD_POS')
+    MPD_NEG = platform.ConstantInteger('MPD_NEG')
     MPD_STATIC = platform.ConstantInteger('MPD_STATIC')
     MPD_STATIC_DATA = platform.ConstantInteger('MPD_STATIC_DATA')
 
-    MPD_Malloc_error = platform.ConstantInteger('MPD_Malloc_error')
-
     for name in ROUND_CONSTANTS:
         name = 'MPD_' + name
         locals()[name] = platform.ConstantInteger(name)
 
+    for name in STATUS_FLAGS_CONSTANTS:
+        locals()[name] = platform.ConstantInteger(name)
+
     MPD_T = platform.Struct('mpd_t',
                             [('flags', rffi.UINT),
                              ('exp', rffi.SSIZE_T),
 
 globals().update(platform.configure(CConfig))
 
+MPD_Float_operation = MPD_Not_implemented
 
 def external(name, args, result, **kwds):
     return rffi.llexternal(name, args, result, compilation_info=eci, **kwds)
     'mpd_qset_ssize', [MPD_PTR, rffi.SSIZE_T, MPD_CONTEXT_PTR, rffi.UINTP], lltype.Void)
 mpd_qset_string = external(
     'mpd_qset_string', [MPD_PTR, rffi.CCHARP, MPD_CONTEXT_PTR, rffi.UINTP], lltype.Void)
+mpd_qimport_u32 = external(
+    'mpd_qimport_u32', [MPD_PTR, rffi.UINTP, rffi.SIZE_T,
+                        rffi.UCHAR, rffi.UINT, MPD_CONTEXT_PTR, rffi.UINTP], rffi.SIZE_T)
 
 # Context operations
 mpd_getprec = external(
 mpd_maxcontext = external(
     'mpd_maxcontext', [MPD_CONTEXT_PTR], lltype.Void)
 
+mpd_free = external(
+    'mpd_free', [rffi.VOIDP], lltype.Void, macro=True)
+
+# Conversion
+mpd_to_sci_size = external(
+    'mpd_to_sci_size', [rffi.CCHARPP, MPD_PTR, rffi.INT], rffi.SSIZE_T)
+
 # Operations
 mpd_qcmp = external(
     'mpd_qcmp', [MPD_PTR, MPD_PTR, rffi.UINTP], rffi.INT)