def checkConstantInit(val, st, signed, wl, fpf): """Check the constant init, by comparing the three methods""" methods = ('', 'log','Benoit','threshold') c = [Constant(val, wl=wl, signed=signed, method=method, fpf=fpf, name=st) for method in methods] # check the mantissa range for cst in c: if signed: assert((-2 ** (wl - 1) <= cst.mantissa < -2 ** (wl - 2)) or (2 ** (wl-2) <= cst.mantissa < 2 ** (wl-1))) else: assert(2 ** (wl-1) <= cst.mantissa < 2 ** wl) # check str and value method str(c) repr(c) assert(cst.value == val) # compare the three constants (compare their FPF, mantissa and approx) cst = c[0] for i in range(1,len(c)): assert(cst.FPF == c[i].FPF) assert(cst.mantissa == c[i].mantissa) assert(cst.approx == c[i].approx) # check if |c - c_FxP| < 2 ^(l-1) for cst in c: if cst.mantissa == -2**(cst.FPF.wl-1): assert(almosteq(cst.approx, val, abs_eps=ldexp(1, cst.FPF.lsb))) else: assert (almosteq(cst.approx, val, abs_eps=ldexp(1, cst.FPF.lsb - 1))) # check relative and absolute error cst = c[0]
def iterSomeSignedMidPoints(N): """Iter some particular values (mid-point between two consecutive signed FxP number with w bits) Returns the wordlength, string and mpf value""" # ordinary case: +/- (M+0.5)2^l, with (2^(w-1))+2 <= M <= 2^w - 2 for _ in range(N): w = randint(8,200) l = randint(-50,50) M = randint( 2**(w-1)+2, 2**w-2) st = "(%d+0.5)2^%d" % (M,l) mp = ldexp(fadd(M, 0.5, exact=True),l) yield w, st, mp # 2^m+2^(l-1) (so exactly the mid-point between 2^m-2^l and 2^m) for _ in range(N): w = randint(8,200) l = randint(-50, 50) m = w + l - 1 st = "2^%d-2^%d" % (m,l-1) mp = fadd(ldexp(1,m), ldexp(1,l-1), exact=True) yield w, st, mp # -2^m-2^l (so exactly the mid-point between -2^m and -2^m-2^(l+1)) for _ in range(N): w = randint(8,200) l = randint(-50, 50) m = w + l - 1 st = "-2^%d-2^%d" % (m,l) mp = fadd(ldexp(-1,m), ldexp(-1,l), exact=True) yield w, st, mp
def value_interval(self): with mpmath.workprec(self.wordlength + 1): # wl bits is enough if self.signed: return interval( -mpmath.ldexp(1, self.msb), mpmath.ldexp(1, self.msb) - mpmath.ldexp(1, self.lsb)) return interval( 0, mpmath.ldexp(1, self.msb) - mpmath.ldexp(1, self.lsb))
def minmax(self): """Gives the interval a variable of this FPF may belong to. Returns: a tuple (min, max) """ with workprec(self._wl+1): # wl bits is enough if self._signed: return -ldexp(1, self._msb), ldexp(1, self._msb) - ldexp(1, self.lsb) else: return 0, ldexp(1, self._msb+1) - ldexp(1, self.lsb)
def rinfo(self, *, signed=True): if signed: epsilon = mpmath.ldexp(1, -self.wordlength + 1) limit = mpmath.ldexp(1, self.wordlength - 1) return MultiFormatArithmeticLogicUnit.Info( eps=epsilon, min=-limit, max=limit - 1 ) epsilon = mpmath.ldexp(1, -self.wordlength) limit = mpmath.ldexp(1, self.wordlength) return MultiFormatArithmeticLogicUnit.Info( eps=epsilon, min=mpmath.mp.zero, max=limit )
def quantize(c, shr, dtype=numpy.int64): assert numpy.all(shr >= 1) c = mpmath.matrix( [[mpmath.ldexp(c[i, j] + .5, int(shr[j])) for j in range(c.cols - 1)] + [c[i, c.cols - 1]] for i in range(c.rows)]) return numpy.array([[int(mpmath.nint(c[i, j])) for j in range(c.cols)] for i in range(c.rows)], dtype)
def decompose(x, gamma_pow, precision=40): with mp.workdps(2048): tail = x compressed = np.zeros(precision + 1) for i in xrange(precision + 1): if tail == 0: break tail, compressed[i] = mp.frac(tail), mp.floor(tail) tail = mp.ldexp(tail, gamma_pow) return compressed
def align(p, a, b, x_exp, bits, y_exp=None, epsilon=EPSILON): exp = intermediate_exp(p, a, b, epsilon) - bits if y_exp is not None: assert exp[0] <= y_exp exp[0] = y_exp #print('exp', exp) shr = exp[:-1] - exp[1:] - x_exp #print('shr', shr) return (mpmath.matrix( [[mpmath.ldexp(p[i, j], int(-exp[j])) for j in range(p.cols)] for i in range(p.rows)]), shr, exp)
def mpquantize(value, nbits, rounding_method): if isinstance(value, Interval): return Interval( lower_bound=mpquantize( value.lower_bound, nbits=nbits, rounding_method=rounding_method ), upper_bound=mpquantize( value.upper_bound, nbits=nbits, rounding_method=rounding_method ) ) if not mpmath.isfinite(value): raise ValueError(f'Cannot quantize non-finite value: {value}') return int(rounding_method(mpmath.ldexp(value, int(nbits))))
def iterSomeNumbers(N, pmax=50, wmax=100): """Generate: - N numbers that are powers of 2 (randomly choose between python floats and mpmath) then - N numbers that are close to some powers of 2 (mpmath or float approx.) (in form +/-2^p +/- x*2^q , with p, q and x random, |p|<=pmax, |p-q|<=wmax and |x|<=1) and finally - N random numbers we can express with at least wmax bits (mpmath or float approx.) Returns the string representation and the float/mp """ # some powers of 2 for _ in range(N//10): sign = choice((0, 1)) p = randint(-pmax, pmax) # float and mp pfloat = -2**p if sign else 2**p mp = ldexp(-1 if sign else 1, p) usemp = choice((True, False)) # str st = "%s(%s2^%d)" % ('mpf' if usemp else 'float', '-' if sign else '', p) yield st, mp if usemp else pfloat # some +/-2^p +/- x*2^q , with p, q and x random, |p|<=pmax, |p-q|<=wmax and |x|<=1 for _ in range(N): # build signs, p, q and x signp = choice((0, 1)) signq = choice((0, 1)) p = randint(-pmax, pmax) q = p - randint(0, wmax) x = random() usemp = choice((True, False)) st, mp = twoMinusXtwo(signp, p, signq, q, x) yield "%s(%s)" % ('mpf' if usemp else 'float',st), mp if usemp else float(mp) # some random numbers we can express with at least wmax bits (mpmath or float approx.) for _ in range(N): st = [ choice("0123456789") for _ in range(randint(int(wmax/2/3.3), int(wmax/3.3)))] pos = randint(0,len(st)) st.insert(pos, ".") st = "".join(st) yield st, mpf(st)
def value_epsilon(self): return mpmath.ldexp(1, self.lsb)
def __init__(self, value, wl=None, signed=None, fpf=None, method='float', name=None): """Constructs a constant object, from a real value (something that mpmath can handle, so a float or a string). It could be build with wl bits (and signedness) OR (exclusive) with the given fpf Parameters: - value: (float or mpf) the real value of the constant - wl: (positive integer) word-length - signed: (boolean) True if signed fixed-point arithmetic is used, False if unsigned arithmetic - fpf: (FPF) Fixed-Point Format - method: (string) should be 'float' (default) for using the mantissa from floating-point conversion (using mpmath if it's a string) 'Benoit' for using Benoit's method (see "Reliable Implementation of Linear Filters with Fixed-Point Arithmetic", Hilaire and Lopez, 2013) 'log' using the complete logarithm-based formula -> Of course, the 3 methods returns the same results Raises: ValueError if the parameters' values are not coherent """ # combination of arguments (wl AND signed) XOR fpf if wl is not None and fpf is not None: raise ValueError( "Bad combination of arguments: cannot give a word-length and a FPF in the same time" ) if wl is None and fpf is None: raise ValueError( "Bad combination of arguments: no wordlength or FPF given") if wl is not None and wl < 2: raise ValueError( "Constant: The word-length should be at least equal to 2") if signed is None: if fpf is None: signed = True else: signed = fpf.signed # store name if name: self._name = name else: self._name = str(value) # store the exact value given (string, float or mpf) and it's mp math conversion (losslessly) self._value = value mpvalue = mpmathify(value) # check for non-sense values if signed is False and mpvalue < 0: raise ValueError("Unsigned FPF with negative constant !!") # treat null constant if mpvalue == 0: self._mantissa = 0 self._value = 0 if fpf is not None: self._FPF = fpf else: self._FPF = FPF(wl=wl, lsb=0) # arbitrary choose lsb=0 # raise ValueError("zero cannot be stored in a Constant !!") return # conversion to a given FPF if fpf is not None: # do the conversion self._FPF = fpf self._mantissa = nint(ldexp(mpvalue, -fpf.lsb)) self._value = ldexp(self._mantissa, fpf.lsb) # check if mantissa is ok if signed: if not (-2**(fpf.wl - 1) <= self._mantissa <= (2**(fpf.wl - 1) - 1)): raise ValueError( "The constant cannot be converted to the FPF %s", fpf) else: if not (0 <= self._mantissa <= (2**fpf.wl - 1)): raise ValueError( "The constant cannot be converted to the FPF %s", fpf) # check underflow #TODO: add option to allow underflow (sometimes useful) if self._mantissa == 0: raise ValueError( "Underflow during the conversion of the constant!") return # Otherwise, perform the best w-bit conversion (with different methods) if method == 'Benoit': # Benoit's method (ie log2 + check for special cases) # see "Reliable Implementation of Linear Filters with Fixed-Point Arithmetic", Hilaire and Lopez, 2013 with workprec(20 * wl): # "enough bits" # compute MSB # if fpf: # msb = fpf.msb if mpvalue > 0: msb = int(floor(log(mpvalue, 2))) + (1 if signed else 0) else: msb = int(ceil(log(-mpvalue, 2))) # compute mantissa self._mantissa = int(nint(ldexp( mpvalue, (wl - msb - 1)))) # round-to-nearest even # check for particular case (when the quantized constant overpass the limit whereas the constant doesN'T) if self._mantissa == 2**(wl - (1 if signed else 0)): # like for the case 127.8 on 8 bits unsigned... msb += 1 self._mantissa = 2**(wl - (2 if signed else 1)) elif self._mantissa == -2**(wl - 2): # like -128.1 on 8 bits msb -= 1 self._mantissa = -2**(wl - 1) elif method == 'log': # log2-based method (only based on logarithm base 2, with exact mp arithmetic) # that takes care of two's complement asymmetry with workprec(20 * wl + 1): # enough ?? # set the msb if mpvalue > 0: corr = fsub(1, ldexp(1, -wl + (0 if signed else -1)), exact=True) msb = int(floor(log(mpvalue / corr, 2))) + (1 if signed else 0) else: corr = fadd(1, ldexp(1, -wl + 1), exact=True) msb = int(ceil(log(-mpvalue / corr, 2))) # set the lsb and the mantissa lsb = msb + 1 - wl self._mantissa = max(int(nint(ldexp(mpvalue, -lsb))), -2**(wl - 1)) # round-to-nearest even elif method == 'threshold': # compute the minimum wordlength where the constant is not a special case (boundaries) with workprec(20 * wl): # enough? # compute wmin if mpvalue > 0: fr = mpvalue / 2**floor(log( mpvalue, 2)) # fr = 2**frac(log(mpvalue, 2)) if fr < 2: wmin = int( floor(-log(2 - fr, 2))) + (2 if signed else 1) else: # should not happen when fr is computed with infinite precision... here, it happens wmin = mpf('+inf') else: fr = -mpvalue / 2**ceil(log(-mpvalue, 2)) wmin = int(floor(-log(2 * fr - 1, 2)) + 2) # compute msb if mpvalue > 0: msb = int(floor(log(mpvalue, 2))) + ( 1 if signed else 0) + (1 if wl < wmin else 0) else: msb = int(ceil(log(-mpvalue, 2))) - (1 if wl < wmin else 0) # compute lsb and mantissa lsb = msb + 1 - wl self._mantissa = max(int(nint(ldexp(mpvalue, -lsb))), -2**(wl - 1)) # round-to-nearest even else: # default method # since the value is already transformed in floating-point (mantissa + exponent) with mpmath # we can use that mantissa and exponent (be careful of the two's complement asymmetry, -2^p is ok with msb=p) with workprec(20 * wl): # floating-point with the right word-length fl = mpf(value, prec=wl - (1 if signed else 0)) # since mpmath do not have the integral significant (with MSB=1), we take the normalized significant # and build the mantissa (M) and exponent (e) m, ep = frexp(fl) M = ldexp(m, wl - (1 if signed else 0)) e = ep - wl + (1 if signed else 0) # FxP mantissa, msb and lsb self._mantissa = int(M) lsb = e msb = wl + lsb - 1 if self._mantissa == -2**(wl - 2): lsb -= 1 msb -= 1 self._mantissa = -2**(wl - 1) # associated FPF self._FPF = FPF(wl=wl, msb=msb, signed=signed)
def approx(self): """Return the approximation of the constant with the chosen fixed-point format""" return ldexp(self._mantissa, self._FPF.lsb)
def twoMinusXtwo(signp, p, signq, q, x): """Returns a string and a mp number in form +/-2^p +/- x*2^q""" mp = fadd(ldexp(-1 if signp else 1, p), ldexp(-x if signq else x, q), exact=True) st = "%s2^%d %s%s*2^%d" % ('-' if signp else '', p, '-' if signq else '+', x.hex(), q) return st, mp