Mercurial > babel > old > babel-test
annotate babel/numbers.py @ 508:dbb70d75718a stable-0.9.x
Fix Python 2.3 compatibility for 0.9 branch (closes #233)
author | fschwarz |
---|---|
date | Fri, 04 Mar 2011 14:16:15 +0000 |
parents | dddfd2551f94 |
children |
rev | line source |
---|---|
1 | 1 # -*- coding: utf-8 -*- |
2 # | |
3 # Copyright (C) 2007 Edgewall Software | |
4 # All rights reserved. | |
5 # | |
245 | 6 # This software is licensed as described in the file COPYING, which |
1 | 7 # you should have received as part of this distribution. The terms |
8 # are also available at http://babel.edgewall.org/wiki/License. | |
9 # | |
10 # This software consists of voluntary contributions made by many | |
11 # individuals. For the exact contribution history, see the revision | |
12 # history and logs, available at http://babel.edgewall.org/log/. | |
13 | |
14 """Locale dependent formatting and parsing of numeric data. | |
15 | |
16 The default locale for the functions in this module is determined by the | |
17 following environment variables, in that order: | |
18 | |
19 * ``LC_NUMERIC``, | |
20 * ``LC_ALL``, and | |
21 * ``LANG`` | |
22 """ | |
246 | 23 # TODO: |
24 # Padding and rounding increments in pattern: | |
25 # - http://www.unicode.org/reports/tr35/ (Appendix G.6) | |
243
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
26 import math |
1 | 27 import re |
218 | 28 try: |
29 from decimal import Decimal | |
30 have_decimal = True | |
31 except ImportError: | |
32 have_decimal = False | |
1 | 33 |
72
4dcdb1d367ec
More explicit module-level function names in `babel.core`. Added `Locale.negotiate` class method.
cmlenz
parents:
50
diff
changeset
|
34 from babel.core import default_locale, Locale |
508
dbb70d75718a
Fix Python 2.3 compatibility for 0.9 branch (closes #233)
fschwarz
parents:
389
diff
changeset
|
35 from babel.util import rsplit |
1 | 36 |
37 __all__ = ['format_number', 'format_decimal', 'format_currency', | |
38 'format_percent', 'format_scientific', 'parse_number', | |
32 | 39 'parse_decimal', 'NumberFormatError'] |
1 | 40 __docformat__ = 'restructuredtext en' |
41 | |
72
4dcdb1d367ec
More explicit module-level function names in `babel.core`. Added `Locale.negotiate` class method.
cmlenz
parents:
50
diff
changeset
|
42 LC_NUMERIC = default_locale('LC_NUMERIC') |
1 | 43 |
389
dddfd2551f94
Ported [424], [425], and [428] back to 0.9.x branch.
cmlenz
parents:
379
diff
changeset
|
44 def get_currency_name(currency, locale=LC_NUMERIC): |
dddfd2551f94
Ported [424], [425], and [428] back to 0.9.x branch.
cmlenz
parents:
379
diff
changeset
|
45 """Return the name used by the locale for the specified currency. |
dddfd2551f94
Ported [424], [425], and [428] back to 0.9.x branch.
cmlenz
parents:
379
diff
changeset
|
46 |
dddfd2551f94
Ported [424], [425], and [428] back to 0.9.x branch.
cmlenz
parents:
379
diff
changeset
|
47 >>> get_currency_name('USD', 'en_US') |
dddfd2551f94
Ported [424], [425], and [428] back to 0.9.x branch.
cmlenz
parents:
379
diff
changeset
|
48 u'US Dollar' |
dddfd2551f94
Ported [424], [425], and [428] back to 0.9.x branch.
cmlenz
parents:
379
diff
changeset
|
49 |
dddfd2551f94
Ported [424], [425], and [428] back to 0.9.x branch.
cmlenz
parents:
379
diff
changeset
|
50 :param currency: the currency code |
dddfd2551f94
Ported [424], [425], and [428] back to 0.9.x branch.
cmlenz
parents:
379
diff
changeset
|
51 :param locale: the `Locale` object or locale identifier |
dddfd2551f94
Ported [424], [425], and [428] back to 0.9.x branch.
cmlenz
parents:
379
diff
changeset
|
52 :return: the currency symbol |
dddfd2551f94
Ported [424], [425], and [428] back to 0.9.x branch.
cmlenz
parents:
379
diff
changeset
|
53 :rtype: `unicode` |
dddfd2551f94
Ported [424], [425], and [428] back to 0.9.x branch.
cmlenz
parents:
379
diff
changeset
|
54 :since: version 0.9.4 |
dddfd2551f94
Ported [424], [425], and [428] back to 0.9.x branch.
cmlenz
parents:
379
diff
changeset
|
55 """ |
dddfd2551f94
Ported [424], [425], and [428] back to 0.9.x branch.
cmlenz
parents:
379
diff
changeset
|
56 return Locale.parse(locale).currencies.get(currency, currency) |
dddfd2551f94
Ported [424], [425], and [428] back to 0.9.x branch.
cmlenz
parents:
379
diff
changeset
|
57 |
125 | 58 def get_currency_symbol(currency, locale=LC_NUMERIC): |
59 """Return the symbol used by the locale for the specified currency. | |
60 | |
61 >>> get_currency_symbol('USD', 'en_US') | |
62 u'$' | |
63 | |
64 :param currency: the currency code | |
65 :param locale: the `Locale` object or locale identifier | |
66 :return: the currency symbol | |
67 :rtype: `unicode` | |
68 """ | |
69 return Locale.parse(locale).currency_symbols.get(currency, currency) | |
70 | |
1 | 71 def get_decimal_symbol(locale=LC_NUMERIC): |
72 """Return the symbol used by the locale to separate decimal fractions. | |
73 | |
74 >>> get_decimal_symbol('en_US') | |
75 u'.' | |
76 | |
77 :param locale: the `Locale` object or locale identifier | |
78 :return: the decimal symbol | |
79 :rtype: `unicode` | |
80 """ | |
81 return Locale.parse(locale).number_symbols.get('decimal', u'.') | |
82 | |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
83 def get_plus_sign_symbol(locale=LC_NUMERIC): |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
84 """Return the plus sign symbol used by the current locale. |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
85 |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
86 >>> get_plus_sign_symbol('en_US') |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
87 u'+' |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
88 |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
89 :param locale: the `Locale` object or locale identifier |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
90 :return: the plus sign symbol |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
91 :rtype: `unicode` |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
92 """ |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
93 return Locale.parse(locale).number_symbols.get('plusSign', u'+') |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
94 |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
95 def get_minus_sign_symbol(locale=LC_NUMERIC): |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
96 """Return the plus sign symbol used by the current locale. |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
97 |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
98 >>> get_minus_sign_symbol('en_US') |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
99 u'-' |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
100 |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
101 :param locale: the `Locale` object or locale identifier |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
102 :return: the plus sign symbol |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
103 :rtype: `unicode` |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
104 """ |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
105 return Locale.parse(locale).number_symbols.get('minusSign', u'-') |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
106 |
243
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
107 def get_exponential_symbol(locale=LC_NUMERIC): |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
108 """Return the symbol used by the locale to separate mantissa and exponent. |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
109 |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
110 >>> get_exponential_symbol('en_US') |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
111 u'E' |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
112 |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
113 :param locale: the `Locale` object or locale identifier |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
114 :return: the exponential symbol |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
115 :rtype: `unicode` |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
116 """ |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
117 return Locale.parse(locale).number_symbols.get('exponential', u'E') |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
118 |
1 | 119 def get_group_symbol(locale=LC_NUMERIC): |
120 """Return the symbol used by the locale to separate groups of thousands. | |
121 | |
122 >>> get_group_symbol('en_US') | |
123 u',' | |
124 | |
125 :param locale: the `Locale` object or locale identifier | |
126 :return: the group symbol | |
127 :rtype: `unicode` | |
128 """ | |
9 | 129 return Locale.parse(locale).number_symbols.get('group', u',') |
1 | 130 |
131 def format_number(number, locale=LC_NUMERIC): | |
101
1312ad6b624d
Add wrapper class bundling the various formatting functions bound to a specific locale and time-zone.
cmlenz
parents:
72
diff
changeset
|
132 """Return the given number formatted for a specific locale. |
1 | 133 |
134 >>> format_number(1099, locale='en_US') | |
135 u'1,099' | |
136 | |
137 :param number: the number to format | |
138 :param locale: the `Locale` object or locale identifier | |
139 :return: the formatted number | |
140 :rtype: `unicode` | |
141 """ | |
9 | 142 # Do we really need this one? |
143 return format_decimal(number, locale=locale) | |
1 | 144 |
9 | 145 def format_decimal(number, format=None, locale=LC_NUMERIC): |
101
1312ad6b624d
Add wrapper class bundling the various formatting functions bound to a specific locale and time-zone.
cmlenz
parents:
72
diff
changeset
|
146 """Return the given decimal number formatted for a specific locale. |
1 | 147 |
9 | 148 >>> format_decimal(1.2345, locale='en_US') |
149 u'1.234' | |
50 | 150 >>> format_decimal(1.2346, locale='en_US') |
151 u'1.235' | |
152 >>> format_decimal(-1.2346, locale='en_US') | |
153 u'-1.235' | |
9 | 154 >>> format_decimal(1.2345, locale='sv_SE') |
155 u'1,234' | |
50 | 156 >>> format_decimal(12345, locale='de') |
9 | 157 u'12.345' |
158 | |
1 | 159 The appropriate thousands grouping and the decimal separator are used for |
160 each locale: | |
161 | |
125 | 162 >>> format_decimal(12345.5, locale='en_US') |
163 u'12,345.5' | |
9 | 164 |
1 | 165 :param number: the number to format |
9 | 166 :param format: |
1 | 167 :param locale: the `Locale` object or locale identifier |
168 :return: the formatted decimal number | |
169 :rtype: `unicode` | |
170 """ | |
171 locale = Locale.parse(locale) | |
125 | 172 if not format: |
173 format = locale.decimal_formats.get(format) | |
174 pattern = parse_pattern(format) | |
9 | 175 return pattern.apply(number, locale) |
1 | 176 |
125 | 177 def format_currency(number, currency, format=None, locale=LC_NUMERIC): |
133 | 178 u"""Return formatted currency value. |
1 | 179 |
32 | 180 >>> format_currency(1099.98, 'USD', locale='en_US') |
125 | 181 u'$1,099.98' |
182 >>> format_currency(1099.98, 'USD', locale='es_CO') | |
379 | 183 u'US$\\xa01.099,98' |
125 | 184 >>> format_currency(1099.98, 'EUR', locale='de_DE') |
379 | 185 u'1.099,98\\xa0\\u20ac' |
125 | 186 |
187 The pattern can also be specified explicitly: | |
188 | |
189 >>> format_currency(1099.98, 'EUR', u'\xa4\xa4 #,##0.00', locale='en_US') | |
190 u'EUR 1,099.98' | |
1 | 191 |
26
710090104678
* Reduce size of locale data pickles by only storing the data provided by each locale itself, and merging inherited data at runtime.
cmlenz
parents:
22
diff
changeset
|
192 :param number: the number to format |
32 | 193 :param currency: the currency code |
1 | 194 :param locale: the `Locale` object or locale identifier |
195 :return: the formatted currency value | |
196 :rtype: `unicode` | |
197 """ | |
125 | 198 locale = Locale.parse(locale) |
199 if not format: | |
200 format = locale.currency_formats.get(format) | |
201 pattern = parse_pattern(format) | |
202 return pattern.apply(number, locale, currency=currency) | |
1 | 203 |
26
710090104678
* Reduce size of locale data pickles by only storing the data provided by each locale itself, and merging inherited data at runtime.
cmlenz
parents:
22
diff
changeset
|
204 def format_percent(number, format=None, locale=LC_NUMERIC): |
101
1312ad6b624d
Add wrapper class bundling the various formatting functions bound to a specific locale and time-zone.
cmlenz
parents:
72
diff
changeset
|
205 """Return formatted percent value for a specific locale. |
22 | 206 |
207 >>> format_percent(0.34, locale='en_US') | |
208 u'34%' | |
209 >>> format_percent(25.1234, locale='en_US') | |
210 u'2,512%' | |
211 >>> format_percent(25.1234, locale='sv_SE') | |
233
bc22f5aef216
Upgraded to CLDR 1.5 and improved timezone formatting.
cmlenz
parents:
218
diff
changeset
|
212 u'2\\xa0512\\xa0%' |
22 | 213 |
126 | 214 The format pattern can also be specified explicitly: |
215 | |
216 >>> format_percent(25.1234, u'#,##0\u2030', locale='en_US') | |
217 u'25,123\u2030' | |
218 | |
22 | 219 :param number: the percent number to format |
220 :param format: | |
221 :param locale: the `Locale` object or locale identifier | |
222 :return: the formatted percent number | |
223 :rtype: `unicode` | |
224 """ | |
225 locale = Locale.parse(locale) | |
125 | 226 if not format: |
227 format = locale.percent_formats.get(format) | |
228 pattern = parse_pattern(format) | |
26
710090104678
* Reduce size of locale data pickles by only storing the data provided by each locale itself, and merging inherited data at runtime.
cmlenz
parents:
22
diff
changeset
|
229 return pattern.apply(number, locale) |
1 | 230 |
243
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
231 def format_scientific(number, format=None, locale=LC_NUMERIC): |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
232 """Return value formatted in scientific notation for a specific locale. |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
233 |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
234 >>> format_scientific(10000, locale='en_US') |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
235 u'1E4' |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
236 |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
237 The format pattern can also be specified explicitly: |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
238 |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
239 >>> format_scientific(1234567, u'##0E00', locale='en_US') |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
240 u'1.23E06' |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
241 |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
242 :param number: the number to format |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
243 :param format: |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
244 :param locale: the `Locale` object or locale identifier |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
245 :return: value formatted in scientific notation. |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
246 :rtype: `unicode` |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
247 """ |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
248 locale = Locale.parse(locale) |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
249 if not format: |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
250 format = locale.scientific_formats.get(format) |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
251 pattern = parse_pattern(format) |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
252 return pattern.apply(number, locale) |
1 | 253 |
32 | 254 |
255 class NumberFormatError(ValueError): | |
256 """Exception raised when a string cannot be parsed into a number.""" | |
257 | |
258 | |
1 | 259 def parse_number(string, locale=LC_NUMERIC): |
260 """Parse localized number string into a long integer. | |
261 | |
262 >>> parse_number('1,099', locale='en_US') | |
263 1099L | |
264 >>> parse_number('1.099', locale='de_DE') | |
265 1099L | |
266 | |
32 | 267 When the given string cannot be parsed, an exception is raised: |
268 | |
269 >>> parse_number('1.099,98', locale='de') | |
270 Traceback (most recent call last): | |
271 ... | |
272 NumberFormatError: '1.099,98' is not a valid number | |
273 | |
1 | 274 :param string: the string to parse |
275 :param locale: the `Locale` object or locale identifier | |
276 :return: the parsed number | |
277 :rtype: `long` | |
32 | 278 :raise `NumberFormatError`: if the string can not be converted to a number |
1 | 279 """ |
32 | 280 try: |
281 return long(string.replace(get_group_symbol(locale), '')) | |
282 except ValueError: | |
283 raise NumberFormatError('%r is not a valid number' % string) | |
1 | 284 |
285 def parse_decimal(string, locale=LC_NUMERIC): | |
286 """Parse localized decimal string into a float. | |
287 | |
288 >>> parse_decimal('1,099.98', locale='en_US') | |
289 1099.98 | |
32 | 290 >>> parse_decimal('1.099,98', locale='de') |
1 | 291 1099.98 |
292 | |
32 | 293 When the given string cannot be parsed, an exception is raised: |
294 | |
295 >>> parse_decimal('2,109,998', locale='de') | |
296 Traceback (most recent call last): | |
297 ... | |
298 NumberFormatError: '2,109,998' is not a valid decimal number | |
299 | |
1 | 300 :param string: the string to parse |
301 :param locale: the `Locale` object or locale identifier | |
302 :return: the parsed decimal number | |
303 :rtype: `float` | |
32 | 304 :raise `NumberFormatError`: if the string can not be converted to a |
305 decimal number | |
1 | 306 """ |
307 locale = Locale.parse(locale) | |
32 | 308 try: |
309 return float(string.replace(get_group_symbol(locale), '') | |
310 .replace(get_decimal_symbol(locale), '.')) | |
311 except ValueError: | |
312 raise NumberFormatError('%r is not a valid decimal number' % string) | |
9 | 313 |
314 | |
315 PREFIX_END = r'[^0-9@#.,]' | |
243
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
316 NUMBER_TOKEN = r'[0-9@#.\-,E+]' |
9 | 317 |
318 PREFIX_PATTERN = r"(?P<prefix>(?:'[^']*'|%s)*)" % PREFIX_END | |
319 NUMBER_PATTERN = r"(?P<number>%s+)" % NUMBER_TOKEN | |
320 SUFFIX_PATTERN = r"(?P<suffix>.*)" | |
321 | |
127 | 322 number_re = re.compile(r"%s%s%s" % (PREFIX_PATTERN, NUMBER_PATTERN, |
9 | 323 SUFFIX_PATTERN)) |
324 | |
218 | 325 def split_number(value): |
326 """Convert a number into a (intasstring, fractionasstring) tuple""" | |
327 if have_decimal and isinstance(value, Decimal): | |
328 text = str(value) | |
329 else: | |
330 text = ('%.9f' % value).rstrip('0') | |
331 if '.' in text: | |
332 a, b = text.split('.', 1) | |
333 if b == '0': | |
334 b = '' | |
335 else: | |
336 a, b = text, '' | |
337 return a, b | |
338 | |
212 | 339 def bankersround(value, ndigits=0): |
340 """Round a number to a given precision. | |
341 | |
342 Works like round() except that the round-half-even (banker's rounding) | |
343 algorithm is used instead of round-half-up. | |
344 | |
345 >>> bankersround(5.5, 0) | |
346 6.0 | |
347 >>> bankersround(6.5, 0) | |
348 6.0 | |
349 >>> bankersround(-6.5, 0) | |
350 -6.0 | |
218 | 351 >>> bankersround(1234.0, -2) |
212 | 352 1200.0 |
353 """ | |
354 sign = int(value < 0) and -1 or 1 | |
355 value = abs(value) | |
218 | 356 a, b = split_number(value) |
357 digits = a + b | |
212 | 358 add = 0 |
359 i = len(a) + ndigits | |
360 if i < 0 or i >= len(digits): | |
361 pass | |
362 elif digits[i] > '5': | |
363 add = 1 | |
364 elif digits[i] == '5' and digits[i-1] in '13579': | |
365 add = 1 | |
218 | 366 scale = 10**ndigits |
367 if have_decimal and isinstance(value, Decimal): | |
368 return Decimal(int(value * scale + add)) / scale * sign | |
369 else: | |
370 return float(int(value * scale + add)) / scale * sign | |
212 | 371 |
9 | 372 def parse_pattern(pattern): |
373 """Parse number format patterns""" | |
374 if isinstance(pattern, NumberPattern): | |
375 return pattern | |
376 | |
377 # Do we have a negative subpattern? | |
378 if ';' in pattern: | |
379 pattern, neg_pattern = pattern.split(';', 1) | |
380 pos_prefix, number, pos_suffix = number_re.search(pattern).groups() | |
381 neg_prefix, _, neg_suffix = number_re.search(neg_pattern).groups() | |
382 else: | |
383 pos_prefix, number, pos_suffix = number_re.search(pattern).groups() | |
384 neg_prefix = '-' + pos_prefix | |
385 neg_suffix = pos_suffix | |
243
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
386 if 'E' in number: |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
387 number, exp = number.split('E', 1) |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
388 else: |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
389 exp = None |
211
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
390 if '@' in number: |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
391 if '.' in number and '0' in number: |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
392 raise ValueError('Significant digit patterns can not contain ' |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
393 '"@" or "0"') |
22 | 394 if '.' in number: |
508
dbb70d75718a
Fix Python 2.3 compatibility for 0.9 branch (closes #233)
fschwarz
parents:
389
diff
changeset
|
395 integer, fraction = rsplit(number, '.', 1) |
22 | 396 else: |
397 integer = number | |
398 fraction = '' | |
9 | 399 min_frac = max_frac = 0 |
400 | |
401 def parse_precision(p): | |
402 """Calculate the min and max allowed digits""" | |
403 min = max = 0 | |
404 for c in p: | |
211
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
405 if c in '@0': |
9 | 406 min += 1 |
407 max += 1 | |
408 elif c == '#': | |
409 max += 1 | |
211
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
410 elif c == ',': |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
411 continue |
9 | 412 else: |
413 break | |
414 return min, max | |
415 | |
416 def parse_grouping(p): | |
417 """Parse primary and secondary digit grouping | |
418 | |
419 >>> parse_grouping('##') | |
420 0, 0 | |
421 >>> parse_grouping('#,###') | |
422 3, 3 | |
423 >>> parse_grouping('#,####,###') | |
424 3, 4 | |
425 """ | |
426 width = len(p) | |
427 g1 = p.rfind(',') | |
428 if g1 == -1: | |
429 return 1000, 1000 | |
430 g1 = width - g1 - 1 | |
431 g2 = p[:-g1 - 1].rfind(',') | |
432 if g2 == -1: | |
433 return g1, g1 | |
434 g2 = width - g1 - g2 - 2 | |
435 return g1, g2 | |
436 | |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
437 int_prec = parse_precision(integer) |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
438 frac_prec = parse_precision(fraction) |
243
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
439 if exp: |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
440 frac_prec = parse_precision(integer+fraction) |
243
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
441 exp_plus = exp.startswith('+') |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
442 exp = exp.lstrip('+') |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
443 exp_prec = parse_precision(exp) |
243
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
444 else: |
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
445 exp_plus = None |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
446 exp_prec = None |
9 | 447 grouping = parse_grouping(integer) |
448 return NumberPattern(pattern, (pos_prefix, neg_prefix), | |
449 (pos_suffix, neg_suffix), grouping, | |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
450 int_prec, frac_prec, |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
451 exp_prec, exp_plus) |
9 | 452 |
453 | |
454 class NumberPattern(object): | |
22 | 455 |
9 | 456 def __init__(self, pattern, prefix, suffix, grouping, |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
457 int_prec, frac_prec, exp_prec, exp_plus): |
9 | 458 self.pattern = pattern |
459 self.prefix = prefix | |
460 self.suffix = suffix | |
461 self.grouping = grouping | |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
462 self.int_prec = int_prec |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
463 self.frac_prec = frac_prec |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
464 self.exp_prec = exp_prec |
243
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
465 self.exp_plus = exp_plus |
22 | 466 if '%' in ''.join(self.prefix + self.suffix): |
218 | 467 self.scale = 100 |
22 | 468 elif u'‰' in ''.join(self.prefix + self.suffix): |
218 | 469 self.scale = 1000 |
22 | 470 else: |
218 | 471 self.scale = 1 |
9 | 472 |
473 def __repr__(self): | |
474 return '<%s %r>' % (type(self).__name__, self.pattern) | |
475 | |
125 | 476 def apply(self, value, locale, currency=None): |
22 | 477 value *= self.scale |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
478 is_negative = int(value < 0) |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
479 if self.exp_prec: # Scientific notation |
243
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
480 value = abs(value) |
369 | 481 if value: |
482 exp = int(math.floor(math.log(value, 10))) | |
483 else: | |
484 exp = 0 | |
243
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
485 # Minimum number of integer digits |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
486 if self.int_prec[0] == self.int_prec[1]: |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
487 exp -= self.int_prec[0] - 1 |
243
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
488 # Exponent grouping |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
489 elif self.int_prec[1]: |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
490 exp = int(exp) / self.int_prec[1] * self.int_prec[1] |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
491 if not have_decimal or not isinstance(value, Decimal): |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
492 value = float(value) |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
493 if exp < 0: |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
494 value = value * 10**(-exp) |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
495 else: |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
496 value = value / 10**exp |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
497 exp_sign = '' |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
498 if exp < 0: |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
499 exp_sign = get_minus_sign_symbol(locale) |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
500 elif self.exp_plus: |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
501 exp_sign = get_plus_sign_symbol(locale) |
243
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
502 exp = abs(exp) |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
503 number = u'%s%s%s%s' % \ |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
504 (self._format_sigdig(value, self.frac_prec[0], |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
505 self.frac_prec[1]), |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
506 get_exponential_symbol(locale), exp_sign, |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
507 self._format_int(str(exp), self.exp_prec[0], |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
508 self.exp_prec[1], locale)) |
243
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
509 elif '@' in self.pattern: # Is it a siginificant digits pattern? |
211
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
510 text = self._format_sigdig(abs(value), |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
511 self.int_prec[0], |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
512 self.int_prec[1]) |
211
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
513 if '.' in text: |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
514 a, b = text.split('.') |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
515 a = self._format_int(a, 0, 1000, locale) |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
516 if b: |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
517 b = get_decimal_symbol(locale) + b |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
518 number = a + b |
211
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
519 else: |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
520 number = self._format_int(text, 0, 1000, locale) |
211
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
521 else: # A normal number pattern |
218 | 522 a, b = split_number(bankersround(abs(value), |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
523 self.frac_prec[1])) |
218 | 524 b = b or '0' |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
525 a = self._format_int(a, self.int_prec[0], |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
526 self.int_prec[1], locale) |
211
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
527 b = self._format_frac(b, locale) |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
528 number = a + b |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
529 retval = u'%s%s%s' % (self.prefix[is_negative], number, |
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
530 self.suffix[is_negative]) |
125 | 531 if u'¤' in retval: |
532 retval = retval.replace(u'¤¤', currency.upper()) | |
533 retval = retval.replace(u'¤', get_currency_symbol(currency, locale)) | |
534 return retval | |
9 | 535 |
211
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
536 def _format_sigdig(self, value, min, max): |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
537 """Convert value to a string. |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
538 |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
539 The resulting string will contain between (min, max) number of |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
540 significant digits. |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
541 """ |
218 | 542 a, b = split_number(value) |
211
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
543 ndecimals = len(a) |
218 | 544 if a == '0' and b != '': |
211
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
545 ndecimals = 0 |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
546 while b.startswith('0'): |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
547 b = b[1:] |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
548 ndecimals -= 1 |
218 | 549 a, b = split_number(bankersround(value, max - ndecimals)) |
550 digits = len((a + b).lstrip('0')) | |
551 if not digits: | |
211
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
552 digits = 1 |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
553 # Figure out if we need to add any trailing '0':s |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
554 if len(a) >= max and a != '0': |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
555 return a |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
556 if digits < min: |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
557 b += ('0' * (min - digits)) |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
558 if b: |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
559 return '%s.%s' % (a, b) |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
560 return a |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
561 |
1adcbeb4607b
Added support for siginificant digits in number patterns.
jonas
parents:
137
diff
changeset
|
562 def _format_int(self, value, min, max, locale): |
9 | 563 width = len(value) |
564 if width < min: | |
243
5e4b1fd6b348
Added initial support for scientific notation patterns.
jonas
parents:
233
diff
changeset
|
565 value = '0' * (min - width) + value |
9 | 566 gsize = self.grouping[0] |
567 ret = '' | |
568 symbol = get_group_symbol(locale) | |
569 while len(value) > gsize: | |
570 ret = symbol + value[-gsize:] + ret | |
571 value = value[:-gsize] | |
572 gsize = self.grouping[1] | |
573 return value + ret | |
574 | |
575 def _format_frac(self, value, locale): | |
244
222ccdeef393
Added `Decimal` support to the scientific notation formatter and some code cleanup. This closes #52.
jonas
parents:
243
diff
changeset
|
576 min, max = self.frac_prec |
218 | 577 if len(value) < min: |
578 value += ('0' * (min - len(value))) | |
22 | 579 if max == 0 or (min == 0 and int(value) == 0): |
9 | 580 return '' |
581 width = len(value) | |
50 | 582 while len(value) > min and value[-1] == '0': |
583 value = value[:-1] | |
9 | 584 return get_decimal_symbol(locale) + value |