def extract_operands(source): """Extract operands from a decimal, a float or an int, according to `CLDR rules`_. .. _`CLDR rules`: http://www.unicode.org/reports/tr35/tr35-33/tr35-numbers.html#Operands """ n = abs(source) i = int(n) if isinstance(n, float): if i == n: n = i else: # 2.6's Decimal cannot convert from float directly if sys.version_info < (2, 7): n = str(n) n = Decimal(n) if isinstance(n, Decimal): dec_tuple = n.as_tuple() exp = dec_tuple.exponent fraction_digits = dec_tuple.digits[exp:] if exp < 0 else () trailing = ''.join(str(d) for d in fraction_digits) no_trailing = trailing.rstrip('0') v = len(trailing) w = len(no_trailing) f = int(trailing or 0) t = int(no_trailing or 0) else: v = w = f = t = 0 return n, i, v, w, f, t
def test_can_parse_decimals(self): self.assertEqual(Decimal('1099.98'), numbers.parse_decimal('1,099.98', locale='en_US')) self.assertEqual(Decimal('1099.98'), numbers.parse_decimal('1.099,98', locale='de')) self.assertRaises(numbers.NumberFormatError, lambda: numbers.parse_decimal('2,109,998', locale='de'))
def test_parse_decimal(): assert (numbers.parse_decimal('1,099.98', locale='en_US') == Decimal('1099.98')) assert numbers.parse_decimal('1.099,98', locale='de') == Decimal('1099.98') with pytest.raises(numbers.NumberFormatError) as excinfo: numbers.parse_decimal('2,109,998', locale='de') assert excinfo.value.args[0] == "'2,109,998' is not a valid decimal number"
def test_smoke_numbers(locale): locale = Locale.parse(locale) for number in ( Decimal("-33.76"), # Negative Decimal Decimal("13.37"), # Positive Decimal 1.2 - 1.0, # Inaccurate float 10, # Plain old integer 0, # Zero ): assert numbers.format_number(number, locale=locale)
def apply(self, value, locale, currency=None, force_frac=None): frac_prec = force_frac or self.frac_prec if not isinstance(value, Decimal): value = Decimal(str(value)) value = value.scaleb(self.scale) is_negative = int(value.is_signed()) if self.exp_prec: # Scientific notation exp = value.adjusted() value = abs(value) # Minimum number of integer digits if self.int_prec[0] == self.int_prec[1]: exp -= self.int_prec[0] - 1 # Exponent grouping elif self.int_prec[1]: exp = int(exp / self.int_prec[1]) * self.int_prec[1] if exp < 0: value = value * 10**(-exp) else: value = value / 10**exp exp_sign = '' if exp < 0: exp_sign = get_minus_sign_symbol(locale) elif self.exp_plus: exp_sign = get_plus_sign_symbol(locale) exp = abs(exp) number = u'%s%s%s%s' % \ (self._format_significant(value, frac_prec[0], frac_prec[1]), get_exponential_symbol(locale), exp_sign, self._format_int(str(exp), self.exp_prec[0], self.exp_prec[1], locale)) elif '@' in self.pattern: # Is it a siginificant digits pattern? text = self._format_significant(abs(value), self.int_prec[0], self.int_prec[1]) a, sep, b = text.partition(".") number = self._format_int(a, 0, 1000, locale) if sep: number += get_decimal_symbol(locale) + b else: # A normal number pattern precision = Decimal('1.' + '1' * frac_prec[1]) rounded = value.quantize(precision, ROUND_HALF_EVEN) a, sep, b = str(abs(rounded)).partition(".") number = (self._format_int(a, self.int_prec[0], self.int_prec[1], locale) + self._format_frac(b or '0', locale, force_frac)) retval = u'%s%s%s' % (self.prefix[is_negative], number, self.suffix[is_negative]) if u'¤' in retval: retval = retval.replace(u'¤¤¤', get_currency_name(currency, value, locale)) retval = retval.replace(u'¤¤', currency.upper()) retval = retval.replace(u'¤', get_currency_symbol(currency, locale)) return retval
def test_decimals(self): """Test significant digits patterns""" self.assertEqual(numbers.format_decimal(Decimal('1.2345'), '#.00', locale='en_US'), '1.23') self.assertEqual(numbers.format_decimal(Decimal('1.2345000'), '#.00', locale='en_US'), '1.23') self.assertEqual(numbers.format_decimal(Decimal('1.2345000'), '@@', locale='en_US'), '1.2') self.assertEqual(numbers.format_decimal(Decimal('12345678901234567890.12345'), '#.00', locale='en_US'), '12345678901234567890.12')
def parse_decimal(string, locale=LC_NUMERIC): """Parse localized decimal string into a decimal. >>> parse_decimal('1,099.98', locale='en_US') Decimal('1099.98') >>> parse_decimal('1.099,98', locale='de') Decimal('1099.98') When the given string cannot be parsed, an exception is raised: >>> parse_decimal('2,109,998', locale='de') Traceback (most recent call last): ... NumberFormatError: '2,109,998' is not a valid decimal number :param string: the string to parse :param locale: the `Locale` object or locale identifier :raise NumberFormatError: if the string can not be converted to a decimal number """ locale = Locale.parse(locale) try: return Decimal(string.replace(get_group_symbol(locale), '') .replace(get_decimal_symbol(locale), '.')) except InvalidOperation: raise NumberFormatError('%r is not a valid decimal number' % string)
def test_scientific_notation(self): fmt = numbers.format_scientific(0.1, '#E0', locale='en_US') self.assertEqual(fmt, '1E-1') fmt = numbers.format_scientific(0.01, '#E0', locale='en_US') self.assertEqual(fmt, '1E-2') fmt = numbers.format_scientific(10, '#E0', locale='en_US') self.assertEqual(fmt, '1E1') fmt = numbers.format_scientific(1234, '0.###E0', locale='en_US') self.assertEqual(fmt, '1.234E3') fmt = numbers.format_scientific(1234, '0.#E0', locale='en_US') self.assertEqual(fmt, '1.2E3') # Exponent grouping fmt = numbers.format_scientific(12345, '##0.####E0', locale='en_US') self.assertEqual(fmt, '12.345E3') # Minimum number of int digits fmt = numbers.format_scientific(12345, '00.###E0', locale='en_US') self.assertEqual(fmt, '12.345E3') fmt = numbers.format_scientific(-12345.6, '00.###E0', locale='en_US') self.assertEqual(fmt, '-12.346E3') fmt = numbers.format_scientific(-0.01234, '00.###E0', locale='en_US') self.assertEqual(fmt, '-12.34E-3') # Custom pattern suffic fmt = numbers.format_scientific(123.45, '#.##E0 m/s', locale='en_US') self.assertEqual(fmt, '1.23E2 m/s') # Exponent patterns fmt = numbers.format_scientific(123.45, '#.##E00 m/s', locale='en_US') self.assertEqual(fmt, '1.23E02 m/s') fmt = numbers.format_scientific(0.012345, '#.##E00 m/s', locale='en_US') self.assertEqual(fmt, '1.23E-02 m/s') fmt = numbers.format_scientific(Decimal('12345'), '#.##E+00 m/s', locale='en_US') self.assertEqual(fmt, '1.23E+04 m/s') # 0 (see ticket #99) fmt = numbers.format_scientific(0, '#E0', locale='en_US') self.assertEqual(fmt, '0E0')
def _format_significant(self, value, minimum, maximum): exp = value.adjusted() scale = maximum - 1 - exp digits = str(value.scaleb(scale).quantize(Decimal(1), ROUND_HALF_EVEN)) if scale <= 0: result = digits + '0' * -scale else: intpart = digits[:-scale] i = len(intpart) j = i + max(minimum - i, 0) result = "{intpart}.{pad:0<{fill}}{fracpart}{fracextra}".format( intpart=intpart or '0', pad='', fill=-min(exp + 1, 0), fracpart=digits[i:j], fracextra=digits[j:].rstrip('0'), ).rstrip('.') return result
def test_formatting_of_very_small_decimals(self): # previously formatting very small decimals could lead to a type error # because the Decimal->string conversion was too simple (see #214) number = Decimal("7E-7") fmt = numbers.format_decimal(number, format="@@@", locale='en_US') self.assertEqual('0.000000700', fmt)
def test_plural_rule_operands_t(): rule = plural.PluralRule({'one': 't = 5'}) assert rule(Decimal('1.53')) == 'other' assert rule(Decimal('1.50')) == 'one' assert rule(1.5) == 'one'
def test_plural_rule_operands_f(): rule = plural.PluralRule({'one': 'f is 20'}) assert rule(Decimal('1.23')) == 'other' assert rule(Decimal('1.20')) == 'one' assert rule(1.2) == 'other'
def test_plural_rule_operands_v(): rule = plural.PluralRule({'one': 'v is 2'}) assert rule(Decimal('1.20')) == 'one' assert rule(Decimal('1.2')) == 'other' assert rule(2) == 'other'
def test_extract_operands(source, n, i, v, w, f, t): source = Decimal(source) if isinstance(source, str) else source assert (plural.extract_operands(source) == Decimal(n), i, v, w, f, t)