django/django

View on GitHub
django/utils/numberformat.py

Summary

Maintainability
B
6 hrs
Test Coverage
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 ",")
    """
    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 = 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