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',
|
__all__ = ['basestring', 'unicode', 'unichr', 'byteord', 'bytechr', 'BytesIO',
|
||||||
'StringIO', 'UnicodeIO', 'strjoin', 'bytesjoin', 'tobytes', 'tostr',
|
'StringIO', 'UnicodeIO', 'strjoin', 'bytesjoin', 'tobytes', 'tostr',
|
||||||
'tounicode', 'Tag', 'open', 'range', 'xrange', 'Py23Error']
|
'tounicode', 'Tag', 'open', 'range', 'xrange', 'round', 'Py23Error']
|
||||||
|
|
||||||
|
|
||||||
class Py23Error(NotImplementedError):
|
class Py23Error(NotImplementedError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
PY3 = sys.version_info[0] == 3
|
||||||
|
PY2 = sys.version_info[0] == 2
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
basestring = basestring
|
basestring = basestring
|
||||||
except NameError:
|
except NameError:
|
||||||
@ -255,6 +259,94 @@ def xrange(*args, **kwargs):
|
|||||||
raise Py23Error("'xrange' is not defined. Use 'range' instead.")
|
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
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from fontTools.misc.py23 import round2, round3
|
||||||
|
|
||||||
|
|
||||||
PIPE_SCRIPT = """\
|
PIPE_SCRIPT = """\
|
||||||
import sys
|
import sys
|
||||||
@ -65,5 +67,188 @@ class OpenFuncWrapperTest(unittest.TestCase):
|
|||||||
self.assertEqual(result, expected)
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user