Source code for curry.backends.py.currylib.prelude.math

from ..... import inspect, show as show_module
from .....exceptions import EvaluationError
import math
import operator as op

__all__ = [
    'apply_unboxed', 'divInt', 'minusInt', 'modInt', 'plusInt', 'prim_divFloat'
  , 'prim_minusFloat', 'prim_roundFloat', 'prim_truncateFloat', 'quotInt'
  , 'remInt', 'timesInt'
  ]

# The range of Int: 64 bits, as on the C++ backend.  A result outside it is
# an error, not a wider integer (issue #105), and a division by zero is an
# error, not a Python exception (issue #106).  The messages are the ones the
# C++ backend spells (cyrt/currylib/prelude/math.cpp), so the two backends
# agree.
INT_MIN = -(1 << 63)
INT_MAX = (1 << 63) - 1

[docs] def apply_unboxed(rts, unboxedfunc, _0): '''Apply an unboxed function to the successors of _0 and box the result.''' result_value = unboxedfunc(*_unbox(rts, _0)) box = BOXER[type(result_value)] return rts.Node(*box(rts, result_value), target=_0)
def _operand(value): '''The text of an operand: in parentheses when it is negative.''' if isinstance(value, float): text = show_module.show_float(value) else: text = str(value) return '(%s)' % text if text.startswith('-') else text def _overflow(x, op_text, y): raise EvaluationError( 'integer overflow: %s %s %s' % (_operand(x), op_text, _operand(y)) ) def _division_error(name, x, y): kind = 'division by zero' if y == 0 else 'integer overflow' raise EvaluationError('%s: %s %s %s' % (kind, name, _operand(x), _operand(y))) def _conversion_error(name, x): raise EvaluationError('integer overflow: %s %s' % (name, _operand(x))) def _fits(value): return INT_MIN <= value <= INT_MAX
[docs] def plusInt(x, y): result = x + y if not _fits(result): _overflow(x, '+', y) return result
[docs] def minusInt(x, y): result = x - y if not _fits(result): _overflow(x, '-', y) return result
[docs] def timesInt(x, y): result = x * y if not _fits(result): _overflow(x, '*', y) return result
# The divisions follow the Prelude: div and mod round toward minus infinity, # quot and rem toward zero. The one quotient that overflows is the minimum # divided by -1; its remainder is zero.
[docs] def divInt(x, y): if y == 0: _division_error('div', x, y) result = x // y if not _fits(result): _division_error('div', x, y) return result
[docs] def modInt(x, y): if y == 0: _division_error('mod', x, y) return x - y * (x // y)
[docs] def quotInt(x, y): if y == 0: _division_error('quot', x, y) magnitude = abs(x) // abs(y) result = magnitude if (x < 0) == (y < 0) else -magnitude if not _fits(result): _division_error('quot', x, y) return result
[docs] def remInt(x, y): if y == 0: _division_error('rem', x, y) magnitude = abs(x) % abs(y) return -magnitude if x < 0 else magnitude
[docs] def prim_divFloat(x, y): # The division of a Float follows IEEE 754, as on the C++ backend: an # infinity for a nonzero dividend over zero, NaN for 0.0 / 0.0. try: return op.truediv(y, x) except ZeroDivisionError: if y == 0 or math.isnan(y): return math.nan return math.copysign(math.inf, y) * math.copysign(1.0, x)
[docs] def prim_minusFloat(x, y): return y - x
[docs] def prim_truncateFloat(x): if not math.isfinite(x): _conversion_error('truncate', x) result = math.trunc(x) if not _fits(result): _conversion_error('truncate', x) return result
[docs] def prim_roundFloat(x): # Half away from zero, as std::round of the C++ backend and PAKCS round; # Python's round halves to even. if not math.isfinite(x): _conversion_error('round', x) magnitude = math.floor(abs(x)) if abs(x) - magnitude >= 0.5: magnitude += 1 result = int(math.copysign(magnitude, x)) if not _fits(result): _conversion_error('round', x) return result
def _unbox(rts, _0): # The arguments are evaluated from the left, and the step suspends at the # first free variable before it touches the arguments after it, as the # C++ backend does (currylib/defs/unboxed.def) and as PAKCS does. A # generator in a later argument then does not run while the step cannot # complete; that run forked the configuration once per alternative, and # a conjunction of such steps forked their product before it suspended. args = [] for i in range(len(_0.successors)): arg = rts.variable(_0, i).hnf_or_free() if inspect.isa_freevar(arg.target): rts.suspend(arg.target) args.append(arg) return (arg.unboxed_value for arg in args) BOXER = { bool: lambda rts, rv: [getattr(rts.prelude, 'True' if rv else 'False')] , float: lambda rts, rv: [rts.prelude.Float, rv] , int: lambda rts, rv: [rts.prelude.Int, rv] , str: lambda rts, rv: [rts.prelude.Char, rv] }