:license: BSD, see LICENSE for more details.
"""
import re
-import sys
from babel._compat import decimal
def extract_operands(source):
- """Extract operands from a decimal, a float or an int, according to
- `CLDR rules`_.
+ """Extract operands from a decimal, a float or an int, according to `CLDR rules`_.
+
+ The result is a 6-tuple (n, i, v, w, f, t), where those symbols are as follows:
+
+ ====== ===============================================================
+ Symbol Value
+ ------ ---------------------------------------------------------------
+ n absolute value of the source number (integer and decimals).
+ i integer digits of n.
+ v number of visible fraction digits in n, with trailing zeros.
+ w number of visible fraction digits in n, without trailing zeros.
+ f visible fractional digits in n, with trailing zeros.
+ t visible fractional digits in n, without trailing zeros.
+ ====== ===============================================================
.. _`CLDR rules`: http://www.unicode.org/reports/tr35/tr35-33/tr35-numbers.html#Operands
+
+ :param source: A real number
+ :type source: int|float|decimal.Decimal
+ :return: A n-i-v-w-f-t tuple
+ :rtype: tuple[decimal.Decimal, int, int, int, int, int]
"""
n = abs(source)
i = int(n)
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.Decimal(n)
+ # Cast the `float` to a number via the string representation.
+ # This is required for Python 2.6 anyway (it will straight out fail to
+ # do the conversion otherwise), and it's highly unlikely that the user
+ # actually wants the lossless conversion behavior (quoting the Python
+ # documentation):
+ # > If value is a float, the binary floating point value is losslessly
+ # > converted to its exact decimal equivalent.
+ # > This conversion can often require 53 or more digits of precision.
+ # Should the user want that behavior, they can simply pass in a pre-
+ # converted `Decimal` instance of desired accuracy.
+ n = decimal.Decimal(str(n))
if isinstance(n, decimal.Decimal):
dec_tuple = n.as_tuple()
from babel import plural, localedata
from babel._compat import decimal
+EPSILON = decimal.Decimal("0.0001")
+
def test_plural_rule():
rule = plural.PluralRule({'one': 'n is 1'})
EXTRACT_OPERANDS_TESTS = (
(1, 1, 1, 0, 0, 0, 0),
- ('1.0', '1.0', 1, 1, 0, 0, 0),
- ('1.00', '1.00', 1, 2, 0, 0, 0),
- ('1.3', '1.3', 1, 1, 1, 3, 3),
- ('1.30', '1.30', 1, 2, 1, 30, 3),
- ('1.03', '1.03', 1, 2, 2, 3, 3),
- ('1.230', '1.230', 1, 3, 2, 230, 23),
+ (decimal.Decimal('1.0'), '1.0', 1, 1, 0, 0, 0),
+ (decimal.Decimal('1.00'), '1.00', 1, 2, 0, 0, 0),
+ (decimal.Decimal('1.3'), '1.3', 1, 1, 1, 3, 3),
+ (decimal.Decimal('1.30'), '1.30', 1, 2, 1, 30, 3),
+ (decimal.Decimal('1.03'), '1.03', 1, 2, 2, 3, 3),
+ (decimal.Decimal('1.230'), '1.230', 1, 3, 2, 230, 23),
(-1, 1, 1, 0, 0, 0, 0),
(1.3, '1.3', 1, 1, 1, 3, 3),
)
@pytest.mark.parametrize('source,n,i,v,w,f,t', EXTRACT_OPERANDS_TESTS)
def test_extract_operands(source, n, i, v, w, f, t):
- source = decimal.Decimal(source) if isinstance(source, str) else source
- assert (plural.extract_operands(source) ==
- decimal.Decimal(n), i, v, w, f, t)
+ e_n, e_i, e_v, e_w, e_f, e_t = plural.extract_operands(source)
+ assert abs(e_n - decimal.Decimal(n)) <= EPSILON # float-decimal conversion inaccuracy
+ assert e_i == i
+ assert e_v == v
+ assert e_w == w
+ assert e_f == f
+ assert e_t == t
@pytest.mark.parametrize('locale', ('ru', 'pl'))