Package orm2 :: Package util :: Module fixedpoint
[hide private]
[frames] | no frames]

Source Code for Module orm2.util.fixedpoint

  1  #!/usr/bin/env python 
  2  __docformat__ = "plaintext" 
  3   
  4  """ 
  5  FixedPoint objects support decimal arithmetic with a fixed number of 
  6  digits (called the object's precision) after the decimal point.  The 
  7  number of digits before the decimal point is variable & unbounded. 
  8   
  9  The precision is user-settable on a per-object basis when a FixedPoint 
 10  is constructed, and may vary across FixedPoint objects.  The precision 
 11  may also be changed after construction via FixedPoint.set_precision(p). 
 12  Note that if the precision of a FixedPoint is reduced via set_precision, 
 13  information may be lost to rounding. 
 14   
 15  >>> x = FixedPoint("5.55")  # precision defaults to 2 
 16  >>> print x 
 17  5.55 
 18  >>> x.set_precision(1)      # round to one fraction digit 
 19  >>> print x 
 20  5.6 
 21  >>> print FixedPoint("5.55", 1)  # same thing setting to 1 in constructor 
 22  5.6 
 23  >>> repr(x) #  returns constructor string that reproduces object exactly 
 24  "FixedPoint('5.6', 1)" 
 25  >>> 
 26   
 27  When FixedPoint objects of different precision are combined via + - * /, 
 28  the result is computed to the larger of the inputs' precisions, which also 
 29  becomes the precision of the resulting FixedPoint object. 
 30   
 31  >>> print FixedPoint("3.42") + FixedPoint("100.005", 3) 
 32  103.425 
 33  >>> 
 34   
 35  When a FixedPoint is combined with other numeric types (ints, floats, 
 36  strings representing a number) via + - * /, then similarly the computation 
 37  is carried out using-- and the result inherits --the FixedPoint's 
 38  precision. 
 39   
 40  >>> print FixedPoint(1) / 7 
 41  0.14 
 42  >>> print FixedPoint(1, 30) / 7 
 43  0.142857142857142857142857142857 
 44  >>> 
 45   
 46  The string produced by str(x) (implictly invoked by "print") always 
 47  contains at least one digit before the decimal point, followed by a 
 48  decimal point, followed by exactly x.get_precision() digits.  If x is 
 49  negative, str(x)[0] == "-". 
 50   
 51  The FixedPoint constructor can be passed an int, long, string, float, 
 52  FixedPoint, or any object convertible to a float via float() or to a 
 53  long via long().  Passing a precision is optional; if specified, the 
 54  precision must be a non-negative int.  There is no inherent limit on 
 55  the size of the precision, but if very very large you'll probably run 
 56  out of memory. 
 57   
 58  Note that conversion of floats to FixedPoint can be surprising, and 
 59  should be avoided whenever possible.  Conversion from string is exact 
 60  (up to final rounding to the requested precision), so is greatly 
 61  preferred. 
 62   
 63  >>> print FixedPoint(1.1e30) 
 64  1099999999999999993725589651456.00 
 65  >>> print FixedPoint("1.1e30") 
 66  1100000000000000000000000000000.00 
 67  >>> 
 68   
 69  The following Python operators and functions accept FixedPoints in the 
 70  expected ways: 
 71   
 72      binary + - * / % divmod 
 73          with auto-coercion of other types to FixedPoint. 
 74          + - % divmod  of FixedPoints are always exact. 
 75          * / of FixedPoints may lose information to rounding, in 
 76              which case the result is the infinitely precise answer 
 77              rounded to the result's precision. 
 78          divmod(x, y) returns (q, r) where q is a long equal to 
 79              floor(x/y) as if x/y were computed to infinite precision, 
 80              and r is a FixedPoint equal to x - q * y; no information 
 81              is lost.  Note that q has the sign of y, and abs(r) < abs(y). 
 82      unary - 
 83      == != < > <= >=  cmp 
 84      min  max 
 85      float  int  long    (int and long truncate) 
 86      abs 
 87      str  repr 
 88      hash 
 89      use as dict keys 
 90      use as boolean (e.g. "if some_FixedPoint:" -- true iff not zero) 
 91   
 92  Methods unique to FixedPoints: 
 93     .copy()              return new FixedPoint with same value 
 94     .frac()              long(x) + x.frac() == x 
 95     .get_precision()     return the precision(p) of this FixedPoint object 
 96     .set_precision(p)    set the precision of this FixedPoint object 
 97      
 98  Provided as-is; use at your own risk; no warranty; no promises; enjoy! 
 99  """ 
