Source code for shoop.utils.numbers

# -*- coding: utf-8 -*-
# This file is part of Shoop.
#
# Copyright (c) 2012-2016, Shoop Ltd. All rights reserved.
#
# This source code is licensed under the AGPLv3 license found in the
# LICENSE file in the root directory of this source tree.
import re
from decimal import Decimal, ROUND_HALF_EVEN, ROUND_HALF_UP

import six

from shoop.utils import update_module_attributes

from ._unitted_decimal import UnitMixupError, UnittedDecimal

__all__ = [
    "UnittedDecimal",
    "UnitMixupError",
    "bankers_round",
    "strip_non_float_chars",
    "parse_decimal_string",
    "try_parse_decimal_string",
    "get_string_sort_order",
]

quant_cache = {}


[docs]def bankers_round(value, ndigits=0): if isinstance(value, float): value = Decimal(str(value)) elif not isinstance(value, Decimal): value = Decimal(value) # We can cache the quantizers... quantizer = quant_cache.get(ndigits) if quantizer is None: quantizer = quant_cache[ndigits] = Decimal(10) ** (-int(ndigits)) return value.quantize(quantizer, rounding=ROUND_HALF_EVEN)
def nickel_round(value, quant=Decimal('0.05'), rounding=ROUND_HALF_UP): """ Round decimal value to nearest quant. >>> nickel_round(Decimal('10.32')) Decimal('10.30') >>> nickel_round(Decimal('10.33')) Decimal('10.35') >>> nickel_round(Decimal('10.325')) Decimal('10.35') >>> nickel_round(Decimal('10.3249')) Decimal('10.30') >>> nickel_round(Decimal('10.31'), Decimal('0.125')) Decimal('10.250') >>> nickel_round(Decimal('10.32'), Decimal('0.125')) Decimal('10.375') :type value: decimal.Decimal :type quant: decimal.Decimal :type rounding: str :rtype: decimal.Decimal """ assert isinstance(value, Decimal) assert isinstance(quant, Decimal) return (value / quant).quantize(1, rounding=rounding) * quant
[docs]def strip_non_float_chars(s): """ Strips characters that aren't part of normal floats. """ return re.sub("[^-+0123456789.]+", "", six.text_type(s))
[docs]def parse_decimal_string(s): """ Parse decimals with "best effort". Parses a string (or unicode) that may be embellished with spaces and other weirdness into the most probable Decimal. :param s: Input value :type s: str :return: Decimal :rtype: Decimal """ if isinstance(s, six.integer_types) or isinstance(s, Decimal): return Decimal(s) if isinstance(s, float): return Decimal(str(s)) s = s.strip().replace(" ", "") # Also 500 000.0 would be.. well, 500 :D if not s: return Decimal(0) if "," in s: # 500000,0? D: if "." in s: # Taking care of cases like 500,000.00 s = s.replace(",", "") # There we go, we have 500000.0 already else: s = s.replace(",", ".") # And there, it's a 500000.0 # Then clean up the rest and pray for DEITY HERE return Decimal(strip_non_float_chars(s.strip()))
[docs]def try_parse_decimal_string(s): try: return parse_decimal_string(s) except: return None
GARMENT_SIZES = ("XXXS", "XXS", "XS", "S", "M", "L", "XL", "XXL", "XXXL")
[docs]def get_string_sort_order(s): """ Return a sorting order value for a string that contains a garment size. :param s: Input value (string or number) :type s: str :return: Sorting tuple :rtype: tuple """ # See if it's one of the predefined sizes for i, size in enumerate(GARMENT_SIZES): if size in s: return (10 + i, s) try: # If not, see if it looks enough like a decimal return (5, parse_decimal_string(s)) except: # Otherwise just sort as a string return (1, s)
update_module_attributes(__all__, __name__)