from decimal import Decimal from django.conf import settings from django.utils.safestring import mark_safe def format( number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep="", force_grouping=False, use_l10n=None, ): """ Get a number (as a number or string), and return it as a string, using formats defined as arguments: * decimal_sep: Decimal separator symbol (for example ".") * decimal_pos: Number of decimal positions * grouping: Number of digits in every group limited by thousand separator. For non-uniform digit grouping, it can be a sequence with the number of digit group sizes following the format used by the Python locale module in locale.localeconv() LC_NUMERIC grouping (e.g. (3, 2, 0)). * thousand_sep: Thousand separator symbol (for example ",") """ if number is None or number == "": return mark_safe(number) use_grouping = ( use_l10n or (use_l10n is None and settings.USE_L10N) ) and settings.USE_THOUSAND_SEPARATOR use_grouping = use_grouping or force_grouping use_grouping = use_grouping and grouping != 0 # Make the common case fast if isinstance(number, int) and not use_grouping and not decimal_pos: return mark_safe(number) # sign sign = "" # Treat potentially very large/small floats as Decimals. if isinstance(number, float) and "e" in str(number).lower(): number = Decimal(str(number)) if isinstance(number, Decimal): if decimal_pos is not None: # If the provided number is too small to affect any of the visible # decimal places, consider it equal to '0'. cutoff = Decimal("0." + "1".rjust(decimal_pos, "0")) if abs(number) < cutoff: number = Decimal("0") # Format values with more than 200 digits (an arbitrary cutoff) using # scientific notation to avoid high memory usage in {:f}'.format(). _, digits, exponent = number.as_tuple() if abs(exponent) + len(digits) > 200: number = "{:e}".format(number) coefficient, exponent = number.split("e") # Format the coefficient. coefficient = format( coefficient, decimal_sep, decimal_pos, grouping, thousand_sep, force_grouping, use_l10n, ) return "{}e{}".format(coefficient, exponent) else: str_number = "{:f}".format(number) else: str_number = str(number) if str_number[0] == "-": sign = "-" str_number = str_number[1:] # decimal part if "." in str_number: int_part, dec_part = str_number.split(".") if decimal_pos is not None: dec_part = dec_part[:decimal_pos] else: int_part, dec_part = str_number, "" if decimal_pos is not None: dec_part += "0" * (decimal_pos - len(dec_part)) dec_part = dec_part and decimal_sep + dec_part # grouping if use_grouping: try: # if grouping is a sequence intervals = list(grouping) except TypeError: # grouping is a single value intervals = [grouping, 0] active_interval = intervals.pop(0) int_part_gd = "" cnt = 0 for digit in int_part[::-1]: if cnt and cnt == active_interval: if intervals: active_interval = intervals.pop(0) or active_interval int_part_gd += thousand_sep[::-1] cnt = 0 int_part_gd += digit cnt += 1 int_part = int_part_gd[::-1] return sign + int_part + dec_part