1from decimal import Decimal
2
3from django.conf import settings
4from django.utils.safestring import mark_safe
5
6
7def format(
8 number,
9 decimal_sep,
10 decimal_pos=None,
11 grouping=0,
12 thousand_sep="",
13 force_grouping=False,
14 use_l10n=None,
15):
16 """
17 Get a number (as a number or string), and return it as a string,
18 using formats defined as arguments:
19
20 * decimal_sep: Decimal separator symbol (for example ".")
21 * decimal_pos: Number of decimal positions
22 * grouping: Number of digits in every group limited by thousand separator.
23 For non-uniform digit grouping, it can be a sequence with the number
24 of digit group sizes following the format used by the Python locale
25 module in locale.localeconv() LC_NUMERIC grouping (e.g. (3, 2, 0)).
26 * thousand_sep: Thousand separator symbol (for example ",")
27 """
28 if number is None or number == "":
29 return mark_safe(number)
30 if use_l10n is None:
31 use_l10n = True
32 use_grouping = use_l10n and settings.USE_THOUSAND_SEPARATOR
33 use_grouping = use_grouping or force_grouping
34 use_grouping = use_grouping and grouping != 0
35 # Make the common case fast
36 if isinstance(number, int) and not use_grouping and not decimal_pos:
37 return mark_safe(number)
38 # sign
39 sign = ""
40 # Treat potentially very large/small floats as Decimals.
41 if isinstance(number, float) and "e" in str(number).lower():
42 number = Decimal(str(number))
43 if isinstance(number, Decimal):
44 if decimal_pos is not None:
45 # If the provided number is too small to affect any of the visible
46 # decimal places, consider it equal to '0'.
47 cutoff = Decimal("0." + "1".rjust(decimal_pos, "0"))
48 if abs(number) < cutoff:
49 number = Decimal("0")
50
51 # Format values with more than 200 digits (an arbitrary cutoff) using
52 # scientific notation to avoid high memory usage in {:f}'.format().
53 _, digits, exponent = number.as_tuple()
54 if abs(exponent) + len(digits) > 200:
55 number = "{:e}".format(number)
56 coefficient, exponent = number.split("e")
57 # Format the coefficient.
58 coefficient = format(
59 coefficient,
60 decimal_sep,
61 decimal_pos,
62 grouping,
63 thousand_sep,
64 force_grouping,
65 use_l10n,
66 )
67 return "{}e{}".format(coefficient, exponent)
68 else:
69 str_number = "{:f}".format(number)
70 else:
71 str_number = str(number)
72 if str_number[0] == "-":
73 sign = "-"
74 str_number = str_number[1:]
75 # decimal part
76 if "." in str_number:
77 int_part, dec_part = str_number.split(".")
78 if decimal_pos is not None:
79 dec_part = dec_part[:decimal_pos]
80 else:
81 int_part, dec_part = str_number, ""
82 if decimal_pos is not None:
83 dec_part += "0" * (decimal_pos - len(dec_part))
84 dec_part = dec_part and decimal_sep + dec_part
85 # grouping
86 if use_grouping:
87 try:
88 # if grouping is a sequence
89 intervals = list(grouping)
90 except TypeError:
91 # grouping is a single value
92 intervals = [grouping, 0]
93 active_interval = intervals.pop(0)
94 int_part_gd = ""
95 cnt = 0
96 for digit in int_part[::-1]:
97 if cnt and cnt == active_interval:
98 if intervals:
99 active_interval = intervals.pop(0) or active_interval
100 int_part_gd += thousand_sep[::-1]
101 cnt = 0
102 int_part_gd += digit
103 cnt += 1
104 int_part = int_part_gd[::-1]
105 return sign + int_part + dec_part