From bd0de82a8ef6ab5b9545ea4c01d721d70157c1b9 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 5 Nov 2017 01:10:22 -0500 Subject: [PATCH] remove repo version of FloatSpin, go with wx bundled version --- gui/characterEditor.py | 5 +- gui/utils/floatspin.py | 1666 ---------------------------------------- 2 files changed, 4 insertions(+), 1667 deletions(-) delete mode 100644 gui/utils/floatspin.py diff --git a/gui/characterEditor.py b/gui/characterEditor.py index 20555d488..ce2841764 100644 --- a/gui/characterEditor.py +++ b/gui/characterEditor.py @@ -21,7 +21,7 @@ import wx import wx.dataview import wx.lib.agw.hyperlink -from .utils.floatspin import FloatSpin + # noinspection PyPackageRequirements import wx.lib.newevent # noinspection PyPackageRequirements @@ -36,6 +36,9 @@ from service.character import Character from service.network import AuthenticationError, TimeoutError from service.market import Market from logbook import Logger + +from wx.lib.agw.floatspin import FloatSpin + pyfalog = Logger(__name__) diff --git a/gui/utils/floatspin.py b/gui/utils/floatspin.py deleted file mode 100644 index 2a636433a..000000000 --- a/gui/utils/floatspin.py +++ /dev/null @@ -1,1666 +0,0 @@ -# --------------------------------------------------------------------------- # -# FLOATSPIN Control wxPython IMPLEMENTATION -# Python Code By: -# -# Andrea Gavana, @ 16 Nov 2005 -# Latest Revision: 03 Jan 2014, 23.00 GMT -# -# -# TODO List/Caveats -# -# 1. Ay Idea? -# -# For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please -# Write To Me At: -# -# andrea.gavana@gmail.com -# andrea.gavana@maerskoil.com -# -# Or, Obviously, To The wxPython Mailing List!!! -# -# -# End Of Comments -# --------------------------------------------------------------------------- # - - -""" -:class:`FloatSpin` implements a floating point :class:`SpinCtrl`. - - -Description -=========== - -:class:`FloatSpin` implements a floating point :class:`SpinCtrl`. It is built using a custom -:class:`PyControl`, composed by a :class:`TextCtrl` and a :class:`SpinButton`. In order to -correctly handle floating points numbers without rounding errors or non-exact -floating point representations, :class:`FloatSpin` uses the great :class:`FixedPoint` class -from Tim Peters. - -What you can do: - -- Set the number of representative digits for your floating point numbers; -- Set the floating point format (``%f``, ``%F``, ``%e``, ``%E``, ``%g``, ``%G``); -- Set the increment of every ``EVT_FLOATSPIN`` event; -- Set minimum, maximum values for :class:`FloatSpin` as well as its range; -- Change font and colour for the underline :class:`TextCtrl`. - - -Usage -===== - -Usage example:: - - import wx - import wx.lib.agw.floatspin as FS - - class MyFrame(wx.Frame): - - def __init__(self, parent): - - wx.Frame.__init__(self, parent, -1, "FloatSpin Demo") - - panel = wx.Panel(self) - - floatspin = FS.FloatSpin(panel, -1, pos=(50, 50), min_val=0, max_val=1, - increment=0.01, value=0.1, agwStyle=FS.FS_LEFT) - floatspin.SetFormat("%f") - floatspin.SetDigits(2) - - - # our normal wxApp-derived class, as usual - - app = wx.App(0) - - frame = MyFrame(None) - app.SetTopWindow(frame) - frame.Show() - - app.MainLoop() - - - -Events -====== - -:class:`FloatSpin` catches 3 different types of events: - -1) Spin events: events generated by spinning up/down the spinbutton; -2) Char events: playing with up/down arrows of the keyboard increase/decrease - the value of :class:`FloatSpin`; -3) Mouse wheel event: using the wheel will change the value of :class:`FloatSpin`. - -In addition, there are some other functionalities: - -- It remembers the initial value as a default value, call meth:~FloatSpin.SetToDefaultValue`, or - press ``Esc`` to return to it; -- ``Shift`` + arrow = 2 * increment (or ``Shift`` + mouse wheel); -- ``Ctrl`` + arrow = 10 * increment (or ``Ctrl`` + mouse wheel); -- ``Alt`` + arrow = 100 * increment (or ``Alt`` + mouse wheel); -- Combinations of ``Shift``, ``Ctrl``, ``Alt`` increment the :class:`FloatSpin` value by the - product of the factors; -- ``PgUp`` & ``PgDn`` = 10 * increment * the product of the ``Shift``, ``Ctrl``, ``Alt`` - factors; -- ``Space`` sets the control's value to it's last valid state. - - -Window Styles -============= - -This class supports the following window styles: - -=============== =========== ================================================== -Window Styles Hex Value Description -=============== =========== ================================================== -``FS_READONLY`` 0x1 Sets :class:`FloatSpin` as read-only control. -``FS_LEFT`` 0x2 Horizontally align the underlying :class:`TextCtrl` on the left. -``FS_CENTRE`` 0x4 Horizontally align the underlying :class:`TextCtrl` on center. -``FS_RIGHT`` 0x8 Horizontally align the underlying :class:`TextCtrl` on the right. -=============== =========== ================================================== - - -Events Processing -================= - -This class processes the following events: - -================= ================================================== -Event Name Description -================= ================================================== -``EVT_FLOATSPIN`` Emitted when the user changes the value of :class:`FloatSpin`, either with the mouse or with the keyboard. -================= ================================================== - - -License And Version -=================== - -:class:`FloatSpin` control is distributed under the wxPython license. - -Latest revision: Andrea Gavana @ 03 Jan 2014, 23.00 GMT - -Version 0.9 - - -Backward Incompatibilities -========================== - -Modifications to allow `min_val` or `max_val` to be ``None`` done by: - -James Bigler, -SCI Institute, University of Utah, -March 14, 2007 - -:note: Note that the changes I made will break backward compatibility, - because I changed the contructor's parameters from `min` / `max` to - `min_val` / `max_val` to be consistent with the other functions and to - eliminate any potential confusion with the built in `min` and `max` - functions. - -You specify open ranges like this (you can equally do this in the -constructor):: - - SetRange(min_val=1, max_val=None) # [1, ] - SetRange(min_val=None, max_val=0) # [ , 0] - -or no range:: - - SetRange(min_val=None, max_val=None) # [ , ] - -""" - -# ---------------------------------------------------------------------- -# Beginning Of FLOATSPIN wxPython Code -# ---------------------------------------------------------------------- - -import wx -import locale -from math import ceil, floor - -# Set The Styles For The Underline wx.TextCtrl -FS_READONLY = 1 -""" Sets :class:`FloatSpin` as read-only control. """ -FS_LEFT = 2 -""" Horizontally align the underlying :class:`TextCtrl` on the left. """ -FS_CENTRE = 4 -""" Horizontally align the underlying :class:`TextCtrl` on center. """ -FS_RIGHT = 8 -""" Horizontally align the underlying :class:`TextCtrl` on the right. """ - -# Define The FloatSpin Event -wxEVT_FLOATSPIN = wx.NewEventType() - -# -----------------------------------# -# FloatSpinEvent -# -----------------------------------# - -EVT_FLOATSPIN = wx.PyEventBinder(wxEVT_FLOATSPIN, 1) -""" Emitted when the user changes the value of :class:`FloatSpin`, either with the mouse or""" \ -""" with the keyboard. """ - - -def _cmp(self, a, b): - return (a>b)-(a 0: - self.SetValue(self._value + self._increment * modifier) - self.DoSendEvent() - - else: - - self.SetValue(self._value - self._increment * modifier) - self.DoSendEvent() - - def OnSize(self, event): - """ - Handles the ``wx.EVT_SIZE`` event for :class:`FloatSpin`. - - :param `event`: a :class:`SizeEvent` event to be processed. - - :note: This method resizes the text control and reposition the spin button when - resized. - """ - # start Philip Semanchuk addition - event_width = event.GetSize().width - - self._textctrl.SetPosition((self._text_left, self._text_top)) - - text_width, text_height = self._textctrl.GetSizeTuple() - - spin_width, _ = self._spinbutton.GetSizeTuple() - - text_width = event_width - (spin_width + self._gap + self._text_left) - - self._textctrl.SetSize(wx.Size(text_width, event.GetSize().height)) - - # The spin button is always snug against the right edge of the - # control. - self._spinbutton.SetPosition((event_width - spin_width, self._spin_top)) - - event.Skip() - # end Philip Semanchuk addition - - def ReplaceDoubleZero(self, strs): - """ - Replaces the (somewhat) python ugly `+e000` with `+e00`. - - :param `strs`: a string (possibly) containing a `+e00` substring. - """ - - if self._textformat not in ["%g", "%e", "%E", "%G"]: - return strs - - if strs.find("e+00") >= 0: - strs = strs.replace("e+00", "e+0") - elif strs.find("e-00") >= 0: - strs = strs.replace("e-00", "e-0") - elif strs.find("E+00") >= 0: - strs = strs.replace("E+00", "E+0") - elif strs.find("E-00") >= 0: - strs = strs.replace("E-00", "E-0") - - return strs - - def SetValue(self, value): - """ - Sets the :class:`FloatSpin` value. - - :param `value`: the new value. - """ - if not self._textctrl or not self.InRange(value): - return - - if self._snapticks and self._increment != 0.0: - - finite, snap_value = self.IsFinite(value) - - if not finite: # FIXME What To Do About A Failure? - - if (snap_value - floor(snap_value) < ceil(snap_value) - snap_value): - value = self._defaultvalue + floor(snap_value) * self._increment - else: - value = self._defaultvalue + ceil(snap_value) * self._increment - - decimal = locale.localeconv()["decimal_point"] - strs = ("%100." + str(self._digits) + self._textformat[1]) % value - strs = strs.replace(".", decimal) - strs = strs.strip() - strs = self.ReplaceDoubleZero(strs) - - if value != self._value or strs != self._textctrl.GetValue(): - self._textctrl.SetValue(strs) - self._textctrl.DiscardEdits() - self._value = value - - def GetValue(self): - """ Returns the :class:`FloatSpin` value. """ - - return float(self._value) - - def SetRangeDontClampValue(self, min_val, max_val): - """ - Sets the allowed range. - - :param `min_val`: the minimum value for :class:`FloatSpin`. If it is ``None`` it is - ignored; - :param `max_val`: the maximum value for :class:`FloatSpin`. If it is ``None`` it is - ignored. - - :note: This method doesn't modify the current value. - """ - - if (min_val != None): - self._min = FixedPoint(str(min_val), 20) - else: - self._min = None - if (max_val != None): - self._max = FixedPoint(str(max_val), 20) - else: - self._max = None - - def SetRange(self, min_val, max_val): - """ - Sets the allowed range. - - :param `min_val`: the minimum value for :class:`FloatSpin`. If it is ``None`` it is - ignored; - :param `max_val`: the maximum value for :class:`FloatSpin`. If it is ``None`` it is - ignored. - - :note: This method doesn't modify the current value. - - :note: You specify open ranges like this (you can equally do this in the - constructor):: - - SetRange(min_val=1, max_val=None) - SetRange(min_val=None, max_val=0) - - - or no range:: - - SetRange(min_val=None, max_val=None) - - """ - - self.SetRangeDontClampValue(min_val, max_val) - - value = self.ClampValue(self._value) - if (value != self._value): - self.SetValue(value) - - def ClampValue(self, var): - """ - Clamps `var` between `_min` and `_max` depending if the range has - been specified. - - :param `var`: the value to be clamped. - - :return: A clamped copy of `var`. - """ - - if (self._min != None): - if (var < self._min): - var = self._min - return var - - if (self._max != None): - if (var > self._max): - var = self._max - - return var - - def SetIncrement(self, increment): - """ - Sets the increment for every ``EVT_FLOATSPIN`` event. - - :param `increment`: a floating point number specifying the :class:`FloatSpin` increment. - """ - - if increment < 1. / 10.0 ** self._digits: - raise Exception("\nERROR: Increment Should Be Greater Or Equal To 1/(10**digits).") - - self._increment = FixedPoint(str(increment), 20) - self.SetValue(self._value) - - def GetIncrement(self): - """ Returns the increment for every ``EVT_FLOATSPIN`` event. """ - - return self._increment - - def SetDigits(self, digits=-1): - """ - Sets the number of digits to show. - - :param `digits`: the number of digits to show. If `digits` < 0, :class:`FloatSpin` - tries to calculate the best number of digits based on input values passed - in the constructor. - """ - - if digits < 0: - incr = str(self._increment) - if incr.find(".") < 0: - digits = 0 - else: - digits = len(incr[incr.find(".") + 1:]) - - self._digits = digits - - self.SetValue(self._value) - - def GetDigits(self): - """ Returns the number of digits shown. """ - - return self._digits - - def SetFormat(self, fmt="%f"): - """ - Set the string format to use. - - :param `fmt`: the new string format to use. One of the following strings: - - ====== ================================= - Format Description - ====== ================================= - 'e' Floating point exponential format (lowercase) - 'E' Floating point exponential format (uppercase) - 'f' Floating point decimal format - 'F' Floating point decimal format - 'g' Floating point format. Uses lowercase exponential format if exponent is less than -4 or not less than precision, decimal format otherwise - 'G' Floating point format. Uses uppercase exponential format if exponent is less than -4 or not less than precision, decimal format otherwise - ====== ================================= - - """ - - if fmt not in ["%f", "%g", "%e", "%E", "%F", "%G"]: - raise Exception('\nERROR: Bad Float Number Format: ' + repr(fmt) + '. It Should Be ' \ - 'One Of "%f", "%g", "%e", "%E", "%F", "%G"') - - self._textformat = fmt - - if self._digits < 0: - self.SetDigits() - - self.SetValue(self._value) - - def GetFormat(self): - """ - Returns the string format in use. - - :see: :meth:`~FloatSpin.SetFormat` for a list of valid string formats. - """ - - return self._textformat - - def SetDefaultValue(self, defaultvalue): - """ - Sets the :class:`FloatSpin` default value. - - :param `defaultvalue`: a floating point value representing the new default - value for :class:`FloatSpin`. - """ - - if self.InRange(defaultvalue): - self._defaultvalue = FixedPoint(str(defaultvalue), 20) - - def GetDefaultValue(self): - """ Returns the :class:`FloatSpin` default value. """ - - return self._defaultvalue - - def IsDefaultValue(self): - """ Returns whether the current value is the default value or not. """ - - return self._value == self._defaultvalue - - def SetToDefaultValue(self): - """ Sets :class:`FloatSpin` value to its default value. """ - - self.SetValue(self._defaultvalue) - - def SetSnapToTicks(self, forceticks=True): - """ - Force the value to always be divisible by the increment. Initially ``False``. - - :param `forceticks`: ``True`` to force the snap to ticks option, ``False`` otherwise. - - :note: This uses the default value as the basis, you will get strange results - for very large differences between the current value and default value - when the increment is very small. - """ - - if self._snapticks != forceticks: - self._snapticks = forceticks - self.SetValue(self._value) - - def GetSnapToTicks(self): - """ Returns whether the snap to ticks option is active or not. """ - - return self._snapticks - - def OnFocus(self, event): - """ - Handles the ``wx.EVT_SET_FOCUS`` event for :class:`FloatSpin`. - - :param `event`: a :class:`FocusEvent` event to be processed. - """ - - if self._textctrl: - self._textctrl.SetFocus() - - event.Skip() - - def OnKillFocus(self, event): - """ - Handles the ``wx.EVT_KILL_FOCUS`` event for :class:`FloatSpin`. - - :param `event`: a :class:`FocusEvent` event to be processed. - """ - - self.SyncSpinToText(True) - event.Skip() - - def SyncSpinToText(self, send_event=True, force_valid=True): - """ - Synchronize the underlying :class:`TextCtrl` with :class:`SpinButton`. - - :param `send_event`: ``True`` to send a ``EVT_FLOATSPIN`` event, ``False`` - otherwise; - :param `force_valid`: ``True`` to force a valid value (i.e. inside the - provided range), ``False`` otherwise. - """ - - if not self._textctrl: - return - - curr = self._textctrl.GetValue() - curr = curr.strip() - decimal = locale.localeconv()["decimal_point"] - curr = curr.replace(decimal, ".") - - if curr: - try: - curro = float(curr) - curr = FixedPoint(curr, 20) - except: - self.SetValue(self._value) - return - - if force_valid or not self.HasRange() or self.InRange(curr): - - if force_valid and self.HasRange(): - curr = self.ClampValue(curr) - - if self._value != curr: - self.SetValue(curr) - - if send_event: - self.DoSendEvent() - - elif force_valid: - - # textctrl is out of sync, discard and reset - self.SetValue(self.GetValue()) - - def SetFont(self, font=None): - """ - Sets the underlying :class:`TextCtrl` font. - - :param `font`: a valid instance of :class:`Font`. - """ - - if font is None: - font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) - - if not self._textctrl: - return False - - return self._textctrl.SetFont(font) - - def GetFont(self): - """ Returns the underlying :class:`TextCtrl` font. """ - - if not self._textctrl: - return self.GetFont() - - return self._textctrl.GetFont() - - def GetMin(self): - """ - Returns the minimum value for :class:`FloatSpin`. It can be a - number or ``None`` if no minimum is present. - """ - - return self._min - - def GetMax(self): - """ - Returns the maximum value for :class:`FloatSpin`. It can be a - number or ``None`` if no minimum is present. - """ - - return self._max - - def HasRange(self): - """ Returns whether :class:`FloatSpin` range has been set or not. """ - - return (self._min != None) or (self._max != None) - - def InRange(self, value): - """ - Returns whether a value is inside :class:`FloatSpin` range. - - :param `value`: the value to test. - """ - - if (not self.HasRange()): - return True - if (self._min != None): - if (value < self._min): - return False - if (self._max != None): - if (value > self._max): - return False - return True - - def GetTextCtrl(self): - """ Returns the underlying :class:`TextCtrl`. """ - - return self._textctrl - - def IsFinite(self, value): - """ - Tries to determine if a value is finite or infinite/NaN. - - :param `value`: the value to test. - """ - - try: - snap_value = (value - self._defaultvalue) / self._increment - finite = True - except: - finite = False - snap_value = None - - return finite, snap_value - - -# Class FixedPoint, version 0.0.4. -# Released to the public domain 28-Mar-2001, -# by Tim Peters (tim.one@home.com). - -# Provided as-is; use at your own risk; no warranty; no promises; enjoy! - - -# 28-Mar-01 ver 0.0,4 -# Use repr() instead of str() inside __str__, because str(long) changed -# since this was first written (used to produce trailing "L", doesn't -# now). -# -# 09-May-99 ver 0,0,3 -# Repaired __sub__(FixedPoint, string); was blowing up. -# Much more careful conversion of float (now best possible). -# Implemented exact % and divmod. -# -# 14-Oct-98 ver 0,0,2 -# Added int, long, frac. Beefed up docs. Removed DECIMAL_POINT -# and MINUS_SIGN globals to discourage bloating this class instead -# of writing formatting wrapper classes (or subclasses) -# -# 11-Oct-98 ver 0,0,1 -# posted to c.l.py - -__version__ = 0, 0, 4 - -# The default value for the number of decimal digits carried after the -# decimal point. This only has effect at compile-time. -DEFAULT_PRECISION = 2 -""" The default value for the number of decimal digits carried after the decimal point. This only has effect at compile-time. """ - - -class FixedPoint(object): - """ - FixedPoint objects support decimal arithmetic with a fixed number of - digits (called the object's precision) after the decimal point. The - number of digits before the decimal point is variable & unbounded. - - The precision is user-settable on a per-object basis when a FixedPoint - is constructed, and may vary across FixedPoint objects. The precision - may also be changed after construction via `FixedPoint.set_precision(p)`. - Note that if the precision of a FixedPoint is reduced via :meth:`FixedPoint.set_precision() `, - information may be lost to rounding. - - Example:: - - >>> x = FixedPoint("5.55") # precision defaults to 2 - >>> print x - 5.55 - >>> x.set_precision(1) # round to one fraction digit - >>> print x - 5.6 - >>> print FixedPoint("5.55", 1) # same thing setting to 1 in constructor - 5.6 - >>> repr(x) # returns constructor string that reproduces object exactly - "FixedPoint('5.6', 1)" - >>> - - - When :class:`FixedPoint` objects of different precision are combined via + - * /, - the result is computed to the larger of the inputs' precisions, which also - becomes the precision of the resulting :class:`FixedPoint` object. Example:: - - >>> print FixedPoint("3.42") + FixedPoint("100.005", 3) - 103.425 - >>> - - - When a :class:`FixedPoint` is combined with other numeric types (ints, floats, - strings representing a number) via + - * /, then similarly the computation - is carried out using -- and the result inherits -- the :class:`FixedPoint`'s - precision. Example:: - - >>> print FixedPoint(1) / 7 - 0.14 - >>> print FixedPoint(1, 30) / 7 - 0.142857142857142857142857142857 - >>> - - - The string produced by `str(x)` (implictly invoked by `print`) always - contains at least one digit before the decimal point, followed by a - decimal point, followed by exactly `x.get_precision()` digits. If `x` is - negative, `str(x)[0] == "-"`. - - The :class:`FixedPoint` constructor can be passed an int, long, string, float, - :class:`FixedPoint`, or any object convertible to a float via `float()` or to a - long via `long()`. Passing a precision is optional; if specified, the - precision must be a non-negative int. There is no inherent limit on - the size of the precision, but if very very large you'll probably run - out of memory. - - Note that conversion of floats to :class:`FixedPoint` can be surprising, and - should be avoided whenever possible. Conversion from string is exact - (up to final rounding to the requested precision), so is greatly - preferred. Example:: - - >>> print FixedPoint(1.1e30) - 1099999999999999993725589651456.00 - >>> print FixedPoint("1.1e30") - 1100000000000000000000000000000.00 - >>> - - - """ - - # the exact value is self.n / 10**self.p; - # self.n is a long; self.p is an int - - def __init__(self, value=0, precision=DEFAULT_PRECISION): - """ - Default class constructor. - - :param `value`: the initial value; - :param `precision`: must be an int >= 0, and defaults to ``DEFAULT_PRECISION``. - """ - - self.n = self.p = 0 - self.set_precision(precision) - p = self.p - - if isinstance(value, type("42.3e5")): - n, exp = _string2exact(value) - # exact value is n*10**exp = n*10**(exp+p)/10**p - effective_exp = exp + p - if effective_exp > 0: - n = n * _tento(effective_exp) - elif effective_exp < 0: - n = _roundquotient(n, _tento(-effective_exp)) - self.n = n - return - - if isinstance(value, type(42)) or isinstance(value, type(42)): - self.n = int(value) * _tento(p) - return - - if isinstance(value, FixedPoint): - temp = value.copy() - temp.set_precision(p) - self.n, self.p = temp.n, temp.p - return - - if isinstance(value, type(42.0)): - # XXX ignoring infinities and NaNs and overflows for now - import math - f, e = math.frexp(abs(value)) - assert f == 0 or 0.5 <= f < 1.0 - # |value| = f * 2**e exactly - - # Suck up CHUNK bits at a time; 28 is enough so that we suck - # up all bits in 2 iterations for all known binary double- - # precision formats, and small enough to fit in an int. - CHUNK = 28 - top = 0 - # invariant: |value| = (top + f) * 2**e exactly - while f: - f = math.ldexp(f, CHUNK) - digit = int(f) - assert digit >> CHUNK == 0 - top = (top << CHUNK) | digit - f = f - digit - assert 0.0 <= f < 1.0 - e = e - CHUNK - - # now |value| = top * 2**e exactly - # want n such that n / 10**p = top * 2**e, or - # n = top * 10**p * 2**e - top = top * _tento(p) - if e >= 0: - n = top << e - else: - n = _roundquotient(top, 1 << -e) - if value < 0: - n = -n - self.n = n - return - - if isinstance(value, type(42 - 42j)): - raise TypeError("can't convert complex to FixedPoint: " + - repr(value)) - - # can we coerce to a float? - yes = 1 - try: - asfloat = float(value) - except: - yes = 0 - if yes: - self.__init__(asfloat, p) - return - - # similarly for long - yes = 1 - try: - aslong = int(value) - except: - yes = 0 - if yes: - self.__init__(aslong, p) - return - - raise TypeError("can't convert to FixedPoint: " + repr(value)) - - def get_precision(self): - """ - Return the precision of this :class:`FixedPoint`. - - :note: The precision is the number of decimal digits carried after - the decimal point, and is an int >= 0. - """ - - return self.p - - def set_precision(self, precision=DEFAULT_PRECISION): - """ - Change the precision carried by this :class:`FixedPoint` to `precision`. - - :param `precision`: must be an int >= 0, and defaults to - ``DEFAULT_PRECISION``. - - :note: If `precision` is less than this :class:`FixedPoint`'s current precision, - information may be lost to rounding. - """ - - try: - p = int(precision) - except: - raise TypeError("precision not convertable to int: " + - repr(precision)) - if p < 0: - raise ValueError("precision must be >= 0: " + repr(precision)) - - if p > self.p: - self.n = self.n * _tento(p - self.p) - elif p < self.p: - self.n = _roundquotient(self.n, _tento(self.p - p)) - self.p = p - - def __str__(self): - - n, p = self.n, self.p - i, f = divmod(abs(n), _tento(p)) - if p: - frac = repr(f)[:-1] - frac = "0" * (p - len(frac)) + frac - else: - frac = "" - return "-"[:n < 0] + \ - repr(i)[:-1] + \ - "." + frac - - def __repr__(self): - - return "FixedPoint" + repr((str(self), self.p)) - - def copy(self): - """ Create a copy of the current :class:`FixedPoint`. """ - - return _mkFP(self.n, self.p) - - __copy__ = __deepcopy__ = copy - - def __cmp__(self, other): - - if (other == None): - return 1 - xn, yn, p = _norm(self, other) - return _cmp(xn, yn) - - def __hash__(self): - # caution! == values must have equal hashes, and a FixedPoint - # is essentially a rational in unnormalized form. There's - # really no choice here but to normalize it, so hash is - # potentially expensive. - n, p = self.__reduce() - - # Obscurity: if the value is an exact integer, p will be 0 now, - # so the hash expression reduces to hash(n). So FixedPoints - # that happen to be exact integers hash to the same things as - # their int or long equivalents. This is Good. But if a - # FixedPoint happens to have a value exactly representable as - # a float, their hashes may differ. This is a teensy bit Bad. - return hash(n) ^ hash(p) - - def __bool__(self): - return self.n != 0 - - def __neg__(self): - return _mkFP(-self.n, self.p) - - def __abs__(self): - if self.n >= 0: - return self.copy() - else: - return -self - - def __add__(self, other): - n1, n2, p = _norm(self, other) - # n1/10**p + n2/10**p = (n1+n2)/10**p - return _mkFP(n1 + n2, p) - - __radd__ = __add__ - - def __sub__(self, other): - if not isinstance(other, FixedPoint): - other = FixedPoint(other, self.p) - return self.__add__(-other) - - def __rsub__(self, other): - return (-self) + other - - def __mul__(self, other): - n1, n2, p = _norm(self, other) - # n1/10**p * n2/10**p = (n1*n2/10**p)/10**p - return _mkFP(_roundquotient(n1 * n2, _tento(p)), p) - - __rmul__ = __mul__ - - def __div__(self, other): - n1, n2, p = _norm(self, other) - if n2 == 0: - raise ZeroDivisionError("FixedPoint division") - if n2 < 0: - n1, n2 = -n1, -n2 - # n1/10**p / (n2/10**p) = n1/n2 = (n1*10**p/n2)/10**p - return _mkFP(_roundquotient(n1 * _tento(p), n2), p) - - def __rdiv__(self, other): - n1, n2, p = _norm(self, other) - return _mkFP(n2, p) / self - - def __divmod__(self, other): - n1, n2, p = _norm(self, other) - if n2 == 0: - raise ZeroDivisionError("FixedPoint modulo") - # floor((n1/10**p)/(n2*10**p)) = floor(n1/n2) - q = n1 / n2 - # n1/10**p - q * n2/10**p = (n1 - q * n2)/10**p - return q, _mkFP(n1 - q * n2, p) - - def __rdivmod__(self, other): - n1, n2, p = _norm(self, other) - return divmod(_mkFP(n2, p), self) - - def __mod__(self, other): - return self.__divmod__(other)[1] - - def __rmod__(self, other): - n1, n2, p = _norm(self, other) - return _mkFP(n2, p).__mod__(self) - - # caution! float can lose precision - def __float__(self): - n, p = self.__reduce() - return float(n) / float(_tento(p)) - - # XXX should this round instead? - # XXX note e.g. long(-1.9) == -1L and long(1.9) == 1L in Python - # XXX note that __int__ inherits whatever __long__ does, - # XXX and .frac() is affected too - def __long__(self): - answer = abs(self.n) / _tento(self.p) - if self.n < 0: - answer = -answer - return answer - - def __int__(self): - return int(self.__long__()) - - def frac(self): - """ - Returns fractional portion as a :class:`FixedPoint`. - - :note: In :class:`FixedPoint`, - - this equality holds true:: - - x = x.frac() + long(x) - - - """ - return self - int(self) - - # return n, p s.t. self == n/10**p and n % 10 != 0 - def __reduce(self): - n, p = self.n, self.p - if n == 0: - p = 0 - while p and n % 10 == 0: - p = p - 1 - n = n / 10 - return n, p - - -# return 10L**n - -def _tento(n, cache={}): - try: - return cache[n] - except KeyError: - answer = cache[n] = 10 ** n - return answer - - -# return xn, yn, p s.t. -# p = max(x.p, y.p) -# x = xn / 10**p -# y = yn / 10**p -# -# x must be FixedPoint to begin with; if y is not FixedPoint, -# it inherits its precision from x. -# -# Note that this is called a lot, so default-arg tricks are helpful. - -def _norm(x, y, isinstance=isinstance, FixedPoint=FixedPoint, - _tento=_tento): - assert isinstance(x, FixedPoint) - if not isinstance(y, FixedPoint): - y = FixedPoint(y, x.p) - xn, yn = x.n, y.n - xp, yp = x.p, y.p - if xp > yp: - yn = yn * _tento(xp - yp) - p = xp - elif xp < yp: - xn = xn * _tento(yp - xp) - p = yp - else: - p = xp # same as yp - return xn, yn, p - - -def _mkFP(n, p, FixedPoint=FixedPoint): - f = FixedPoint() - f.n = n - f.p = p - return f - - -# divide x by y, rounding to int via nearest-even -# y must be > 0 -# XXX which rounding modes are useful? - -def _roundquotient(x, y): - assert y > 0 - n, leftover = divmod(x, y) - c = _cmp(leftover << 1, y) - # c < 0 <-> leftover < y/2, etc - if c > 0 or (c == 0 and (n & 1) == 1): - n = n + 1 - return n - - -# crud for parsing strings -import re - -# There's an optional sign at the start, and an optional exponent -# at the end. The exponent has an optional sign and at least one -# digit. In between, must have either at least one digit followed -# by an optional fraction, or a decimal point followed by at least -# one digit. Yuck. - -_parser = re.compile(r""" - \s* - (?P[-+])? - ( - (?P\d+) (\. (?P\d*))? - | - \. (?P\d+) - ) - ([eE](?P[-+]? \d+))? - \s* $ -""", re.VERBOSE).match - -del re - - -# return n, p s.t. float string value == n * 10**p exactly - -def _string2exact(s): - m = _parser(s) - if m is None: - raise ValueError("can't parse as number: " + repr(s)) - - exp = m.group('exp') - if exp is None: - exp = 0 - else: - exp = int(exp) - - intpart = m.group('int') - if intpart is None: - intpart = "0" - fracpart = m.group('onlyfrac') - else: - fracpart = m.group('frac') - if fracpart is None or fracpart == "": - fracpart = "0" - assert intpart - assert fracpart - - i, f = int(intpart), int(fracpart) - nfrac = len(fracpart) - i = i * _tento(nfrac) + f - exp = exp - nfrac - - if m.group('sign') == "-": - i = -i - - return i, exp