Source code for upy.decimal2

# Copyright (c) 2008, 2010 Friedrich Romstedt <friedrichromstedt@gmail.com>
# See also <www.friedrichromstedt.org> (if e-mail has changed)
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

# Developed since: Aug 2008

import math

"""Supplies various operations with numbers in decimal system."""


[docs]class IntPlus: """Represents number in Z+ = Z + {Infinity}. Read-only attributes: .z - The number. .infinite - The instance represents Infinity."""
[docs] def __init__(self, z = None, infinite = None): """Initialise from number Z, or specify infinite as true.""" if infinite: self.infinite = True self.z = None else: self.z = z self.infinite = False
[docs] def __str__(self): if self.infinite: return "infinite" else: return str(self.z)
[docs] def __repr__(self): if self.infinite: return "IntPlus(infinite = True)" else: return "IntPlus(z = %r)" % self.z
[docs]class DecimalStrings: """Holds the result of formatting a number."""
[docs] def __init__(self, str_left, str_point, str_right): self.str_left = str_left self.str_point = str_point self.str_right = str_right
[docs] def __str__(self): return self.str_left + self.str_point + self.str_right
[docs]class DecimalNumber: """Calculates the leftmost digit, and formats real numbers. All values given to __init__() are read-write, except for .precision, which must be set via .set_precision(), and .exponent, which must be set via .set_exponent()."""
[docs] def __init__(self, value, precision = None, exponent = None, infinite_precision = None, enforce_sign = None, ceil = None, width_sign = None, width_left = None, width_point = None, width_right = None): """VALUE is the value to be formatted. INFINITE_PRECISION is the number of digits used when emulating infinite precision (default 15). EXPONENT is the power of ten separated up from the VALUE. Example: VALUE = 12.3 and EXPONENT = 1 -> 1.23e1. Strings returned represent .value_without_exponent up to digit with weight 10 ** PRECISION. If ENFORCE_SIGN is True, return a '+' in front of positive numbers. If CEIL is True, discarded portions of the number will always increase the value represented by the string. When calculating the parts of the string, WIDTH_* are used. By default IntPlus(infinite = True) is used as PRECISION. WIDTH_SIGN forces the sign to have a certain width, this means, the sign string will be padded with whitespace, right justified. WIDTH_LEFT is the width of the sign string plus the string before the point, right justified. WIDTH_POINT is the width of the point, center justified. WIDTH_RIGHT is the width of the post-point string, left justified.""" if exponent is None: exponent = 0 if infinite_precision is None: infinite_precision = -15 if width_sign is None: width_sign = 0 if width_left is None: width_left = 0 if width_point is None: width_point = 0 if width_right is None: width_right = 0 self.value = value self.infinite_precision = infinite_precision self.enforce_sign = enforce_sign self.ceil = ceil self.width_sign = width_sign self.width_left = width_left self.width_point = width_point self.width_right = width_right self.set_exponent(exponent) self.set_precision(precision)
[docs] def set_exponent(self, exponent): """Set the exponent to EXPONENT. It must be integer.""" self.exponent = exponent self.value_without_exponent = self.value * 10 ** (-exponent)
[docs] def set_precision(self, precision): """Set the precision to PRECISION. If it is an IntPlus instance, it will be taken over, else an IntPlus instance will be created.""" if isinstance(precision, IntPlus): self.precision = precision elif precision is None: self.precision = IntPlus(z = self.infinite_precision) else: self.precision = IntPlus(z = precision)
[docs] def get_leftmost_digit(self, guess = None): """The position of the leftmost digit of .value_without_exponent in decimal representation. A position of zero means, that the leftmost digit is the digit with weight 10 ** 0. Other return values RETURN mean, that the leftmost non-zero digit has weight 10 ** RETURN. Thus nonnegative RETURNs are before the point in "fixed-point" representation, and negative RETURNs will be behind the point. To obtain a real value with the leftmost non-zero digit at weight 1, use the expression VALUE * 10 ** (-RETURN). Note that the return value is an instance of class IntPlus, which may represent infinity too (in case .value_without_exponent == 0). You can supply an initial position of the search via GUESS.""" if self.value == 0: return IntPlus(infinite = True) # When we are at a certain digit position, the expression: # # 10 ** exponent # # represents this digit. Thus the expression: # # value % (10 ** exponent) # # gives the part of the value right of this digit. Example: For # VALUE = 12.3, and EXPONENT = 0, 12.3 % (10 ** 0) = 12.3 % 1 = 0.3. # Thus subtracting this expression eliminates all this parts of the # value, and leaves the part of the value non-right of the digit: # # value - value % (10 ** exponent) (1) # # For the example above: 12.3 - 12.3 % (10 ** 0) = 12.3 - 0.3 = 12.0 # Thus, the expression (1) is nonzero, if and only if there are # digits in VALUE non-right of the digit given by EXPONENT. # There are at each position two cases: # # 1. (1) is nonzero. There are digits non-right of the position. # This means, the digit is itself non-left of the leftmost digit. # 1. (1) is zero. There are no digits non-right of the position. # All digits are right of the position. This means, the digit is # left of the leftmost digit. # # Examples for VALUE = 12.3: EXPONENT = 2 -> zero, position left # of leftmost digit. EXPONENT = 1 -> nonzero, non-left. EXPONENT = 0 # -> nonzero, position 0 non-left of leftmost digit. # To ensure that the resulting position is /exactly/ at the left-most # digit, the following approach suffices: # # 1. Go right until (1) is nonzero. This means, go right until we # are non-left of the leftmost digit. # 2. Go left until (1) is zero. This means, go left until we are # left of the leftmost digit. # # Because after (1.), we are non-left of the leftmost digit, (2.) # breaks when we are at the rightmost position left of the digit, # which is /next left to/ the position of the digit. # # 3. Go one step right. # Find out the initial position ... if guess is None: # As a first guess, use the position at weight 1. exponent = 0 else: # Use the guess given. exponent = guess # Use abs() because the % operations do nonsense else ... value = abs(self.value_without_exponent) # Go right until (1) is nonzero ... while (value - value % (10 ** exponent)) == 0: exponent -= 1 # Go left until (1) is zero ... while (value - value % (10 ** exponent)) != 0: exponent += 1 # Go one step right ... exponent -= 1 return IntPlus(z = exponent)
[docs] def get_strings(self): """Return the string representing this DecimalNumber. Note that the string will represent .value_without_exponent, and not .value.""" # Calculate the value to use and the string of the sign ... if self.value_without_exponent < 0: abs_value = -self.value_without_exponent str_sign = '-' else: abs_value = self.value_without_exponent if self.enforce_sign: str_sign = '+' else: str_sign = '' str_sign = str_sign.rjust(self.width_sign) # Find out how many digits behind the point to display ... if self.precision.infinite: precision = self.infinite_precision else: precision = self.precision.z # Calculate the integer value to use as digit stream ... if not self.ceil: digitstream_number = int(round(abs_value * 10 ** (-precision))) else: digitstream_number = int(math.ceil(abs_value * 10 ** (-precision))) # Calculate the digit stream ... str_digitstream = str(digitstream_number) # Calculate the left and right part of the string ... if precision >= 0: # Append zeros. str_left = str_digitstream + '0' * precision str_right = '' str_point = '' elif precision < 0: # Prepend zeros. str_digitstream = '0' * (-precision + 1 - \ len(str_digitstream)) + str_digitstream # Split stream. str_left = str_digitstream[:precision] str_right = str_digitstream[precision:] str_point = '.' # Put the sign in front of the str_left, and rjust the sum of both. str_sign_left = (str_sign + str_left).rjust(self.width_left) str_point = str_point.center(self.width_point) str_right = str_right.ljust(self.width_right) return DecimalStrings( str_left = str_sign_left, str_point = str_point, str_right = str_right)
[docs] def __str__(self): return str(self.get_strings())