"""Tests for Lib/fractions.py.""" from decimal import Decimal from test.support import requires_IEEE_754 import math import numbers import operator import fractions import functools import os import sys import typing import unittest from copy import copy, deepcopy import pickle from pickle import dumps, loads F = fractions.Fraction #locate file with float format test values test_dir = os.path.dirname(__file__) or os.curdir format_testfile = os.path.join(test_dir, 'mathdata', 'formatfloat_testcases.txt') class DummyFloat(object): """Dummy float class for testing comparisons with Fractions""" def __init__(self, value): if not isinstance(value, float): raise TypeError("DummyFloat can only be initialized from float") self.value = value def _richcmp(self, other, op): if isinstance(other, numbers.Rational): return op(F.from_float(self.value), other) elif isinstance(other, DummyFloat): return op(self.value, other.value) else: return NotImplemented def __eq__(self, other): return self._richcmp(other, operator.eq) def __le__(self, other): return self._richcmp(other, operator.le) def __lt__(self, other): return self._richcmp(other, operator.lt) def __ge__(self, other): return self._richcmp(other, operator.ge) def __gt__(self, other): return self._richcmp(other, operator.gt) # shouldn't be calling __float__ at all when doing comparisons def __float__(self): assert False, "__float__ should not be invoked for comparisons" # same goes for subtraction def __sub__(self, other): assert False, "__sub__ should not be invoked for comparisons" __rsub__ = __sub__ class DummyRational(object): """Test comparison of Fraction with a naive rational implementation.""" def __init__(self, num, den): g = math.gcd(num, den) self.num = num // g self.den = den // g def __eq__(self, other): if isinstance(other, fractions.Fraction): return (self.num == other._numerator and self.den == other._denominator) else: return NotImplemented def __lt__(self, other): return(self.num * other._denominator < self.den * other._numerator) def __gt__(self, other): return(self.num * other._denominator > self.den * other._numerator) def __le__(self, other): return(self.num * other._denominator <= self.den * other._numerator) def __ge__(self, other): return(self.num * other._denominator >= self.den * other._numerator) # this class is for testing comparisons; conversion to float # should never be used for a comparison, since it loses accuracy def __float__(self): assert False, "__float__ should not be invoked" class DummyFraction(fractions.Fraction): """Dummy Fraction subclass for copy and deepcopy testing.""" def _components(r): return (r.numerator, r.denominator) class FractionTest(unittest.TestCase): def assertTypedEquals(self, expected, actual): """Asserts that both the types and values are the same.""" self.assertEqual(type(expected), type(actual)) self.assertEqual(expected, actual) def assertTypedTupleEquals(self, expected, actual): """Asserts that both the types and values in the tuples are the same.""" self.assertTupleEqual(expected, actual) self.assertListEqual(list(map(type, expected)), list(map(type, actual))) def assertRaisesMessage(self, exc_type, message, callable, *args, **kwargs): """Asserts that callable(*args, **kwargs) raises exc_type(message).""" try: callable(*args, **kwargs) except exc_type as e: self.assertEqual(message, str(e)) else: self.fail("%s not raised" % exc_type.__name__) def testInit(self): self.assertEqual((0, 1), _components(F())) self.assertEqual((7, 1), _components(F(7))) self.assertEqual((7, 3), _components(F(F(7, 3)))) self.assertEqual((-1, 1), _components(F(-1, 1))) self.assertEqual((-1, 1), _components(F(1, -1))) self.assertEqual((1, 1), _components(F(-2, -2))) self.assertEqual((1, 2), _components(F(5, 10))) self.assertEqual((7, 15), _components(F(7, 15))) self.assertEqual((10**23, 1), _components(F(10**23))) self.assertEqual((3, 77), _components(F(F(3, 7), 11))) self.assertEqual((-9, 5), _components(F(2, F(-10, 9)))) self.assertEqual((2486, 2485), _components(F(F(22, 7), F(355, 113)))) self.assertRaisesMessage(ZeroDivisionError, "Fraction(12, 0)", F, 12, 0) self.assertRaises(TypeError, F, 1.5 + 3j) self.assertRaises(TypeError, F, "3/2", 3) self.assertRaises(TypeError, F, 3, 0j) self.assertRaises(TypeError, F, 3, 1j) self.assertRaises(TypeError, F, 1, 2, 3) @requires_IEEE_754 def testInitFromFloat(self): self.assertEqual((5, 2), _components(F(2.5))) self.assertEqual((0, 1), _components(F(-0.0))) self.assertEqual((3602879701896397, 36028797018963968), _components(F(0.1))) # bug 16469: error types should be consistent with float -> int self.assertRaises(ValueError, F, float('nan')) self.assertRaises(OverflowError, F, float('inf')) self.assertRaises(OverflowError, F, float('-inf')) def testInitFromDecimal(self): self.assertEqual((11, 10), _components(F(Decimal('1.1')))) self.assertEqual((7, 200), _components(F(Decimal('3.5e-2')))) self.assertEqual((0, 1), _components(F(Decimal('.000e20')))) # bug 16469: error types should be consistent with decimal -> int self.assertRaises(ValueError, F, Decimal('nan')) self.assertRaises(ValueError, F, Decimal('snan')) self.assertRaises(OverflowError, F, Decimal('inf')) self.assertRaises(OverflowError, F, Decimal('-inf')) def testFromString(self): self.assertEqual((5, 1), _components(F("5"))) self.assertEqual((3, 2), _components(F("3/2"))) self.assertEqual((3, 2), _components(F("3 / 2"))) self.assertEqual((3, 2), _components(F(" \n +3/2"))) self.assertEqual((-3, 2), _components(F("-3/2 "))) self.assertEqual((13, 2), _components(F(" 013/02 \n "))) self.assertEqual((16, 5), _components(F(" 3.2 "))) self.assertEqual((-16, 5), _components(F(" -3.2 "))) self.assertEqual((-3, 1), _components(F(" -3. "))) self.assertEqual((3, 5), _components(F(" .6 "))) self.assertEqual((1, 3125), _components(F("32.e-5"))) self.assertEqual((1000000, 1), _components(F("1E+06"))) self.assertEqual((-12300, 1), _components(F("-1.23e4"))) self.assertEqual((0, 1), _components(F(" .0e+0\t"))) self.assertEqual((0, 1), _components(F("-0.000e0"))) self.assertEqual((123, 1), _components(F("1_2_3"))) self.assertEqual((41, 107), _components(F("1_2_3/3_2_1"))) self.assertEqual((6283, 2000), _components(F("3.14_15"))) self.assertEqual((6283, 2*10**13), _components(F("3.14_15e-1_0"))) self.assertEqual((101, 100), _components(F("1.01"))) self.assertEqual((101, 100), _components(F("1.0_1"))) self.assertRaisesMessage( ZeroDivisionError, "Fraction(3, 0)", F, "3/0") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '3/'", F, "3/") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '/2'", F, "/2") self.assertRaisesMessage( # Denominators don't need a sign. ValueError, "Invalid literal for Fraction: '3/+2'", F, "3/+2") self.assertRaisesMessage( # Imitate float's parsing. ValueError, "Invalid literal for Fraction: '+ 3/2'", F, "+ 3/2") self.assertRaisesMessage( # Avoid treating '.' as a regex special character. ValueError, "Invalid literal for Fraction: '3a2'", F, "3a2") self.assertRaisesMessage( # Don't accept combinations of decimals and rationals. ValueError, "Invalid literal for Fraction: '3/7.2'", F, "3/7.2") self.assertRaisesMessage( # Don't accept combinations of decimals and rationals. ValueError, "Invalid literal for Fraction: '3.2/7'", F, "3.2/7") self.assertRaisesMessage( # Allow 3. and .3, but not . ValueError, "Invalid literal for Fraction: '.'", F, ".") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '_'", F, "_") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '_1'", F, "_1") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '1__2'", F, "1__2") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '/_'", F, "/_") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '1_/'", F, "1_/") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '_1/'", F, "_1/") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '1__2/'", F, "1__2/") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '1/_'", F, "1/_") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '1/_1'", F, "1/_1") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '1/1__2'", F, "1/1__2") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '1._111'", F, "1._111") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '1.1__1'", F, "1.1__1") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '1.1e+_1'", F, "1.1e+_1") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '1.1e+1__1'", F, "1.1e+1__1") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '123.dd'", F, "123.dd") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '123.5_dd'", F, "123.5_dd") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: 'dd.5'", F, "dd.5") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '7_dd'", F, "7_dd") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '1/dd'", F, "1/dd") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '1/123_dd'", F, "1/123_dd") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '789edd'", F, "789edd") self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '789e2_dd'", F, "789e2_dd") # Test catastrophic backtracking. val = "9"*50 + "_" self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '" + val + "'", F, val) self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '1/" + val + "'", F, "1/" + val) self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '1." + val + "'", F, "1." + val) self.assertRaisesMessage( ValueError, "Invalid literal for Fraction: '1.1+e" + val + "'", F, "1.1+e" + val) def testImmutable(self): r = F(7, 3) r.__init__(2, 15) self.assertEqual((7, 3), _components(r)) self.assertRaises(AttributeError, setattr, r, 'numerator', 12) self.assertRaises(AttributeError, setattr, r, 'denominator', 6) self.assertEqual((7, 3), _components(r)) # But if you _really_ need to: r._numerator = 4 r._denominator = 2 self.assertEqual((4, 2), _components(r)) # Which breaks some important operations: self.assertNotEqual(F(4, 2), r) def testFromFloat(self): self.assertRaises(TypeError, F.from_float, 3+4j) self.assertEqual((10, 1), _components(F.from_float(10))) bigint = 1234567890123456789 self.assertEqual((bigint, 1), _components(F.from_float(bigint))) self.assertEqual((0, 1), _components(F.from_float(-0.0))) self.assertEqual((10, 1), _components(F.from_float(10.0))) self.assertEqual((-5, 2), _components(F.from_float(-2.5))) self.assertEqual((99999999999999991611392, 1), _components(F.from_float(1e23))) self.assertEqual(float(10**23), float(F.from_float(1e23))) self.assertEqual((3602879701896397, 1125899906842624), _components(F.from_float(3.2))) self.assertEqual(3.2, float(F.from_float(3.2))) inf = 1e1000 nan = inf - inf # bug 16469: error types should be consistent with float -> int self.assertRaisesMessage( OverflowError, "cannot convert Infinity to integer ratio", F.from_float, inf) self.assertRaisesMessage( OverflowError, "cannot convert Infinity to integer ratio", F.from_float, -inf) self.assertRaisesMessage( ValueError, "cannot convert NaN to integer ratio", F.from_float, nan) def testFromDecimal(self): self.assertRaises(TypeError, F.from_decimal, 3+4j) self.assertEqual(F(10, 1), F.from_decimal(10)) self.assertEqual(F(0), F.from_decimal(Decimal("-0"))) self.assertEqual(F(5, 10), F.from_decimal(Decimal("0.5"))) self.assertEqual(F(5, 1000), F.from_decimal(Decimal("5e-3"))) self.assertEqual(F(5000), F.from_decimal(Decimal("5e3"))) self.assertEqual(1 - F(1, 10**30), F.from_decimal(Decimal("0." + "9" * 30))) # bug 16469: error types should be consistent with decimal -> int self.assertRaisesMessage( OverflowError, "cannot convert Infinity to integer ratio", F.from_decimal, Decimal("inf")) self.assertRaisesMessage( OverflowError, "cannot convert Infinity to integer ratio", F.from_decimal, Decimal("-inf")) self.assertRaisesMessage( ValueError, "cannot convert NaN to integer ratio", F.from_decimal, Decimal("nan")) self.assertRaisesMessage( ValueError, "cannot convert NaN to integer ratio", F.from_decimal, Decimal("snan")) def test_is_integer(self): self.assertTrue(F(1, 1).is_integer()) self.assertTrue(F(-1, 1).is_integer()) self.assertTrue(F(1, -1).is_integer()) self.assertTrue(F(2, 2).is_integer()) self.assertTrue(F(-2, 2).is_integer()) self.assertTrue(F(2, -2).is_integer()) self.assertFalse(F(1, 2).is_integer()) self.assertFalse(F(-1, 2).is_integer()) self.assertFalse(F(1, -2).is_integer()) self.assertFalse(F(-1, -2).is_integer()) def test_as_integer_ratio(self): self.assertEqual(F(4, 6).as_integer_ratio(), (2, 3)) self.assertEqual(F(-4, 6).as_integer_ratio(), (-2, 3)) self.assertEqual(F(4, -6).as_integer_ratio(), (-2, 3)) self.assertEqual(F(0, 6).as_integer_ratio(), (0, 1)) def testLimitDenominator(self): rpi = F('3.1415926535897932') self.assertEqual(rpi.limit_denominator(10000), F(355, 113)) self.assertEqual(-rpi.limit_denominator(10000), F(-355, 113)) self.assertEqual(rpi.limit_denominator(113), F(355, 113)) self.assertEqual(rpi.limit_denominator(112), F(333, 106)) self.assertEqual(F(201, 200).limit_denominator(100), F(1)) self.assertEqual(F(201, 200).limit_denominator(101), F(102, 101)) self.assertEqual(F(0).limit_denominator(10000), F(0)) for i in (0, -1): self.assertRaisesMessage( ValueError, "max_denominator should be at least 1", F(1).limit_denominator, i) def testConversions(self): self.assertTypedEquals(-1, math.trunc(F(-11, 10))) self.assertTypedEquals(1, math.trunc(F(11, 10))) self.assertTypedEquals(-2, math.floor(F(-11, 10))) self.assertTypedEquals(-1, math.ceil(F(-11, 10))) self.assertTypedEquals(-1, math.ceil(F(-10, 10))) self.assertTypedEquals(-1, int(F(-11, 10))) self.assertTypedEquals(0, round(F(-1, 10))) self.assertTypedEquals(0, round(F(-5, 10))) self.assertTypedEquals(-2, round(F(-15, 10))) self.assertTypedEquals(-1, round(F(-7, 10))) self.assertEqual(False, bool(F(0, 1))) self.assertEqual(True, bool(F(3, 2))) self.assertTypedEquals(0.1, float(F(1, 10))) # Check that __float__ isn't implemented by converting the # numerator and denominator to float before dividing. self.assertRaises(OverflowError, float, int('2'*400+'7')) self.assertAlmostEqual(2.0/3, float(F(int('2'*400+'7'), int('3'*400+'1')))) self.assertTypedEquals(0.1+0j, complex(F(1,10))) def testSupportsInt(self): # See bpo-44547. f = F(3, 2) self.assertIsInstance(f, typing.SupportsInt) self.assertEqual(int(f), 1) self.assertEqual(type(int(f)), int) def testIntGuaranteesIntReturn(self): # Check that int(some_fraction) gives a result of exact type `int` # even if the fraction is using some other Integral type for its # numerator and denominator. class CustomInt(int): """ Subclass of int with just enough machinery to convince the Fraction constructor to produce something with CustomInt numerator and denominator. """ @property def numerator(self): return self @property def denominator(self): return CustomInt(1) def __mul__(self, other): return CustomInt(int(self) * int(other)) def __floordiv__(self, other): return CustomInt(int(self) // int(other)) f = F(CustomInt(13), CustomInt(5)) self.assertIsInstance(f.numerator, CustomInt) self.assertIsInstance(f.denominator, CustomInt) self.assertIsInstance(f, typing.SupportsInt) self.assertEqual(int(f), 2) self.assertEqual(type(int(f)), int) def testBoolGuarateesBoolReturn(self): # Ensure that __bool__ is used on numerator which guarantees a bool # return. See also bpo-39274. @functools.total_ordering class CustomValue: denominator = 1 def __init__(self, value): self.value = value def __bool__(self): return bool(self.value) @property def numerator(self): # required to preserve `self` during instantiation return self def __eq__(self, other): raise AssertionError("Avoid comparisons in Fraction.__bool__") __lt__ = __eq__ # We did not implement all abstract methods, so register: numbers.Rational.register(CustomValue) numerator = CustomValue(1) r = F(numerator) # ensure the numerator was not lost during instantiation: self.assertIs(r.numerator, numerator) self.assertIs(bool(r), True) numerator = CustomValue(0) r = F(numerator) self.assertIs(bool(r), False) def testRound(self): self.assertTypedEquals(F(-200), round(F(-150), -2)) self.assertTypedEquals(F(-200), round(F(-250), -2)) self.assertTypedEquals(F(30), round(F(26), -1)) self.assertTypedEquals(F(-2, 10), round(F(-15, 100), 1)) self.assertTypedEquals(F(-2, 10), round(F(-25, 100), 1)) def testArithmetic(self): self.assertEqual(F(1, 2), F(1, 10) + F(2, 5)) self.assertEqual(F(-3, 10), F(1, 10) - F(2, 5)) self.assertEqual(F(1, 25), F(1, 10) * F(2, 5)) self.assertEqual(F(5, 6), F(2, 3) * F(5, 4)) self.assertEqual(F(1, 4), F(1, 10) / F(2, 5)) self.assertEqual(F(-15, 8), F(3, 4) / F(-2, 5)) self.assertRaises(ZeroDivisionError, operator.truediv, F(1), F(0)) self.assertTypedEquals(2, F(9, 10) // F(2, 5)) self.assertTypedEquals(10**23, F(10**23, 1) // F(1)) self.assertEqual(F(5, 6), F(7, 3) % F(3, 2)) self.assertEqual(F(2, 3), F(-7, 3) % F(3, 2)) self.assertEqual((F(1), F(5, 6)), divmod(F(7, 3), F(3, 2))) self.assertEqual((F(-2), F(2, 3)), divmod(F(-7, 3), F(3, 2))) self.assertEqual(F(8, 27), F(2, 3) ** F(3)) self.assertEqual(F(27, 8), F(2, 3) ** F(-3)) self.assertTypedEquals(2.0, F(4) ** F(1, 2)) self.assertEqual(F(1, 1), +F(1, 1)) z = pow(F(-1), F(1, 2)) self.assertAlmostEqual(z.real, 0) self.assertEqual(z.imag, 1) # Regression test for #27539. p = F(-1, 2) ** 0 self.assertEqual(p, F(1, 1)) self.assertEqual(p.numerator, 1) self.assertEqual(p.denominator, 1) p = F(-1, 2) ** -1 self.assertEqual(p, F(-2, 1)) self.assertEqual(p.numerator, -2) self.assertEqual(p.denominator, 1) p = F(-1, 2) ** -2 self.assertEqual(p, F(4, 1)) self.assertEqual(p.numerator, 4) self.assertEqual(p.denominator, 1) def testLargeArithmetic(self): self.assertTypedEquals( F(10101010100808080808080808101010101010000000000000000, 1010101010101010101010101011111111101010101010101010101010101), F(10**35+1, 10**27+1) % F(10**27+1, 10**35-1) ) self.assertTypedEquals( F(7, 1901475900342344102245054808064), F(-2**100, 3) % F(5, 2**100) ) self.assertTypedTupleEquals( (9999999999999999, F(10101010100808080808080808101010101010000000000000000, 1010101010101010101010101011111111101010101010101010101010101)), divmod(F(10**35+1, 10**27+1), F(10**27+1, 10**35-1)) ) self.assertTypedEquals( -2 ** 200 // 15, F(-2**100, 3) // F(5, 2**100) ) self.assertTypedEquals( 1, F(5, 2**100) // F(3, 2**100) ) self.assertTypedEquals( (1, F(2, 2**100)), divmod(F(5, 2**100), F(3, 2**100)) ) self.assertTypedTupleEquals( (-2 ** 200 // 15, F(7, 1901475900342344102245054808064)), divmod(F(-2**100, 3), F(5, 2**100)) ) def testMixedArithmetic(self): self.assertTypedEquals(F(11, 10), F(1, 10) + 1) self.assertTypedEquals(1.1, F(1, 10) + 1.0) self.assertTypedEquals(1.1 + 0j, F(1, 10) + (1.0 + 0j)) self.assertTypedEquals(F(11, 10), 1 + F(1, 10)) self.assertTypedEquals(1.1, 1.0 + F(1, 10)) self.assertTypedEquals(1.1 + 0j, (1.0 + 0j) + F(1, 10)) self.assertTypedEquals(F(-9, 10), F(1, 10) - 1) self.assertTypedEquals(-0.9, F(1, 10) - 1.0) self.assertTypedEquals(-0.9 + 0j, F(1, 10) - (1.0 + 0j)) self.assertTypedEquals(F(9, 10), 1 - F(1, 10)) self.assertTypedEquals(0.9, 1.0 - F(1, 10)) self.assertTypedEquals(0.9 + 0j, (1.0 + 0j) - F(1, 10)) self.assertTypedEquals(F(1, 10), F(1, 10) * 1) self.assertTypedEquals(0.1, F(1, 10) * 1.0) self.assertTypedEquals(0.1 + 0j, F(1, 10) * (1.0 + 0j)) self.assertTypedEquals(F(1, 10), 1 * F(1, 10)) self.assertTypedEquals(0.1, 1.0 * F(1, 10)) self.assertTypedEquals(0.1 + 0j, (1.0 + 0j) * F(1, 10)) self.assertTypedEquals(F(1, 10), F(1, 10) / 1) self.assertTypedEquals(0.1, F(1, 10) / 1.0) self.assertTypedEquals(0.1 + 0j, F(1, 10) / (1.0 + 0j)) self.assertTypedEquals(F(10, 1), 1 / F(1, 10)) self.assertTypedEquals(10.0, 1.0 / F(1, 10)) self.assertTypedEquals(10.0 + 0j, (1.0 + 0j) / F(1, 10)) self.assertTypedEquals(0, F(1, 10) // 1) self.assertTypedEquals(0.0, F(1, 10) // 1.0) self.assertTypedEquals(10, 1 // F(1, 10)) self.assertTypedEquals(10**23, 10**22 // F(1, 10)) self.assertTypedEquals(1.0 // 0.1, 1.0 // F(1, 10)) self.assertTypedEquals(F(1, 10), F(1, 10) % 1) self.assertTypedEquals(0.1, F(1, 10) % 1.0) self.assertTypedEquals(F(0, 1), 1 % F(1, 10)) self.assertTypedEquals(1.0 % 0.1, 1.0 % F(1, 10)) self.assertTypedEquals(0.1, F(1, 10) % float('inf')) self.assertTypedEquals(float('-inf'), F(1, 10) % float('-inf')) self.assertTypedEquals(float('inf'), F(-1, 10) % float('inf')) self.assertTypedEquals(-0.1, F(-1, 10) % float('-inf')) self.assertTypedTupleEquals((0, F(1, 10)), divmod(F(1, 10), 1)) self.assertTypedTupleEquals(divmod(0.1, 1.0), divmod(F(1, 10), 1.0)) self.assertTypedTupleEquals((10, F(0)), divmod(1, F(1, 10))) self.assertTypedTupleEquals(divmod(1.0, 0.1), divmod(1.0, F(1, 10))) self.assertTypedTupleEquals(divmod(0.1, float('inf')), divmod(F(1, 10), float('inf'))) self.assertTypedTupleEquals(divmod(0.1, float('-inf')), divmod(F(1, 10), float('-inf'))) self.assertTypedTupleEquals(divmod(-0.1, float('inf')), divmod(F(-1, 10), float('inf'))) self.assertTypedTupleEquals(divmod(-0.1, float('-inf')), divmod(F(-1, 10), float('-inf'))) # ** has more interesting conversion rules. self.assertTypedEquals(F(100, 1), F(1, 10) ** -2) self.assertTypedEquals(F(100, 1), F(10, 1) ** 2) self.assertTypedEquals(0.1, F(1, 10) ** 1.0) self.assertTypedEquals(0.1 + 0j, F(1, 10) ** (1.0 + 0j)) self.assertTypedEquals(4 , 2 ** F(2, 1)) z = pow(-1, F(1, 2)) self.assertAlmostEqual(0, z.real) self.assertEqual(1, z.imag) self.assertTypedEquals(F(1, 4) , 2 ** F(-2, 1)) self.assertTypedEquals(2.0 , 4 ** F(1, 2)) self.assertTypedEquals(0.25, 2.0 ** F(-2, 1)) self.assertTypedEquals(1.0 + 0j, (1.0 + 0j) ** F(1, 10)) self.assertRaises(ZeroDivisionError, operator.pow, F(0, 1), -2) def testMixingWithDecimal(self): # Decimal refuses mixed arithmetic (but not mixed comparisons) self.assertRaises(TypeError, operator.add, F(3,11), Decimal('3.1415926')) self.assertRaises(TypeError, operator.add, Decimal('3.1415926'), F(3,11)) def testComparisons(self): self.assertTrue(F(1, 2) < F(2, 3)) self.assertFalse(F(1, 2) < F(1, 2)) self.assertTrue(F(1, 2) <= F(2, 3)) self.assertTrue(F(1, 2) <= F(1, 2)) self.assertFalse(F(2, 3) <= F(1, 2)) self.assertTrue(F(1, 2) == F(1, 2)) self.assertFalse(F(1, 2) == F(1, 3)) self.assertFalse(F(1, 2) != F(1, 2)) self.assertTrue(F(1, 2) != F(1, 3)) def testComparisonsDummyRational(self): self.assertTrue(F(1, 2) == DummyRational(1, 2)) self.assertTrue(DummyRational(1, 2) == F(1, 2)) self.assertFalse(F(1, 2) == DummyRational(3, 4)) self.assertFalse(DummyRational(3, 4) == F(1, 2)) self.assertTrue(F(1, 2) < DummyRational(3, 4)) self.assertFalse(F(1, 2) < DummyRational(1, 2)) self.assertFalse(F(1, 2) < DummyRational(1, 7)) self.assertFalse(F(1, 2) > DummyRational(3, 4)) self.assertFalse(F(1, 2) > DummyRational(1, 2)) self.assertTrue(F(1, 2) > DummyRational(1, 7)) self.assertTrue(F(1, 2) <= DummyRational(3, 4)) self.assertTrue(F(1, 2) <= DummyRational(1, 2)) self.assertFalse(F(1, 2) <= DummyRational(1, 7)) self.assertFalse(F(1, 2) >= DummyRational(3, 4)) self.assertTrue(F(1, 2) >= DummyRational(1, 2)) self.assertTrue(F(1, 2) >= DummyRational(1, 7)) self.assertTrue(DummyRational(1, 2) < F(3, 4)) self.assertFalse(DummyRational(1, 2) < F(1, 2)) self.assertFalse(DummyRational(1, 2) < F(1, 7)) self.assertFalse(DummyRational(1, 2) > F(3, 4)) self.assertFalse(DummyRational(1, 2) > F(1, 2)) self.assertTrue(DummyRational(1, 2) > F(1, 7)) self.assertTrue(DummyRational(1, 2) <= F(3, 4)) self.assertTrue(DummyRational(1, 2) <= F(1, 2)) self.assertFalse(DummyRational(1, 2) <= F(1, 7)) self.assertFalse(DummyRational(1, 2) >= F(3, 4)) self.assertTrue(DummyRational(1, 2) >= F(1, 2)) self.assertTrue(DummyRational(1, 2) >= F(1, 7)) def testComparisonsDummyFloat(self): x = DummyFloat(1./3.) y = F(1, 3) self.assertTrue(x != y) self.assertTrue(x < y or x > y) self.assertFalse(x == y) self.assertFalse(x <= y and x >= y) self.assertTrue(y != x) self.assertTrue(y < x or y > x) self.assertFalse(y == x) self.assertFalse(y <= x and y >= x) def testMixedLess(self): self.assertTrue(2 < F(5, 2)) self.assertFalse(2 < F(4, 2)) self.assertTrue(F(5, 2) < 3) self.assertFalse(F(4, 2) < 2) self.assertTrue(F(1, 2) < 0.6) self.assertFalse(F(1, 2) < 0.4) self.assertTrue(0.4 < F(1, 2)) self.assertFalse(0.5 < F(1, 2)) self.assertFalse(float('inf') < F(1, 2)) self.assertTrue(float('-inf') < F(0, 10)) self.assertFalse(float('nan') < F(-3, 7)) self.assertTrue(F(1, 2) < float('inf')) self.assertFalse(F(17, 12) < float('-inf')) self.assertFalse(F(144, -89) < float('nan')) def testMixedLessEqual(self): self.assertTrue(0.5 <= F(1, 2)) self.assertFalse(0.6 <= F(1, 2)) self.assertTrue(F(1, 2) <= 0.5) self.assertFalse(F(1, 2) <= 0.4) self.assertTrue(2 <= F(4, 2)) self.assertFalse(2 <= F(3, 2)) self.assertTrue(F(4, 2) <= 2) self.assertFalse(F(5, 2) <= 2) self.assertFalse(float('inf') <= F(1, 2)) self.assertTrue(float('-inf') <= F(0, 10)) self.assertFalse(float('nan') <= F(-3, 7)) self.assertTrue(F(1, 2) <= float('inf')) self.assertFalse(F(17, 12) <= float('-inf')) self.assertFalse(F(144, -89) <= float('nan')) def testBigFloatComparisons(self): # Because 10**23 can't be represented exactly as a float: self.assertFalse(F(10**23) == float(10**23)) # The first test demonstrates why these are important. self.assertFalse(1e23 < float(F(math.trunc(1e23) + 1))) self.assertTrue(1e23 < F(math.trunc(1e23) + 1)) self.assertFalse(1e23 <= F(math.trunc(1e23) - 1)) self.assertTrue(1e23 > F(math.trunc(1e23) - 1)) self.assertFalse(1e23 >= F(math.trunc(1e23) + 1)) def testBigComplexComparisons(self): self.assertFalse(F(10**23) == complex(10**23)) self.assertRaises(TypeError, operator.gt, F(10**23), complex(10**23)) self.assertRaises(TypeError, operator.le, F(10**23), complex(10**23)) x = F(3, 8) z = complex(0.375, 0.0) w = complex(0.375, 0.2) self.assertTrue(x == z) self.assertFalse(x != z) self.assertFalse(x == w) self.assertTrue(x != w) for op in operator.lt, operator.le, operator.gt, operator.ge: self.assertRaises(TypeError, op, x, z) self.assertRaises(TypeError, op, z, x) self.assertRaises(TypeError, op, x, w) self.assertRaises(TypeError, op, w, x) def testMixedEqual(self): self.assertTrue(0.5 == F(1, 2)) self.assertFalse(0.6 == F(1, 2)) self.assertTrue(F(1, 2) == 0.5) self.assertFalse(F(1, 2) == 0.4) self.assertTrue(2 == F(4, 2)) self.assertFalse(2 == F(3, 2)) self.assertTrue(F(4, 2) == 2) self.assertFalse(F(5, 2) == 2) self.assertFalse(F(5, 2) == float('nan')) self.assertFalse(float('nan') == F(3, 7)) self.assertFalse(F(5, 2) == float('inf')) self.assertFalse(float('-inf') == F(2, 5)) def testStringification(self): self.assertEqual("Fraction(7, 3)", repr(F(7, 3))) self.assertEqual("Fraction(6283185307, 2000000000)", repr(F('3.1415926535'))) self.assertEqual("Fraction(-1, 100000000000000000000)", repr(F(1, -10**20))) self.assertEqual("7/3", str(F(7, 3))) self.assertEqual("7", str(F(7, 1))) def testHash(self): hmod = sys.hash_info.modulus hinf = sys.hash_info.inf self.assertEqual(hash(2.5), hash(F(5, 2))) self.assertEqual(hash(10**50), hash(F(10**50))) self.assertNotEqual(hash(float(10**23)), hash(F(10**23))) self.assertEqual(hinf, hash(F(1, hmod))) # Check that __hash__ produces the same value as hash(), for # consistency with int and Decimal. (See issue #10356.) self.assertEqual(hash(F(-1)), F(-1).__hash__()) def testApproximatePi(self): # Algorithm borrowed from # http://docs.python.org/lib/decimal-recipes.html three = F(3) lasts, t, s, n, na, d, da = 0, three, 3, 1, 0, 0, 24 while abs(s - lasts) > F(1, 10**9): lasts = s n, na = n+na, na+8 d, da = d+da, da+32 t = (t * n) / d s += t self.assertAlmostEqual(math.pi, s) def testApproximateCos1(self): # Algorithm borrowed from # http://docs.python.org/lib/decimal-recipes.html x = F(1) i, lasts, s, fact, num, sign = 0, 0, F(1), 1, 1, 1 while abs(s - lasts) > F(1, 10**9): lasts = s i += 2 fact *= i * (i-1) num *= x * x sign *= -1 s += num / fact * sign self.assertAlmostEqual(math.cos(1), s) def test_copy_deepcopy_pickle(self): r = F(13, 7) dr = DummyFraction(13, 7) for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): self.assertEqual(r, loads(dumps(r, proto))) self.assertEqual(id(r), id(copy(r))) self.assertEqual(id(r), id(deepcopy(r))) self.assertNotEqual(id(dr), id(copy(dr))) self.assertNotEqual(id(dr), id(deepcopy(dr))) self.assertTypedEquals(dr, copy(dr)) self.assertTypedEquals(dr, deepcopy(dr)) def test_slots(self): # Issue 4998 r = F(13, 7) self.assertRaises(AttributeError, setattr, r, 'a', 10) def test_int_subclass(self): class myint(int): def __mul__(self, other): return type(self)(int(self) * int(other)) def __floordiv__(self, other): return type(self)(int(self) // int(other)) def __mod__(self, other): x = type(self)(int(self) % int(other)) return x @property def numerator(self): return type(self)(int(self)) @property def denominator(self): return type(self)(1) f = fractions.Fraction(myint(1 * 3), myint(2 * 3)) self.assertEqual(f.numerator, 1) self.assertEqual(f.denominator, 2) self.assertEqual(type(f.numerator), myint) self.assertEqual(type(f.denominator), myint) def test_format_no_presentation_type(self): # Triples (fraction, specification, expected_result). testcases = [ # Explicit sign handling (F(2, 3), '+', '+2/3'), (F(-2, 3), '+', '-2/3'), (F(3), '+', '+3'), (F(-3), '+', '-3'), (F(2, 3), ' ', ' 2/3'), (F(-2, 3), ' ', '-2/3'), (F(3), ' ', ' 3'), (F(-3), ' ', '-3'), (F(2, 3), '-', '2/3'), (F(-2, 3), '-', '-2/3'), (F(3), '-', '3'), (F(-3), '-', '-3'), # Padding (F(0), '5', ' 0'), (F(2, 3), '5', ' 2/3'), (F(-2, 3), '5', ' -2/3'), (F(2, 3), '0', '2/3'), (F(2, 3), '1', '2/3'), (F(2, 3), '2', '2/3'), # Alignment (F(2, 3), '<5', '2/3 '), (F(2, 3), '>5', ' 2/3'), (F(2, 3), '^5', ' 2/3 '), (F(2, 3), '=5', ' 2/3'), (F(-2, 3), '<5', '-2/3 '), (F(-2, 3), '>5', ' -2/3'), (F(-2, 3), '^5', '-2/3 '), (F(-2, 3), '=5', '- 2/3'), # Fill (F(2, 3), 'X>5', 'XX2/3'), (F(-2, 3), '.<5', '-2/3.'), (F(-2, 3), '\n^6', '\n-2/3\n'), # Thousands separators (F(1234, 5679), ',', '1,234/5,679'), (F(-1234, 5679), '_', '-1_234/5_679'), (F(1234567), '_', '1_234_567'), (F(-1234567), ',', '-1,234,567'), # Alternate form forces a slash in the output (F(123), '#', '123/1'), (F(-123), '#', '-123/1'), (F(0), '#', '0/1'), ] for fraction, spec, expected in testcases: with self.subTest(fraction=fraction, spec=spec): self.assertEqual(format(fraction, spec), expected) def test_format_e_presentation_type(self): # Triples (fraction, specification, expected_result) testcases = [ (F(2, 3), '.6e', '6.666667e-01'), (F(3, 2), '.6e', '1.500000e+00'), (F(2, 13), '.6e', '1.538462e-01'), (F(2, 23), '.6e', '8.695652e-02'), (F(2, 33), '.6e', '6.060606e-02'), (F(13, 2), '.6e', '6.500000e+00'), (F(20, 2), '.6e', '1.000000e+01'), (F(23, 2), '.6e', '1.150000e+01'), (F(33, 2), '.6e', '1.650000e+01'), (F(2, 3), '.6e', '6.666667e-01'), (F(3, 2), '.6e', '1.500000e+00'), # Zero (F(0), '.3e', '0.000e+00'), # Powers of 10, to exercise the log10 boundary logic (F(1, 1000), '.3e', '1.000e-03'), (F(1, 100), '.3e', '1.000e-02'), (F(1, 10), '.3e', '1.000e-01'), (F(1, 1), '.3e', '1.000e+00'), (F(10), '.3e', '1.000e+01'), (F(100), '.3e', '1.000e+02'), (F(1000), '.3e', '1.000e+03'), # Boundary where we round up to the next power of 10 (F('99.999994999999'), '.6e', '9.999999e+01'), (F('99.999995'), '.6e', '1.000000e+02'), (F('99.999995000001'), '.6e', '1.000000e+02'), # Negatives (F(-2, 3), '.6e', '-6.666667e-01'), (F(-3, 2), '.6e', '-1.500000e+00'), (F(-100), '.6e', '-1.000000e+02'), # Large and small (F('1e1000'), '.3e', '1.000e+1000'), (F('1e-1000'), '.3e', '1.000e-1000'), # Using 'E' instead of 'e' should give us a capital 'E' (F(2, 3), '.6E', '6.666667E-01'), # Tiny precision (F(2, 3), '.1e', '6.7e-01'), (F('0.995'), '.0e', '1e+00'), # Default precision is 6 (F(22, 7), 'e', '3.142857e+00'), # Alternate form forces a decimal point (F('0.995'), '#.0e', '1.e+00'), # Check that padding takes the exponent into account. (F(22, 7), '11.6e', '3.142857e+00'), (F(22, 7), '12.6e', '3.142857e+00'), (F(22, 7), '13.6e', ' 3.142857e+00'), # Thousands separators (F('1234567.123456'), ',.5e', '1.23457e+06'), (F('123.123456'), '012_.2e', '0_001.23e+02'), # z flag is legal, but never makes a difference to the output (F(-1, 7**100), 'z.6e', '-3.091690e-85'), ] for fraction, spec, expected in testcases: with self.subTest(fraction=fraction, spec=spec): self.assertEqual(format(fraction, spec), expected) def test_format_f_presentation_type(self): # Triples (fraction, specification, expected_result) testcases = [ # Simple .f formatting (F(0, 1), '.2f', '0.00'), (F(1, 3), '.2f', '0.33'), (F(2, 3), '.2f', '0.67'), (F(4, 3), '.2f', '1.33'), (F(1, 8), '.2f', '0.12'), (F(3, 8), '.2f', '0.38'), (F(1, 13), '.2f', '0.08'), (F(1, 199), '.2f', '0.01'), (F(1, 200), '.2f', '0.00'), (F(22, 7), '.5f', '3.14286'), (F('399024789'), '.2f', '399024789.00'), # Large precision (more than float can provide) (F(104348, 33215), '.50f', '3.14159265392142104470871594159265392142104470871594'), # Precision defaults to 6 if not given (F(22, 7), 'f', '3.142857'), (F(0), 'f', '0.000000'), (F(-22, 7), 'f', '-3.142857'), # Round-ties-to-even checks (F('1.225'), '.2f', '1.22'), (F('1.2250000001'), '.2f', '1.23'), (F('1.2349999999'), '.2f', '1.23'), (F('1.235'), '.2f', '1.24'), (F('1.245'), '.2f', '1.24'), (F('1.2450000001'), '.2f', '1.25'), (F('1.2549999999'), '.2f', '1.25'), (F('1.255'), '.2f', '1.26'), (F('-1.225'), '.2f', '-1.22'), (F('-1.2250000001'), '.2f', '-1.23'), (F('-1.2349999999'), '.2f', '-1.23'), (F('-1.235'), '.2f', '-1.24'), (F('-1.245'), '.2f', '-1.24'), (F('-1.2450000001'), '.2f', '-1.25'), (F('-1.2549999999'), '.2f', '-1.25'), (F('-1.255'), '.2f', '-1.26'), # Negatives and sign handling (F(2, 3), '.2f', '0.67'), (F(2, 3), '-.2f', '0.67'), (F(2, 3), '+.2f', '+0.67'), (F(2, 3), ' .2f', ' 0.67'), (F(-2, 3), '.2f', '-0.67'), (F(-2, 3), '-.2f', '-0.67'), (F(-2, 3), '+.2f', '-0.67'), (F(-2, 3), ' .2f', '-0.67'), # Formatting to zero places (F(1, 2), '.0f', '0'), (F(-1, 2), '.0f', '-0'), (F(22, 7), '.0f', '3'), (F(-22, 7), '.0f', '-3'), # Formatting to zero places, alternate form (F(1, 2), '#.0f', '0.'), (F(-1, 2), '#.0f', '-0.'), (F(22, 7), '#.0f', '3.'), (F(-22, 7), '#.0f', '-3.'), # z flag for suppressing negative zeros (F('-0.001'), 'z.2f', '0.00'), (F('-0.001'), '-z.2f', '0.00'), (F('-0.001'), '+z.2f', '+0.00'), (F('-0.001'), ' z.2f', ' 0.00'), (F('0.001'), 'z.2f', '0.00'), (F('0.001'), '-z.2f', '0.00'), (F('0.001'), '+z.2f', '+0.00'), (F('0.001'), ' z.2f', ' 0.00'), # Specifying a minimum width (F(2, 3), '6.2f', ' 0.67'), (F(12345), '6.2f', '12345.00'), (F(12345), '12f', '12345.000000'), # Fill and alignment (F(2, 3), '>6.2f', ' 0.67'), (F(2, 3), '<6.2f', '0.67 '), (F(2, 3), '^3.2f', '0.67'), (F(2, 3), '^4.2f', '0.67'), (F(2, 3), '^5.2f', '0.67 '), (F(2, 3), '^6.2f', ' 0.67 '), (F(2, 3), '^7.2f', ' 0.67 '), (F(2, 3), '^8.2f', ' 0.67 '), # '=' alignment (F(-2, 3), '=+8.2f', '- 0.67'), (F(2, 3), '=+8.2f', '+ 0.67'), # Fill character (F(-2, 3), 'X>3.2f', '-0.67'), (F(-2, 3), 'X>7.2f', 'XX-0.67'), (F(-2, 3), 'X<7.2f', '-0.67XX'), (F(-2, 3), 'X^7.2f', 'X-0.67X'), (F(-2, 3), 'X=7.2f', '-XX0.67'), (F(-2, 3), ' >7.2f', ' -0.67'), # Corner cases: weird fill characters (F(-2, 3), '\x00>7.2f', '\x00\x00-0.67'), (F(-2, 3), '\n>7.2f', '\n\n-0.67'), (F(-2, 3), '\t>7.2f', '\t\t-0.67'), (F(-2, 3), '>>7.2f', '>>-0.67'), (F(-2, 3), '<>7.2f', '<<-0.67'), (F(-2, 3), '→>7.2f', '→→-0.67'), # Zero-padding (F(-2, 3), '07.2f', '-000.67'), (F(-2, 3), '-07.2f', '-000.67'), (F(2, 3), '+07.2f', '+000.67'), (F(2, 3), ' 07.2f', ' 000.67'), # An isolated zero is a minimum width, not a zero-pad flag. # So unlike zero-padding, it's legal in combination with alignment. (F(2, 3), '0.2f', '0.67'), (F(2, 3), '>0.2f', '0.67'), (F(2, 3), '<0.2f', '0.67'), (F(2, 3), '^0.2f', '0.67'), (F(2, 3), '=0.2f', '0.67'), # Corner case: zero-padding _and_ a zero minimum width. (F(2, 3), '00.2f', '0.67'), # Thousands separator (only affects portion before the point) (F(2, 3), ',.2f', '0.67'), (F(2, 3), ',.7f', '0.6666667'), (F('123456.789'), ',.2f', '123,456.79'), (F('1234567'), ',.2f', '1,234,567.00'), (F('12345678'), ',.2f', '12,345,678.00'), (F('12345678'), ',f', '12,345,678.000000'), # Underscore as thousands separator (F(2, 3), '_.2f', '0.67'), (F(2, 3), '_.7f', '0.6666667'), (F('123456.789'), '_.2f', '123_456.79'), (F('1234567'), '_.2f', '1_234_567.00'), (F('12345678'), '_.2f', '12_345_678.00'), # Thousands and zero-padding (F('1234.5678'), '07,.2f', '1,234.57'), (F('1234.5678'), '08,.2f', '1,234.57'), (F('1234.5678'), '09,.2f', '01,234.57'), (F('1234.5678'), '010,.2f', '001,234.57'), (F('1234.5678'), '011,.2f', '0,001,234.57'), (F('1234.5678'), '012,.2f', '0,001,234.57'), (F('1234.5678'), '013,.2f', '00,001,234.57'), (F('1234.5678'), '014,.2f', '000,001,234.57'), (F('1234.5678'), '015,.2f', '0,000,001,234.57'), (F('1234.5678'), '016,.2f', '0,000,001,234.57'), (F('-1234.5678'), '07,.2f', '-1,234.57'), (F('-1234.5678'), '08,.2f', '-1,234.57'), (F('-1234.5678'), '09,.2f', '-1,234.57'), (F('-1234.5678'), '010,.2f', '-01,234.57'), (F('-1234.5678'), '011,.2f', '-001,234.57'), (F('-1234.5678'), '012,.2f', '-0,001,234.57'), (F('-1234.5678'), '013,.2f', '-0,001,234.57'), (F('-1234.5678'), '014,.2f', '-00,001,234.57'), (F('-1234.5678'), '015,.2f', '-000,001,234.57'), (F('-1234.5678'), '016,.2f', '-0,000,001,234.57'), # Corner case: no decimal point (F('-1234.5678'), '06,.0f', '-1,235'), (F('-1234.5678'), '07,.0f', '-01,235'), (F('-1234.5678'), '08,.0f', '-001,235'), (F('-1234.5678'), '09,.0f', '-0,001,235'), # Corner-case - zero-padding specified through fill and align # instead of the zero-pad character - in this case, treat '0' as a # regular fill character and don't attempt to insert commas into # the filled portion. This differs from the int and float # behaviour. (F('1234.5678'), '0=12,.2f', '00001,234.57'), # Corner case where it's not clear whether the '0' indicates zero # padding or gives the minimum width, but there's still an obvious # answer to give. We want this to work in case the minimum width # is being inserted programmatically: spec = f'{width}.2f'. (F('12.34'), '0.2f', '12.34'), (F('12.34'), 'X>0.2f', '12.34'), # 'F' should work identically to 'f' (F(22, 7), '.5F', '3.14286'), # %-specifier (F(22, 7), '.2%', '314.29%'), (F(1, 7), '.2%', '14.29%'), (F(1, 70), '.2%', '1.43%'), (F(1, 700), '.2%', '0.14%'), (F(1, 7000), '.2%', '0.01%'), (F(1, 70000), '.2%', '0.00%'), (F(1, 7), '.0%', '14%'), (F(1, 7), '#.0%', '14.%'), (F(100, 7), ',.2%', '1,428.57%'), (F(22, 7), '7.2%', '314.29%'), (F(22, 7), '8.2%', ' 314.29%'), (F(22, 7), '08.2%', '0314.29%'), # Test cases from #67790 and discuss.python.org Ideas thread. (F(1, 3), '.2f', '0.33'), (F(1, 8), '.2f', '0.12'), (F(3, 8), '.2f', '0.38'), (F(2545, 1000), '.2f', '2.54'), (F(2549, 1000), '.2f', '2.55'), (F(2635, 1000), '.2f', '2.64'), (F(1, 100), '.1f', '0.0'), (F(49, 1000), '.1f', '0.0'), (F(51, 1000), '.1f', '0.1'), (F(149, 1000), '.1f', '0.1'), (F(151, 1000), '.1f', '0.2'), ] for fraction, spec, expected in testcases: with self.subTest(fraction=fraction, spec=spec): self.assertEqual(format(fraction, spec), expected) def test_format_g_presentation_type(self): # Triples (fraction, specification, expected_result) testcases = [ (F('0.000012345678'), '.6g', '1.23457e-05'), (F('0.00012345678'), '.6g', '0.000123457'), (F('0.0012345678'), '.6g', '0.00123457'), (F('0.012345678'), '.6g', '0.0123457'), (F('0.12345678'), '.6g', '0.123457'), (F('1.2345678'), '.6g', '1.23457'), (F('12.345678'), '.6g', '12.3457'), (F('123.45678'), '.6g', '123.457'), (F('1234.5678'), '.6g', '1234.57'), (F('12345.678'), '.6g', '12345.7'), (F('123456.78'), '.6g', '123457'), (F('1234567.8'), '.6g', '1.23457e+06'), # Rounding up cases (F('9.99999e+2'), '.4g', '1000'), (F('9.99999e-8'), '.4g', '1e-07'), (F('9.99999e+8'), '.4g', '1e+09'), # Check round-ties-to-even behaviour (F('-0.115'), '.2g', '-0.12'), (F('-0.125'), '.2g', '-0.12'), (F('-0.135'), '.2g', '-0.14'), (F('-0.145'), '.2g', '-0.14'), (F('0.115'), '.2g', '0.12'), (F('0.125'), '.2g', '0.12'), (F('0.135'), '.2g', '0.14'), (F('0.145'), '.2g', '0.14'), # Trailing zeros and decimal point suppressed by default ... (F(0), '.6g', '0'), (F('123.400'), '.6g', '123.4'), (F('123.000'), '.6g', '123'), (F('120.000'), '.6g', '120'), (F('12000000'), '.6g', '1.2e+07'), # ... but not when alternate form is in effect (F(0), '#.6g', '0.00000'), (F('123.400'), '#.6g', '123.400'), (F('123.000'), '#.6g', '123.000'), (F('120.000'), '#.6g', '120.000'), (F('12000000'), '#.6g', '1.20000e+07'), # 'G' format (uses 'E' instead of 'e' for the exponent indicator) (F('123.45678'), '.6G', '123.457'), (F('1234567.8'), '.6G', '1.23457E+06'), # Default precision is 6 significant figures (F('3.1415926535'), 'g', '3.14159'), # Precision 0 is treated the same as precision 1. (F('0.000031415'), '.0g', '3e-05'), (F('0.00031415'), '.0g', '0.0003'), (F('0.31415'), '.0g', '0.3'), (F('3.1415'), '.0g', '3'), (F('3.1415'), '#.0g', '3.'), (F('31.415'), '.0g', '3e+01'), (F('31.415'), '#.0g', '3.e+01'), (F('0.000031415'), '.1g', '3e-05'), (F('0.00031415'), '.1g', '0.0003'), (F('0.31415'), '.1g', '0.3'), (F('3.1415'), '.1g', '3'), (F('3.1415'), '#.1g', '3.'), (F('31.415'), '.1g', '3e+01'), # Thousands separator (F(2**64), '_.25g', '18_446_744_073_709_551_616'), # As with 'e' format, z flag is legal, but has no effect (F(-1, 7**100), 'zg', '-3.09169e-85'), ] for fraction, spec, expected in testcases: with self.subTest(fraction=fraction, spec=spec): self.assertEqual(format(fraction, spec), expected) def test_invalid_formats(self): fraction = F(2, 3) with self.assertRaises(TypeError): format(fraction, None) invalid_specs = [ 'Q6f', # regression test # illegal to use fill or alignment when zero padding 'X>010f', 'X<010f', 'X^010f', 'X=010f', '0>010f', '0<010f', '0^010f', '0=010f', '>010f', '<010f', '^010f', '=010e', '=010f', '=010g', '=010%', '>00.2f', '>00f', # Too many zeros - minimum width should not have leading zeros '006f', # Leading zeros in precision '.010f', '.02f', '.000f', # Missing precision '.e', '.f', '.g', '.%', # Z instead of z for negative zero suppression 'Z.2f' # z flag not supported for general formatting 'z', # zero padding not supported for general formatting '05', ] for spec in invalid_specs: with self.subTest(spec=spec): with self.assertRaises(ValueError): format(fraction, spec) @requires_IEEE_754 def test_float_format_testfile(self): with open(format_testfile, encoding="utf-8") as testfile: for line in testfile: if line.startswith('--'): continue line = line.strip() if not line: continue lhs, rhs = map(str.strip, line.split('->')) fmt, arg = lhs.split() if fmt == '%r': continue fmt2 = fmt[1:] with self.subTest(fmt=fmt, arg=arg): f = F(float(arg)) self.assertEqual(format(f, fmt2), rhs) if f: # skip negative zero self.assertEqual(format(-f, fmt2), '-' + rhs) f = F(arg) self.assertEqual(float(format(f, fmt2)), float(rhs)) self.assertEqual(float(format(-f, fmt2)), float('-' + rhs)) def test_complex_handling(self): # See issue gh-102840 for more details. a = F(1, 2) b = 1j message = "unsupported operand type(s) for %s: '%s' and '%s'" # test forward self.assertRaisesMessage(TypeError, message % ("%", "Fraction", "complex"), operator.mod, a, b) self.assertRaisesMessage(TypeError, message % ("//", "Fraction", "complex"), operator.floordiv, a, b) self.assertRaisesMessage(TypeError, message % ("divmod()", "Fraction", "complex"), divmod, a, b) # test reverse self.assertRaisesMessage(TypeError, message % ("%", "complex", "Fraction"), operator.mod, b, a) self.assertRaisesMessage(TypeError, message % ("//", "complex", "Fraction"), operator.floordiv, b, a) self.assertRaisesMessage(TypeError, message % ("divmod()", "complex", "Fraction"), divmod, b, a) if __name__ == '__main__': unittest.main()