# -*- coding: UTF-8 -*- # mathlib.py, generic math library wrapper by Reinier Heeres # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU 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 GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # # Change log: # 2007-07-03: rwh, first version import types import inspect import math from decimal import Decimal from rational import Rational import random import logging _logger = logging.getLogger('MathLib') from gettext import gettext as _ import locale class MathLib: ANGLE_DEG = math.pi/180 ANGLE_RAD = 1 ANGLE_GRAD = 1 FORMAT_EXPONENT = 1 FORMAT_SCIENTIFIC = 2 def __init__(self): self.set_angle_type(self.ANGLE_DEG) self.set_format_type(self.FORMAT_SCIENTIFIC) self.set_digit_limit(9) self._setup_i18n() def _setup_i18n(self): loc = locale.localeconv() # The separator to mark thousands (default: ',') self.thousand_sep = loc['thousands_sep'] if self.thousand_sep == "" or self.thousand_sep == None: self.thousand_sep = "," # The separator to mark fractions (default: '.') self.fraction_sep = loc['decimal_point'] if self.fraction_sep == "" or self.fraction_sep == None: self.fraction_sep = "." # TRANS: multiplication symbol (default: '*') self.mul_sym = _('mul_sym') if len(self.mul_sym) == 0 or len(self.mul_sym) > 3: self.mul_sym = '*' # TRANS: division symbol (default: '/') self.div_sym = _('div_sym') if len(self.div_sym) == 0 or len(self.div_sym) > 3: self.div_sym = '/' def set_angle_type(self, type): self.angle_scaling = self.d(type) _logger.debug('Angle type set to %s', self.angle_scaling) def set_format_type(self, fmt, digit_limit=9): self.format_type = fmt _logger.debug('Format type set to %s', fmt) def set_digit_limit(self, digits): self.digit_limit = digits _logger.debug('Digit limit set to %s', digits) def d(self, val): if isinstance(val, Decimal): return val elif type(val) in (types.IntType, types.LongType): return Decimal(val) elif type(val) == types.StringType: d = Decimal(val) return d.normalize() elif type(val) is types.FloatType or hasattr(val, '__float__'): s = '%.18e' % float(val) d = Decimal(s) return d.normalize() else: return None def parse_number(self, s): s = s.replace(self.fraction_sep, '.') try: d = Decimal(s) if self.is_int(d): return int(d) else: return Decimal(s) except Exception, inst: return None def format_number(self, n): if type(n) is types.BooleanType: if n: return 'True' else: return 'False' elif type(n) is types.StringType: return n elif type(n) is types.UnicodeType: return n elif type(n) is types.NoneType: return _('Undefined') elif type(n) is types.IntType: n = self.d(n) elif type(n) is types.FloatType: n = self.d(n) elif type(n) is types.LongType: n = self.d(n) elif isinstance(n, Rational): n = self.d(float(n)) elif not isinstance(n, Decimal): return _('Error: unsupported type') (sign, digits, exp) = n.as_tuple() if len(digits) > self.digit_limit: exp += len(digits) - self.digit_limit digits = digits[:self.digit_limit] if sign: res = "-" else: res = "" int_len = len(digits) + exp if int_len == 0: if exp < -self.digit_limit: disp_exp = exp + len(digits) else: disp_exp = 0 elif -self.digit_limit < int_len < self.digit_limit: disp_exp = 0 else: disp_exp = int_len - 1 dot_pos = int_len - disp_exp # _logger.debug('len(digits) %d, exp: %d, int_len: %d, disp_exp: %d, dot_pos: %d', len(digits), exp, int_len, disp_exp, dot_pos) if dot_pos < 0: res = '0' + self.fraction_sep for i in xrange(dot_pos, 0): res += '0' for i in xrange(len(digits)): if i == dot_pos: if i == 0: res += '0' + self.fraction_sep else: res += self.fraction_sep res += str(digits[i]) if int_len > 0 and len(digits) < dot_pos: for i in xrange(len(digits), dot_pos): res += '0' if disp_exp != 0: if self.format_type == self.FORMAT_EXPONENT: res = res + 'e%d' % disp_exp elif self.format_type == self.FORMAT_SCIENTIFIC: res = res + u'×10**%d' % disp_exp return res def short_format(self, n): ret = self.format_number(n) if len(ret) > 7: ret = "%1.1e" % n return ret def is_int(self, n): if type(n) is types.IntType or type(n) is types.LongType: return True if not isinstance(n, Decimal): n = self.d(n) if n is None: return False (sign, d, e) = n.normalize().as_tuple() return e >= 0 if __name__ == "__main__": ml = MathLib() val = 0.99999999999999878 print 'is_int(%.18e): %s' % (val, ml.is_int(val)) # Beyond float precision val = 0.999999999999999999 print 'is_int(%.18e): %s' % (val, ml.is_int(val)) val = ml.d(0.99999999999999878)**2 print 'is_int(%s): %s' % (val, ml.is_int(val))