monetary applications [was: Python GUI app to impress the bos s?]

Michael Amrhein michael.amrhein at t-online.de
Sat Sep 21 17:45:05 CEST 2002


Tim Peters wrote:
 > About http://starship.python.net/crew/aahz/FixedPoint.py
 >
 > If someone is willing and able to do the work (sorry, I can't make
 > time for it), Guido will accept this into the standard library for
 > 2.3.  It's a small, self-contained, pure Python module; it's been in
 > use for a few years with no bugs reported;

There was a short thread in this ng some months ago, where two bugs in
FixedPoint.py were reported (incl. fixes).

 > and is unencumbered by any license (a lawyer has told
 > me that an individual in the USA cannot meaningfully disclaim
 > copyright, so fine, the copyright I didn't want is hereby transferred
 > to the PSF <wink>).
 >
 > What work remains:
 >
 > + Write docs for the Library Reference manual.  I expect the existing
 >  module docstring will be a good start.
 >
 > + Create a test driver for Python's regression suite.
 >
 > + Have fun modernizing it, if you like (for example, "/" should be
 > changed to "//" where integer division is intended; playing with
 > __slots__ may yield some nice savings; whatever).
 >
 > This should be a fun and comparatively easy task -- if you ever
 > wanted to get your name into the ACKS file, here's one way to do it
 > without tithing you income directly to Guido <wink>.
 >
 > although-tithing-saves-me-time-ly y'rs  - tim
 >
 >
I've written a simular class named 'fixed' (see below) with portions
adopted from your FixedPoint, but a slightly different constructor (no
default precision; precision calculated from given value, if no
precision given) and some additional functions. And, yes, it uses
__slots__ and supports the new integer division. So, maybe, it could
serve as the "modernized" version you recommend in your third point.
But, while willing to contribute it to the PSF, I just don't know what
is required to do so. And because I've never written docs for the
Library Reference nor created test drivers I'll need some guidance.
Maybe, Doug can help with this.
Cheers,
Michael

---
##------------------------------------------------------------------------------
## Name:        fixedpoint.py
## Purpose:     Decimal arithmetic with a fixed number of fractional digits
##
## Author:      Michael Amrhein (michael.amrhein at t-online.de)
##
## Created:     2001-12-06
## RCS-ID:      $Id: fixedpoint.py,v 1.2 2002/06/15 12:26:58 am Exp $
## Copyright:   (c) Michael Amrhein
##              Portions adopted from FixedPoint.py written by Tim Peters
## License:     This program is free software; you can redistribute it 
and/or
##              modify it under the terms of the GNU Lesser General Public
##              License as published by the Free Software Foundation; either
##              version 2 of the License, or (at your option) any later 
version.
##              This program is distributed in the hope that it will be 
useful,
##              but WITHOUT ANY WARRANTY; without even the implied 
warranty of
##              MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
##              See the GNU General Public License for more details.
##              You should have received a copy of the license text 
along with
##              this program; if not, get it from 
http://www.gnu.org/licenses,
##              or write to the Free Software Foundation, Inc.,
##              59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
##------------------------------------------------------------------------------
__version__ = 0, 9, 1

from __future__ import division


class fixed(object):

     __slots__ = ['_value', '_precision']

     # initialize fixed point number
     def __init__(self, value=0L, precision=None):
         """Set value adjusted to precision.

         Keyword arguments:
         value       initial value (default: 0L)
         precision   initial number of fractional digits (default: None)

         If precision is given, it must be of type int.

         If value is given, it must be of type fixed, string, float, 
long or int
         (or be convertable to float or long).

         If a string is given as value, it must be a string in one of two
         formats:
             [+|-]<int>[.<frac>][[e|E][+|-]<exp>]
             [+|-].<frac>[[e|E][+|-]<exp>]

         The value is always adjusted to the given precision or the 
precision
         is calculated from the given value, if no precision is given.
         """
         if precision is not None:
             assert isinstance(precision, int), \
                                          "Precision must be of <type 
