numberformat.py 3.4 KB

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