Merge pull request #665 from anthrotype/py23-round
[py23] add round2 and round3 functions
This commit is contained in:
commit
88f7b06ce5
@ -6,13 +6,17 @@ import sys
|
||||
|
||||
__all__ = ['basestring', 'unicode', 'unichr', 'byteord', 'bytechr', 'BytesIO',
|
||||
'StringIO', 'UnicodeIO', 'strjoin', 'bytesjoin', 'tobytes', 'tostr',
|
||||
'tounicode', 'Tag', 'open', 'range', 'xrange', 'Py23Error']
|
||||
'tounicode', 'Tag', 'open', 'range', 'xrange', 'round', 'Py23Error']
|
||||
|
||||
|
||||
class Py23Error(NotImplementedError):
|
||||
pass
|
||||
|
||||
|
||||
PY3 = sys.version_info[0] == 3
|
||||
PY2 = sys.version_info[0] == 2
|
||||
|
||||
|
||||
try:
|
||||
basestring = basestring
|
||||
except NameError:
|
||||
@ -255,6 +259,94 @@ def xrange(*args, **kwargs):
|
||||
raise Py23Error("'xrange' is not defined. Use 'range' instead.")
|
||||
|
||||
|
||||
import decimal as _decimal
|
||||
|
||||
|
||||
def round2(number, ndigits=None):
|
||||
"""
|
||||
See Python 2 documentation.
|
||||
|
||||
Rounds a number to a given precision in decimal digits (default
|
||||
0 digits). The result is a floating point number. Values are rounded
|
||||
to the closest multiple of 10 to the power minus ndigits; if two
|
||||
multiples are equally close, rounding is done away from 0.
|
||||
|
||||
ndigits may be negative.
|
||||
"""
|
||||
if ndigits is None:
|
||||
ndigits = 0
|
||||
elif hasattr(ndigits, '__index__'):
|
||||
# any type with an __index__ method should be permitted as
|
||||
# a second argument
|
||||
ndigits = ndigits.__index__()
|
||||
|
||||
if ndigits < 0:
|
||||
exponent = 10 ** (-ndigits)
|
||||
quotient, remainder = divmod(number, exponent)
|
||||
if remainder >= exponent//2 and number >= 0:
|
||||
quotient += 1
|
||||
return float(quotient * exponent)
|
||||
else:
|
||||
exponent = _decimal.Decimal('10') ** (-ndigits)
|
||||
|
||||
d = _decimal.Decimal.from_float(number).quantize(
|
||||
exponent, rounding=_decimal.ROUND_HALF_UP)
|
||||
|
||||
return float(d)
|
||||
|
||||
|
||||
def round3(number, ndigits=None):
|
||||
"""
|
||||
See Python 3 documentation: uses Banker's Rounding.
|
||||
|
||||
Delegates to the __round__ method if for some reason this exists.
|
||||
|
||||
If not, rounds a number to a given precision in decimal digits (default
|
||||
0 digits). This returns an int when called with one argument,
|
||||
otherwise the same type as the number. ndigits may be negative.
|
||||
|
||||
ndigits may be negative.
|
||||
|
||||
Derived from python-future:
|
||||
https://github.com/PythonCharmers/python-future/blob/master/src/future/builtins/newround.py
|
||||
"""
|
||||
return_int = False
|
||||
if ndigits is None:
|
||||
return_int = True
|
||||
ndigits = 0
|
||||
|
||||
if hasattr(number, '__round__'):
|
||||
d = number.__round__(ndigits)
|
||||
return int(d) if return_int else float(d)
|
||||
|
||||
if hasattr(ndigits, '__index__'):
|
||||
# any type with an __index__ method should be permitted as
|
||||
# a second argument
|
||||
ndigits = ndigits.__index__()
|
||||
|
||||
if ndigits < 0:
|
||||
exponent = 10 ** (-ndigits)
|
||||
quotient, remainder = divmod(number, exponent)
|
||||
half = exponent//2
|
||||
if remainder > half or (remainder == half and quotient % 2 != 0):
|
||||
quotient += 1
|
||||
d = quotient * exponent
|
||||
else:
|
||||
exponent = _decimal.Decimal('10') ** (-ndigits)
|
||||
|
||||
d = _decimal.Decimal.from_float(number).quantize(
|
||||
exponent, rounding=_decimal.ROUND_HALF_EVEN)
|
||||
|
||||
return int(d) if return_int else float(d)
|
||||
|
||||
|
||||
if PY2:
|
||||
round = round3
|
||||
else:
|
||||
import builtins
|
||||
round = builtins.round
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
|
@ -8,6 +8,8 @@ import sys
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from fontTools.misc.py23 import round2, round3
|
||||
|
||||
|
||||
PIPE_SCRIPT = """\
|
||||
import sys
|
||||
@ -65,5 +67,188 @@ class OpenFuncWrapperTest(unittest.TestCase):
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
|
||||
class Round2Test(unittest.TestCase):
|
||||
"""
|
||||
Test cases taken from cpython 2.7 test suite:
|
||||
|
||||
https://github.com/python/cpython/blob/2.7/Lib/test/test_float.py#L748
|
||||
|
||||
Excludes the test cases that are not supported when using the `decimal`
|
||||
module's `quantize` method.
|
||||
"""
|
||||
|
||||
def test_second_argument_type(self):
|
||||
# any type with an __index__ method should be permitted as
|
||||
# a second argument
|
||||
self.assertAlmostEqual(round2(12.34, True), 12.3)
|
||||
|
||||
class MyIndex(object):
|
||||
def __index__(self): return 4
|
||||
self.assertAlmostEqual(round2(-0.123456, MyIndex()), -0.1235)
|
||||
# but floats should be illegal
|
||||
self.assertRaises(TypeError, round2, 3.14159, 2.0)
|
||||
|
||||
def test_halfway_cases(self):
|
||||
# Halfway cases need special attention, since the current
|
||||
# implementation has to deal with them specially. Note that
|
||||
# 2.x rounds halfway values up (i.e., away from zero) while
|
||||
# 3.x does round-half-to-even.
|
||||
self.assertAlmostEqual(round2(0.125, 2), 0.13)
|
||||
self.assertAlmostEqual(round2(0.375, 2), 0.38)
|
||||
self.assertAlmostEqual(round2(0.625, 2), 0.63)
|
||||
self.assertAlmostEqual(round2(0.875, 2), 0.88)
|
||||
self.assertAlmostEqual(round2(-0.125, 2), -0.13)
|
||||
self.assertAlmostEqual(round2(-0.375, 2), -0.38)
|
||||
self.assertAlmostEqual(round2(-0.625, 2), -0.63)
|
||||
self.assertAlmostEqual(round2(-0.875, 2), -0.88)
|
||||
|
||||
self.assertAlmostEqual(round2(0.25, 1), 0.3)
|
||||
self.assertAlmostEqual(round2(0.75, 1), 0.8)
|
||||
self.assertAlmostEqual(round2(-0.25, 1), -0.3)
|
||||
self.assertAlmostEqual(round2(-0.75, 1), -0.8)
|
||||
|
||||
self.assertEqual(round2(-6.5, 0), -7.0)
|
||||
self.assertEqual(round2(-5.5, 0), -6.0)
|
||||
self.assertEqual(round2(-1.5, 0), -2.0)
|
||||
self.assertEqual(round2(-0.5, 0), -1.0)
|
||||
self.assertEqual(round2(0.5, 0), 1.0)
|
||||
self.assertEqual(round2(1.5, 0), 2.0)
|
||||
self.assertEqual(round2(2.5, 0), 3.0)
|
||||
self.assertEqual(round2(3.5, 0), 4.0)
|
||||
self.assertEqual(round2(4.5, 0), 5.0)
|
||||
self.assertEqual(round2(5.5, 0), 6.0)
|
||||
self.assertEqual(round2(6.5, 0), 7.0)
|
||||
|
||||
# same but without an explicit second argument; in 3.x these
|
||||
# will give integers
|
||||
self.assertEqual(round2(-6.5), -7.0)
|
||||
self.assertEqual(round2(-5.5), -6.0)
|
||||
self.assertEqual(round2(-1.5), -2.0)
|
||||
self.assertEqual(round2(-0.5), -1.0)
|
||||
self.assertEqual(round2(0.5), 1.0)
|
||||
self.assertEqual(round2(1.5), 2.0)
|
||||
self.assertEqual(round2(2.5), 3.0)
|
||||
self.assertEqual(round2(3.5), 4.0)
|
||||
self.assertEqual(round2(4.5), 5.0)
|
||||
self.assertEqual(round2(5.5), 6.0)
|
||||
self.assertEqual(round2(6.5), 7.0)
|
||||
|
||||
self.assertEqual(round2(-25.0, -1), -30.0)
|
||||
self.assertEqual(round2(-15.0, -1), -20.0)
|
||||
self.assertEqual(round2(-5.0, -1), -10.0)
|
||||
self.assertEqual(round2(5.0, -1), 10.0)
|
||||
self.assertEqual(round2(15.0, -1), 20.0)
|
||||
self.assertEqual(round2(25.0, -1), 30.0)
|
||||
self.assertEqual(round2(35.0, -1), 40.0)
|
||||
self.assertEqual(round2(45.0, -1), 50.0)
|
||||
self.assertEqual(round2(55.0, -1), 60.0)
|
||||
self.assertEqual(round2(65.0, -1), 70.0)
|
||||
self.assertEqual(round2(75.0, -1), 80.0)
|
||||
self.assertEqual(round2(85.0, -1), 90.0)
|
||||
self.assertEqual(round2(95.0, -1), 100.0)
|
||||
self.assertEqual(round2(12325.0, -1), 12330.0)
|
||||
self.assertEqual(round2(0, -1), 0.0)
|
||||
|
||||
self.assertEqual(round2(350.0, -2), 400.0)
|
||||
self.assertEqual(round2(450.0, -2), 500.0)
|
||||
|
||||
self.assertAlmostEqual(round2(0.5e21, -21), 1e21)
|
||||
self.assertAlmostEqual(round2(1.5e21, -21), 2e21)
|
||||
self.assertAlmostEqual(round2(2.5e21, -21), 3e21)
|
||||
self.assertAlmostEqual(round2(5.5e21, -21), 6e21)
|
||||
self.assertAlmostEqual(round2(8.5e21, -21), 9e21)
|
||||
|
||||
self.assertAlmostEqual(round2(-1.5e22, -22), -2e22)
|
||||
self.assertAlmostEqual(round2(-0.5e22, -22), -1e22)
|
||||
self.assertAlmostEqual(round2(0.5e22, -22), 1e22)
|
||||
self.assertAlmostEqual(round2(1.5e22, -22), 2e22)
|
||||
|
||||
|
||||
class Round3Test(unittest.TestCase):
|
||||
""" Same as above but results adapted for Python 3 round() """
|
||||
|
||||
def test_second_argument_type(self):
|
||||
# any type with an __index__ method should be permitted as
|
||||
# a second argument
|
||||
self.assertAlmostEqual(round3(12.34, True), 12.3)
|
||||
|
||||
class MyIndex(object):
|
||||
def __index__(self): return 4
|
||||
self.assertAlmostEqual(round3(-0.123456, MyIndex()), -0.1235)
|
||||
# but floats should be illegal
|
||||
self.assertRaises(TypeError, round3, 3.14159, 2.0)
|
||||
|
||||
def test_halfway_cases(self):
|
||||
self.assertAlmostEqual(round3(0.125, 2), 0.12)
|
||||
self.assertAlmostEqual(round3(0.375, 2), 0.38)
|
||||
self.assertAlmostEqual(round3(0.625, 2), 0.62)
|
||||
self.assertAlmostEqual(round3(0.875, 2), 0.88)
|
||||
self.assertAlmostEqual(round3(-0.125, 2), -0.12)
|
||||
self.assertAlmostEqual(round3(-0.375, 2), -0.38)
|
||||
self.assertAlmostEqual(round3(-0.625, 2), -0.62)
|
||||
self.assertAlmostEqual(round3(-0.875, 2), -0.88)
|
||||
|
||||
self.assertAlmostEqual(round3(0.25, 1), 0.2)
|
||||
self.assertAlmostEqual(round3(0.75, 1), 0.8)
|
||||
self.assertAlmostEqual(round3(-0.25, 1), -0.2)
|
||||
self.assertAlmostEqual(round3(-0.75, 1), -0.8)
|
||||
|
||||
self.assertEqual(round3(-6.5, 0), -6.0)
|
||||
self.assertEqual(round3(-5.5, 0), -6.0)
|
||||
self.assertEqual(round3(-1.5, 0), -2.0)
|
||||
self.assertEqual(round3(-0.5, 0), 0.0)
|
||||
self.assertEqual(round3(0.5, 0), 0.0)
|
||||
self.assertEqual(round3(1.5, 0), 2.0)
|
||||
self.assertEqual(round3(2.5, 0), 2.0)
|
||||
self.assertEqual(round3(3.5, 0), 4.0)
|
||||
self.assertEqual(round3(4.5, 0), 4.0)
|
||||
self.assertEqual(round3(5.5, 0), 6.0)
|
||||
self.assertEqual(round3(6.5, 0), 6.0)
|
||||
|
||||
# same but without an explicit second argument; in 2.x these
|
||||
# will give floats
|
||||
self.assertEqual(round3(-6.5), -6)
|
||||
self.assertEqual(round3(-5.5), -6)
|
||||
self.assertEqual(round3(-1.5), -2.0)
|
||||
self.assertEqual(round3(-0.5), 0)
|
||||
self.assertEqual(round3(0.5), 0)
|
||||
self.assertEqual(round3(1.5), 2)
|
||||
self.assertEqual(round3(2.5), 2)
|
||||
self.assertEqual(round3(3.5), 4)
|
||||
self.assertEqual(round3(4.5), 4)
|
||||
self.assertEqual(round3(5.5), 6)
|
||||
self.assertEqual(round3(6.5), 6)
|
||||
|
||||
self.assertEqual(round3(-25.0, -1), -20.0)
|
||||
self.assertEqual(round3(-15.0, -1), -20.0)
|
||||
self.assertEqual(round3(-5.0, -1), 0.0)
|
||||
self.assertEqual(round3(5.0, -1), 0.0)
|
||||
self.assertEqual(round3(15.0, -1), 20.0)
|
||||
self.assertEqual(round3(25.0, -1), 20.0)
|
||||
self.assertEqual(round3(35.0, -1), 40.0)
|
||||
self.assertEqual(round3(45.0, -1), 40.0)
|
||||
self.assertEqual(round3(55.0, -1), 60.0)
|
||||
self.assertEqual(round3(65.0, -1), 60.0)
|
||||
self.assertEqual(round3(75.0, -1), 80.0)
|
||||
self.assertEqual(round3(85.0, -1), 80.0)
|
||||
self.assertEqual(round3(95.0, -1), 100.0)
|
||||
self.assertEqual(round3(12325.0, -1), 12320.0)
|
||||
self.assertEqual(round3(0, -1), 0.0)
|
||||
|
||||
self.assertEqual(round3(350.0, -2), 400.0)
|
||||
self.assertEqual(round3(450.0, -2), 400.0)
|
||||
|
||||
self.assertAlmostEqual(round3(0.5e21, -21), 0.0)
|
||||
self.assertAlmostEqual(round3(1.5e21, -21), 2e21)
|
||||
self.assertAlmostEqual(round3(2.5e21, -21), 2e21)
|
||||
self.assertAlmostEqual(round3(5.5e21, -21), 6e21)
|
||||
self.assertAlmostEqual(round3(8.5e21, -21), 8e21)
|
||||
|
||||
self.assertAlmostEqual(round3(-1.5e22, -22), -2e22)
|
||||
self.assertAlmostEqual(round3(-0.5e22, -22), 0.0)
|
||||
self.assertAlmostEqual(round3(0.5e22, -22), 0.0)
|
||||
self.assertAlmostEqual(round3(1.5e22, -22), 2e22)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
Loading…
x
Reference in New Issue
Block a user