'int'>."
             assert precision >= 0, "Precision must be >= 0."
         if isinstance(value, fixed):
             vp = value._precision
             if precision is None or precision == vp:
                 self._precision = vp
                 self._value = value._value
             else:
                 self._precision = precision
                 if precision < vp:
                     self._value = _shiftdecimal(value._value, vp - 
precision)
                 else:
                     self._value = value._value * 10L ** (precision - vp)
             return
         if isinstance(value, str):
             i, e = _string2exact(value)
             if i is None:
                 raise TypeError, "Can't convert "+repr(value)+" to fixed."
             else:
                 if precision is None:
                     if e > 0:
                         self._precision = 0
                         self._value = i * 10L ** e
                     else:
                         self._precision = abs(e)
                         self._value = i
                 else:
                     self._precision = precision
                     p = e + precision
                     if p >= 0:
                         self._value = i * 10L ** p
                     else:
                         self._value = _shiftdecimal(i, -p)
             return
         if isinstance(value, float):
             if precision is None:
                 self._precision = vp = len(repr(value).split('.')[1])
                 self._value = long(round(value * 10L ** vp))
                 self.adjust()
             else:
                 self._precision = precision
                 self._value = long(round(value * 10L ** precision))
             return
         if isinstance(value, (int, long)):
             if precision is None:
                 self._precision = 0
                 self._value = long(value)
             else:
                 self._precision = precision
                 self._value = long(value) * 10L ** precision
             return
         # Coerce value to Float?
         ok = 1
         try:
             v = float(value)
         except:
             ok = 0
         if ok:
             self.__init__(v, precision)
             return
         # Coerce value to Long?
         ok = 1
         try:
             v = long(value)
         except:
             ok = 0
         if ok:
             self.__init__(v, precision)
             return
         # unable to create fixed
         raise TypeError, "Can't convert "+repr(value)+" to fixed."

     # copy instance
     def __copy__(self):
         return fixed(self)
     __deepcopy__ = __copy__

     # return v, p so that self == v // 10 ** p and v % 10 != 0
     def __reduce(self):
         sv = self._value
         if sv == 0L:
             return 0L, 0
         sp = self._precision
         while sp and sv % 10L == 0L:
             sp -= 1
             sv = sv // 10L
         return sv, sp

     # return _value adjusted to given precision
     def __get_adjusted_value(self, precision):
         p = precision - self._precision
         if p == 0:
             return self._value
         elif p > 0:
             return self._value * 10L ** p
         else:
             return _shiftdecimal(self._value, -p)

     # adjust precision
     def adjust(self, precision=None):
         """Adjust this fixed to the given precision.

         If the given precision is less than the current precision 
information
         may be lost.
         If no precision is given it is adjusted to the minimum precision
         preserving x == x.adjust().
         """
         if precision is None:
             self._value, self._precision = self.__reduce()
         else:
             assert isinstance(precision, int), "Precision must be of 
type Int."
             assert precision >= 0, "Precision must be >= 0."
             self._value = self.__get_adjusted_value(precision)
             self._precision = precision

     # return precision
     def getPrecision(self):
         """Return precision."""
         return self._precision

     # string representation
     def __repr__(self):
         return "fixed('"+self.__str()+"')"

     # convert to string
     def __str(self):
         sp = self._precision
         if sp == 0:
             return str(self._value)
         else:
             sv = self._value
             s = str(abs(sv))
             n = len(s)
             if n >= sp:
                 return (sv<0)*'-' + s[0:-sp] + '.' + s[-sp:]
             else:
                 return (sv<0)*'-' + '0.' + (sp-n)*'0' + s

     # convert to string (according to current locale)
     def __str__(self):
         from locale import format, localeconv
         sp = self._precision
         if sp == 0:
             return format("%d", self._value, 1)
         else:
             i = self.__long__()
             f = self.__frac()
             s = (i==0 and f<0)*'-'      # -1 < self < 0 => i = 0 and f 
