# -*- coding: UTF-8 -*-
# mathlib.py, generic math library wrapper by Reinier Heeres <reinier@heeres.eu>
#
# 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))