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