< 0 !!!
             return s + format("%d", i, 1) + localeconv()['decimal_point'] \
                                                 + format("%0*d", (sp, 
abs(f)))

     # compare to fixed or any type that can be converted to a fixed
     def __cmp__(self, other):
         if isinstance(other, fixed):
             p = max(self._precision, other._precision)
             return cmp(self.__get_adjusted_value(p),
 
other.__get_adjusted_value(p))
         elif other is None:
             return 1
         else:
             return self.__cmp__(fixed(other))

     # compute hash index
     def __hash__(self):
         sv, sp = self.__reduce()
         return hash(sv) ^ hash(sp)

     # return 0 or 1 for truth-value testing
     def __nonzero__(self):
         return self._value != 0L

     # return integer portion as long
     def __long__(self):
         sp = self._precision
         if sp == 0:
             return self._value
         else:
             sv = self._value
             result = abs(sv) // 10L ** sp
             if sv < 0:
                 return -result
             else:
                 return result

     # return integer portion as int
     def __int__(self):
         return int(self.__long__())

     # return fractional portion as long
     def __frac(self):
         return self._value - long(self) * 10L ** self._precision

     # return fractional portion as fixed
     def frac(self):
         """Return fractional portion as fixed."""
         return self - long(self)

     # convert to float (may loose precision!)
     def __float__(self):
         sv, sp = self.__reduce()
         return sv / 10L ** sp

     # positiv value
     def __pos__(self):
         return fixed(self)

     # negative value
     def __neg__(self):
         result = fixed(self)
         result._value = -result._value
         return result

     # absolute value
     def __abs__(self):
         result = fixed(self)
         result._value = abs(result._value)
         return result

     # self + other
     def __add__(self, other):
         if isinstance(other, fixed):
             p = self._precision - other._precision
             if p == 0:
                 result = fixed(self)
                 result._value += other._value
             elif p > 0:
                 result = fixed(self)
                 result._value += other._value * 10L ** p
             else:
                 result = fixed(other)
                 result._value += self._value * 10L ** -p
             return result
         else:
             return self.__add__(fixed(other))
     # other + self
     __radd__ = __add__

     # self - other
     def __sub__(self, other):
         if isinstance(other, fixed):
             p = self._precision - other._precision
             if p == 0:
                 result = fixed(self)
                 result._value -= other._value
             elif p > 0:
                 result = fixed(self)
                 result._value -= other._value * 10L ** p
             else:
                 result = fixed(other)
                 result._value -= self._value * 10L ** -p
             return result
         else:
             return self.__sub__(fixed(other))
     # other - self
     def __rsub__(self, other):
         return self.__neg__().__add__(other)

     # self * other
     def __mul__(self, other):
         if isinstance(other, fixed):
             sp, op = self._precision, other._precision
             if sp >= op:
                 result = fixed(self)
                 result._value = _shiftdecimal(self._value * 
other._value, op)
             else:
                 result = fixed(other)
                 result._value = _shiftdecimal(self._value * 
other._value, sp)
             return result
         else:
             return self.__mul__(fixed(other))
     # other * self
     __rmul__ = __mul__

     # self / other
     def __div__(self, other):
         if isinstance(other, fixed):
             if other._value == 0L:
                 raise ZeroDivisionError
             sp, op = self._precision, other._precision
             if sp >= op:
                 result = fixed(self)
                 result._value = _divdecimal(self._value * 10L ** op,
 
other._value)
             else:
                 result = fixed(other)
                 result._value = _divdecimal(self._value * 10L ** (2 * 
op - sp),
 
other._value)
             return result
         else:
             return self.__div__(fixed(other))
     # other / self
     def __rdiv__(self, other):
         return fixed(other).__div__(self)
     # fixed division is true division
     __truediv__ = __div__
     __rtruediv__ = __rdiv__

     # self // other, self % other
     def __divmod__(self, other):
         if isinstance(other, fixed):
             if other._value == 0L:
                 raise ZeroDivisionError
             sp, op = self._precision, other._precision
             if sp >= op:
                 r = fixed(self)
                 sv = self._value
                 ov = other.__get_adjusted_value(sp)
             else:
                 r = fixed(other)
                 sv = self.__get_adjusted_value(op)
                 ov = other._value
             q = sv // ov
             r._value = sv - q * ov
             return fixed(q, r._precision), r
         else:
             return self.__divmod__(fixed(other))
     # other // self, other % self
     def __rdivmod__(self, other):
         return fixed(other).__divmod__(self)

     # self // other
     def __floordiv__(self, other):
         return self.__divmod__(other)[0]
     # other // self
     def __rfloordiv__(self, other):
         return self.__rdivmod__(other)[0]

     # self % other
     def __mod__(self, other):
         return self.__divmod__(other)[1]
     # other % self
     def __rmod__(self, other):
         return self.__rdivmod__(other)[1]

     # self ** other
     def __pow__(self, other):
         if isinstance(other, (int, long)):
             result = fixed(self)
             result._value = _shiftdecimal(self._value ** other,
                                                 self._precision * 
(other - 1))
             return result
         else:
             raise TypeError, "Exponent must be int or long."

     # return percentage of self according to rate
     def percent(self, rate):
         """Return percentage according to rate.

         x.percent(rate) = x * rate / 100

         The result is returned as fixed with the same precision as self.
         """
         if isinstance(rate, fixed):
             result = fixed(self)
             f = 10L ** (rate._precision + 2)
             result._value = _divdecimal(self._value * rate._value, f)
             return result
         else:
             return self.percent(fixed(rate))

     # return net value of self according to rate
     def net(self, rate):
         """Return net value according to rate.

         x.net(rate) = x * 100 / (100 + rate)
         The result is returned as fixed with the same precision as self.
         """
         if isinstance(rate, fixed):
             result = fixed(self)
             f = 10L ** (rate._precision + 2)
             result._value = _divdecimal(f * self._value, f + rate._value)
             return result
         else:
             return self.net(fixed(rate))

     # return gross value of self according to rate
     def gross(self, rate):
         """Return gross value according to rate.

         x.gross(rate) = x * (100 + rate) / 100
         The result is returned as fixed with the same precision as self.
         """
         if isinstance(rate, fixed):
             result = fixed(self)
             f = 10L ** (rate._precision + 2)
             result._value = _divdecimal(self._value * (f + rate._value), f)
             return result
         else:
             return self.gross(fixed(rate))