100   
101  # Released to the public domain 28-Mar-2001, 
102  # by Tim Peters (tim.one@home.com). 
103   
104   
105  # 28-Mar-01 ver 0.0,4 
106  #     Use repr() instead of str() inside __str__, because str(long) changed 
107  #     since this was first written (used to produce trailing "L", doesn't 
108  #     now). 
109  # 
110  # 09-May-99 ver 0,0,3 
111  #     Repaired __sub__(FixedPoint, string); was blowing up. 
112  #     Much more careful conversion of float (now best possible). 
113  #     Implemented exact % and divmod. 
114  # 
115  # 14-Oct-98 ver 0,0,2 
116  #     Added int, long, frac.  Beefed up docs.  Removed DECIMAL_POINT 
117  #     and MINUS_SIGN globals to discourage bloating this class instead 
118  #     of writing formatting wrapper classes (or subclasses) 
119  # 
120  # 11-Oct-98 ver 0,0,1 
121  #     posted to c.l.py 
122   
123  __copyright__ = "Copyright (C) Python Software Foundation" 
124  __author__ = "Tim Peters" 
125  __version__ = 0, 1, 0 
126   
127 -def bankersRounding(self, dividend, divisor, quotient, remainder):
128 """ 129 rounding via nearest-even 130 increment the quotient if 131 the remainder is more than half of the divisor 132 or the remainder is exactly half the divisor and the quotient is odd 133 """ 134 c = cmp(remainder << 1, divisor) 135 # c < 0 <-> remainder < divisor/2, etc 136 if c > 0 or (c == 0 and (quotient & 1) == 1): 137 quotient += 1 138 return quotient
139
140 -def addHalfAndChop(self, dividend, divisor, quotient, remainder):
141 """ 142 the equivalent of 'add half and chop' 143 increment the quotient if 144 the remainder is greater than half of the divisor 145 or the remainder is exactly half the divisor and the quotient is >= 0 146 """ 147 c = cmp(remainder << 1, divisor) 148 # c < 0 <-> remainder < divisor/2, etc 149 if c > 0 or (c == 0 and quotient >= 0): 150 quotient += 1 151 return quotient
152 153 # 2002-10-20 dougfort - fake classes for pre 2.2 compatibility 154 try: 155 object 156 except NameError:
157 - class object:
158 pass
159 - def property(x, y):
160 return None
161 162 # The default value for the number of decimal digits carried after the 163 # decimal point. This only has effect at compile-time. 164 DEFAULT_PRECISION = 2 165
166 -class FixedPoint(object):
167 """Basic FixedPoint object class, 168 The exact value is self.n / 10**self.p; 169 self.n is a long; self.p is an int 170 """ 171 __slots__ = ['n', 'p']
172 - def __init__(self, value=0, precision=DEFAULT_PRECISION):
173 self.n = self.p = 0 174 self.set_precision(precision) 175 p = self.p 176 177 if isinstance(value, type("42.3e5")): 178 n, exp = _string2exact(value) 179 # exact value is n*10**exp = n*10**(exp+p)/10**p 180 effective_exp = exp + p 181 if effective_exp > 0: 182 n = n * _tento(effective_exp) 183 elif effective_exp < 0: 184 n = self._roundquotient(n, _tento(-effective_exp)) 185 self.n = n 186 return 187 188 if isinstance(value, type(42)) or isinstance(value, type(42L)): 189 self.n = long(value) * _tento(p) 190 return 191 192 if isinstance(value, type(self)): 193 temp = value.copy() 194 temp.set_precision(p) 195 self.n, self.p = temp.n, temp.p 196 return 197 198 if isinstance(value, type(42.0)): 199 # XXX ignoring infinities and NaNs and overflows for now 200 import math 201 f, e = math.frexp(abs(value)) 202 assert f == 0 or 0.5 <= f < 1.0 203 # |value| = f * 2**e exactly 204 205 # Suck up CHUNK bits at a time; 28 is enough so that we suck 206 # up all bits in 2 iterations for all known binary double- 207 # precision formats, and small enough to fit in an int. 208 CHUNK = 28 209 top = 0L 210 # invariant: |value| = (top + f) * 2**e exactly 211 while f: 212 f = math.ldexp(f, CHUNK) 213 digit = int(f) 214 assert digit >> CHUNK == 0 215 top = (top << CHUNK) | digit 216 f = f - digit 217 assert 0.0 <= f < 1.0 218 e = e - CHUNK 219 220 # now |value| = top * 2**e exactly 221 # want n such that n / 10**p = top * 2**e, or 222 # n = top * 10**p * 2**e 223 top = top * _tento(p) 224 if e >= 0: 225 n = top << e 226 else: 227 n = self._roundquotient(top, 1L << -e) 228 if value < 0: 229 n = -n 230 self.n = n 231 return 232 233 if isinstance(value, type(42-42j)): 234 raise TypeError("can't convert complex to FixedPoint: " + 235 `value`) 236 237 # can we coerce to a float? 238 yes = 1 239 try: 240 asfloat = float(value) 241 except: 242 yes = 0 243 if yes: 244 self.__init__(asfloat, p) 245 return 246 247 # similarly for long 248 yes = 1 249 try: 250 aslong = long(value) 251 except: 252 yes = 0 253 if yes: 254 self.__init__(aslong, p) 255 return 256 257 raise TypeError("can't convert to FixedPoint: " + `value`)
258
259 - def get_precision(self):
260 """Return the precision of this FixedPoint. 261 262 The precision is the number of decimal digits carried after 263 the decimal point, and is an int >= 0. 264 """ 265 266 return self.p
267
268 - def set_precision(self, precision=DEFAULT_PRECISION):
269 """Change the precision carried by this FixedPoint to p. 270 271 precision must be an int >= 0, and defaults to 272 DEFAULT_PRECISION. 273 274 If precision is less than this FixedPoint's current precision, 275 information may be lost to rounding. 276 """ 277 278 try: 279 p = int(precision) 280 except: 281 raise TypeError("precision not convertable to int: " + 282 `precision`) 283 if p < 0: 284 raise ValueError("precision must be >= 0: " + `precision`) 285 286 if p > self.p: 287 self.n = self.n * _tento(p - self.p) 288 elif p < self.p: 289 self.n = self._roundquotient(self.n, _tento(self.p - p)) 290 self.p = p
291 292 precision = property(get_precision, set_precision) 293
294 - def __str__(self):
295 n, p = self.n, self.p 296 i, f = divmod(abs(n), _tento(p)) 297 if p: 298 frac = repr(f)[:-1] 299 frac = "0" * (p - len(frac)) + frac 300 else: 301 frac = "" 302 return "-"[:n<0] + \ 303 repr(i)[:-1] + \ 304 "." + frac
305
306 - def __repr__(self):
307 return "FixedPoint" + `(str(self), self.p)`
308
309 - def copy(self):
310 return _mkFP(self.n, self.p, type(self))
311 312 __copy__ = copy 313
314 - def __deepcopy__(self, memo):
315 return self.copy()
316
317 - def __cmp__(self, other):
318 xn, yn, p = _norm(self, other, FixedPoint=type(self)) 319 return cmp(xn, yn)
320
321 - def __hash__(self):
322 """ Caution! == values must have equal hashes, and a FixedPoint 323 is essentially a rational in unnormalized form. There's 324 really no choice here but to normalize it, so hash is 325 potentially expensive. 326 n, p = self.__reduce() 327 328 Obscurity: if the value is an exact integer, p will be 0 now, 329 so the hash expression reduces to hash(n). So FixedPoints 330 that happen to be exact integers hash to the same things as 331 their int or long equivalents. This is Good. But if a 332 FixedPoint happens to have a value exactly representable as 333 a float, their hashes may differ. This is a teensy bit Bad. 334 """ 335 n, p = self.__reduce() 336 return hash(n) ^ hash(p)
337
338 - def __nonzero__(self):
339 """ Returns true if this FixedPoint is not equal to zero""" 340 return self.n != 0
341
342 - def __neg__(self):
343 return _mkFP(-self.n, self.p, type(self))
344
345 - def __abs__(self):
346 """ Returns new FixedPoint containing the absolute value of this FixedPoint""" 347 if self.n >= 0: 348 return self.copy() 349 else: 350 return -self
351
352 - def __add__(self, other):
353 n1, n2, p = _norm(self, other, FixedPoint=type(self)) 354 # n1/10**p + n2/10**p = (n1+n2)/10**p 355 return _mkFP(n1 + n2, p, type(self))
356 357 __radd__ = __add__ 358
359 - def __sub__(self, other):
360 if not isinstance(other, type(self)): 361 other = type(self)(other, self.p) 362 return self.__add__(-other)
363
364 - def __rsub__(self, other):
365 return (-self) + other
366
367 - def __mul__(self, other):
368 n1, n2, p = _norm(self, other, FixedPoint=type(self)) 369 # n1/10**p * n2/10**p = (n1*n2/10**p)/10**p 370 return _mkFP(self._roundquotient(n1 * n2, _tento(p)), p, type(self))
371 372 __rmul__ = __mul__ 373
374 - def __div__(self, other):
375 n1, n2, p = _norm(self, other, FixedPoint=type(self)) 376 if n2 == 0: 377 raise ZeroDivisionError("FixedPoint division") 378 if n2 < 0: 379 n1, n2 = -n1, -n2 380 # n1/10**p / (n2/10**p) = n1/n2 = (n1*10**p/n2)/10**p 381 return _mkFP(self._roundquotient(n1 * _tento(p), n2), p, type(self))
382
383 - def __rdiv__(self, other):
384 n1, n2, p = _norm(self, other, FixedPoint=type(self)) 385 return _mkFP(n2, p, FixedPoint=type(self)) / self
386
387 - def __divmod__(self, other):
388 n1, n2, p = _norm(self, other, FixedPoint=type(self)) 389 if n2 == 0: 390 raise ZeroDivisionError("FixedPoint modulo") 391 # floor((n1/10**p)/(n2*10**p)) = floor(n1/n2) 392 q = n1 / n2 393 # n1/10**p - q * n2/10**p = (n1 - q * n2)/10**p 394 return q, _mkFP(n1 - q * n2, p, type(self))
395
396 - def __rdivmod__(self, other):
397 n1, n2, p = _norm(self, other, FixedPoint=type(self)) 398 return divmod(_mkFP(n2, p), self)
399
400 - def __mod__(self, other):
401 return self.__divmod__(other)[1]
402
403 - def __rmod__(self, other):
404 n1, n2, p = _norm(self, other, FixedPoint=type(self)) 405 return _mkFP(n2, p, type(self)).__mod__(self)
406
407 - def __float__(self):
408 """Return the floating point representation of this FixedPoint. 409 Caution! float can lose precision. 410 """ 411 n, p = self.__reduce() 412 return float(n) / float(_tento(p))
413
414 - def __long__(self):
415 """EJG/DF - Should this round instead? 416 Note e.g. long(-1.9) == -1L and long(1.9) == 1L in Python 417 Note that __int__ inherits whatever __long__ does, 418 and .frac() is affected too 419 """ 420 answer = abs(self.n) / _tento(self.p) 421 if self.n < 0: 422 answer = -answer 423 return answer
424
425 - def __int__(self):
426 """Return integer value of FixedPoint object.""" 427 return int(self.__long__())
428
429 - def frac(self):
430 """Return fractional portion as a FixedPoint. 431 432 x.frac() + long(x) == x 433 """ 434 return self - long(self)
435
436 - def _roundquotient(self, x, y):
437 """ 438 Divide x by y, 439 return the result of rounding 440 Developers may substitute their own 'round' for custom rounding 441 y must be > 0 442 """ 443 assert y > 0 444 n, leftover = divmod(x, y) 445 return self.round(x, y, n, leftover)
446
447 - def __reduce(self):
448 """ Return n, p s.t. self == n/10**p and n % 10 != 0""" 449 n, p = self.n, self.p 450 if n == 0: 451 p = 0 452 while p and n % 10 == 0: 453 p = p - 1 454 n = n / 10 455 return n, p
456 457 # 2002-10-04 dougfort - Default to Banker's Rounding for backward compatibility 458 FixedPoint.round = bankersRounding 459 460 # return 10L**n 461
462 -def _tento(n, cache={}):
463 """Cached computation of 10**n""" 464 try: 465 return cache[n] 466 except KeyError: 467 answer = cache[n] = 10L ** n 468 return answer
469
470 -def _norm(x, y, isinstance=isinstance, FixedPoint=FixedPoint, 471 _tento=_tento):
472 """Return xn, yn, p s.t. 473 p = max(x.p, y.p) 474 x = xn / 10**p 475 y = yn / 10**p 476 477 x must be FixedPoint to begin with; if y is not FixedPoint, 478 it inherits its precision from x. 479 480 Note that this method is called a lot, so default-arg tricks are helpful. 481 """ 482 assert isinstance(x, FixedPoint) 483 if not isinstance(y, FixedPoint): 484 y = FixedPoint(y, x.p) 485 xn, yn = x.n, y.n 486 xp, yp = x.p, y.p 487 if xp > yp: 488 yn = yn * _tento(xp - yp) 489 p = xp 490 elif xp < yp: 491 xn = xn * _tento(yp - xp) 492 p = yp 493 else: 494 p = xp # same as yp 495 return xn, yn, p
496
497 -def _mkFP(n, p, FixedPoint=FixedPoint):
498 """Make FixedPoint objext - Return a new FixedPoint object with the selected precision.""" 499 f = FixedPoint() 500 #print '_mkFP Debug: %s, value=%s' % (type(f),n) 501 f.n = n 502 f.p = p 503 return f
504 505 # crud for parsing strings 506 import re 507 508 # There's an optional sign at the start, and an optional exponent 509 # at the end. The exponent has an optional sign and at least one 510 # digit. In between, must have either at least one digit followed 511 # by an optional fraction, or a decimal point followed by at least 512 # one digit. Yuck. 513 514 _parser = re.compile(r""" 515 \s* 516 (?P<sign>[-+])? 517 ( 518 (?P<int>\d+) (\. (?P<frac>\d*))? 519 | 520 \. (?P<onlyfrac>\d+) 521 ) 522 ([eE](?P<exp>[-+]? \d+))? 523 \s* $ 524 """, re.VERBOSE).match 525 526 del re 527 528
529 -def _string2exact(s):
530 """Return n, p s.t. float string value == n * 10**p exactly.""" 531 m = _parser(s) 532 if m is None: 533 raise ValueError("can't parse as number: " + `s`) 534 535 exp = m.group('exp') 536 if exp is None: 537 exp = 0 538 else: 539 exp = int(exp) 540 541 intpart = m.group('int') 542 if intpart is None: 543 intpart = "0" 544 fracpart = m.group('onlyfrac') 545 else: 546 fracpart = m.group('frac') 547 if fracpart is None or fracpart == "": 548 fracpart = "0" 549 assert intpart 550 assert fracpart 551 552 i, f = long(intpart), long(fracpart) 553 nfrac = len(fracpart) 554 i = i * _tento(nfrac) + f 555 exp = exp - nfrac 556 557 if m.group('sign') == "-": 558 i = -i 559 560 return i, exp
561
562 -def _test():
563 """Unit testing framework""" 564 fp = FixedPoint 565 o = fp("0.1") 566 assert str(o) == "0.10" 567 t = fp("-20e-2", 5) 568 assert str(t) == "-0.20000" 569 assert t < o 570 assert o > t 571 assert min(o, t) == min(t, o) == t 572 assert max(o, t) == max(t, o) == o 573 assert o != t 574 assert --t == t 575 assert abs(t) > abs(o) 576 assert abs(o) < abs(t) 577 assert o == o and t == t 578 assert t.copy() == t 579 assert o == -t/2 == -.5 * t 580 assert abs(t) == o + o 581 assert abs(o) == o 582 assert o/t == -0.5 583 assert -(t/o) == (-t)/o == t/-o == 2 584 assert 1 + o == o + 1 == fp(" +00.000011e+5 ") 585 assert 1/o == 10 586 assert o + t == t + o == -o 587 assert 2.0 * t == t * 2 == "2" * t == o/o * 2L * t 588 assert 1 - t == -(t - 1) == fp(6L)/5 589 assert t*t == 4*o*o == o*4*o == o*o*4 590 assert fp(2) - "1" == 1 591 assert float(-1/t) == 5.0 592 for p in range(20): 593 assert 42 + fp("1e-20", p) - 42 == 0 594 assert 1/(42 + fp("1e-20", 20) - 42) == fp("100.0E18") 595 o = fp(".9995", 4) 596 assert 1 - o == fp("5e-4", 10) 597 o.set_precision(3) 598 assert o == 1 599 o = fp(".9985", 4) 600 o.set_precision(3) 601 assert o == fp(".998", 10) 602 assert o == o.frac() 603 o.set_precision(100) 604 assert o == fp(".998", 10) 605 o.set_precision(2) 606 assert o == 1 607 x = fp(1.99) 608 assert long(x) == -long(-x) == 1L 609 assert int(x) == -int(-x) == 1 610 assert x == long(x) + x.frac() 611 assert -x == long(-x) + (-x).frac() 612 assert fp(7) % 4 == 7 % fp(4) == 3 613 assert fp(-7) % 4 == -7 % fp(4) == 1 614 assert fp(-7) % -4 == -7 % fp(-4) == -3 615 assert fp(7.0) % "-4.0" == 7 % fp(-4) == -1 616 assert fp("5.5") % fp("1.1") == fp("5.5e100") % fp("1.1e100") == 0 617 assert divmod(fp("1e100"), 3) == (long(fp("1e100")/3), 1)
618 619 if __name__ == '__main__': 620 _test() 621