Skip to content

Commit

Permalink
Fix float conversion in extract_operands (and the relevant test)
Browse files Browse the repository at this point in the history
Fixes #421
  • Loading branch information
akx committed Jul 15, 2016
1 parent 5c37e6a commit 913886f
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 16 deletions.
37 changes: 30 additions & 7 deletions babel/plural.py
Expand Up @@ -9,7 +9,6 @@
:license: BSD, see LICENSE for more details.
"""
import re
import sys

from babel._compat import decimal

Expand All @@ -19,21 +18,45 @@


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 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.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()
Expand Down
24 changes: 15 additions & 9 deletions tests/test_plural.py
Expand Up @@ -16,6 +16,8 @@
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'})
Expand Down Expand Up @@ -240,22 +242,26 @@ def test_or_and(self):

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'))
Expand Down

0 comments on commit 913886f

Please sign in to comment.