monetary applications [was: Python GUI app to impress the bos s?]
Michael Amrhein
michael.amrhein at t-online.de
Sat Sep 21 11:45:05 EDT 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