# functions:

# return percent value according to rate
def percent(rate):
     """Return percent value according to rate.

     percent(rate) = rate / 100
     The result is returned as fixed with precision of rate increased by 2.
     """
     if isinstance(rate, fixed):
         r = rate
     else:
         r = fixed(rate)
     r._precision += 2
     return r

# helper functions:

# divide x by y, return rounded result
def _divdecimal(x, y):
     i, r = divmod(x, y)
     c = cmp(abs(2*r), abs(y))
     if c > 0 or (c == 0 and i >= 0):    # r > y/2 or r = y/2 and i not 
negative
         return i + 1
     else:
         return i

# shift decimal point left, return rounded result
def _shiftdecimal(x, n):
     if n == 0:
         return x
     return _divdecimal(x, 10L ** n)

# parse string
import re
_pattern = r"""
             \s*
             (?P<sign>[+|-])?
             (
                 (?P<int>\d+)(\.(?P<frac>\d*))?
                 |
                 \.(?P<onlyfrac>\d+)
             )
             ([eE](?P<exp>[+|-]?\d+))?
             \s*$
             """
_parse = re.compile(_pattern, re.VERBOSE).match
del re

def _string2exact(s):
     m = _parse(s)
     if m is None:
         return None, None
     ep = m.group('exp')
     if ep:
         e = int(ep)
     else:
         e = 0
     ip = m.group('int')
     if ip:
         fp = m.group('frac')
     else:
         fp = m.group('onlyfrac')
     if ip:
         i = long(ip)
     else:
         i = 0L
     if fp:
         f = long(fp)
         n = len(fp)
     else:
         f = 0L
         n = 0
     i = i * 10 ** n + f
     e -= n
     if m.group('sign') == '-':
         i = -i
     return i, e




More information about the Python-list mailing list