cpython: 9eb5edfcf604 (original) (raw)
--- a/Lib/statistics.py +++ b/Lib/statistics.py @@ -303,6 +303,230 @@ def _fail_neg(values, errmsg='negative v yield x +class _nroot_NS:
- """Hands off! Don't touch
- Everything inside this namespace (class) is an even-more-private
- implementation detail of the private _nth_root function.
- """
This class exists only to be used as a namespace, for convenience
of being able to keep the related functions together, and to
collapse the group in an editor. If this were C# or C++, I would
use a Namespace, but the closest Python has is a class.
- #
FIXME possibly move this out into a separate module?
That feels like overkill, and may encourage people to treat it as
a public feature.
- def init(self):
raise TypeError('namespace only, do not instantiate')[](#l1.22)
This may be more accurate than ** or pow():[](#l1.27)
>>> math.pow(1000, 1.0/3) #doctest:+SKIP[](#l1.29)
9.999999999999998[](#l1.30)
>>> _nth_root(1000, 3)[](#l1.32)
10.0[](#l1.33)
>>> _nth_root(11**5, 5)[](#l1.34)
11.0[](#l1.35)
>>> _nth_root(2, 12)[](#l1.36)
1.0594630943592953[](#l1.37)
"""[](#l1.39)
if not isinstance(n, int):[](#l1.40)
raise TypeError('degree n must be an int')[](#l1.41)
if n < 2:[](#l1.42)
raise ValueError('degree n must be 2 or more')[](#l1.43)
if isinstance(x, decimal.Decimal):[](#l1.44)
return _nroot_NS.decimal_nroot(x, n)[](#l1.45)
elif isinstance(x, numbers.Real):[](#l1.46)
return _nroot_NS.float_nroot(x, n)[](#l1.47)
else:[](#l1.48)
raise TypeError('expected a number, got %s') % type(x).__name__[](#l1.49)
- def float_nroot(x, n):
"""Handle nth root of Reals, treated as a float."""[](#l1.52)
assert isinstance(n, int) and n > 1[](#l1.53)
if x < 0:[](#l1.54)
if n%2 == 0:[](#l1.55)
raise ValueError('domain error: even root of negative number')[](#l1.56)
else:[](#l1.57)
return -_nroot_NS.nroot(-x, n)[](#l1.58)
elif x == 0:[](#l1.59)
return math.copysign(0.0, x)[](#l1.60)
elif x > 0:[](#l1.61)
try:[](#l1.62)
isinfinity = math.isinf(x)[](#l1.63)
except OverflowError:[](#l1.64)
return _nroot_NS.bignum_nroot(x, n)[](#l1.65)
else:[](#l1.66)
if isinfinity:[](#l1.67)
return float('inf')[](#l1.68)
else:[](#l1.69)
return _nroot_NS.nroot(x, n)[](#l1.70)
else:[](#l1.71)
assert math.isnan(x)[](#l1.72)
return float('nan')[](#l1.73)
- def nroot(x, n):
"""Calculate x**(1/n), then improve the answer."""[](#l1.76)
# This uses math.pow() to calculate an initial guess for the root,[](#l1.77)
# then uses the iterated nroot algorithm to improve it.[](#l1.78)
#[](#l1.79)
# By my testing, about 8% of the time the iterated algorithm ends[](#l1.80)
# up converging to a result which is less accurate than the initial[](#l1.81)
# guess. [FIXME: is this still true?] In that case, we use the[](#l1.82)
# guess instead of the "improved" value. This way, we're never[](#l1.83)
# less accurate than math.pow().[](#l1.84)
r1 = math.pow(x, 1.0/n)[](#l1.85)
eps1 = abs(r1**n - x)[](#l1.86)
if eps1 == 0.0:[](#l1.87)
# r1 is the exact root, so we're done. By my testing, this[](#l1.88)
# occurs about 80% of the time for x < 1 and 30% of the[](#l1.89)
# time for x > 1.[](#l1.90)
return r1[](#l1.91)
else:[](#l1.92)
try:[](#l1.93)
r2 = _nroot_NS.iterated_nroot(x, n, r1)[](#l1.94)
except RuntimeError:[](#l1.95)
return r1[](#l1.96)
else:[](#l1.97)
eps2 = abs(r2**n - x)[](#l1.98)
if eps1 < eps2:[](#l1.99)
return r1[](#l1.100)
return r2[](#l1.101)
This is a special case of Newton's Method.[](#l1.106)
https://en.wikipedia.org/wiki/Nth_root_algorithm[](#l1.107)
"""[](#l1.108)
np = n - 1[](#l1.109)
def iterate(r):[](#l1.110)
try:[](#l1.111)
return (np*r + a/math.pow(r, np))/n[](#l1.112)
except OverflowError:[](#l1.113)
# If r is large enough, r**np may overflow. If that[](#l1.114)
# happens, r**-np will be small, but not necessarily zero.[](#l1.115)
return (np*r + a*math.pow(r, -np))/n[](#l1.116)
# With a good guess, such as g = a**(1/n), this will converge in[](#l1.117)
# only a few iterations. However a poor guess can take thousands[](#l1.118)
# of iterations to converge, if at all. We guard against poor[](#l1.119)
# guesses by setting an upper limit to the number of iterations.[](#l1.120)
r1 = g[](#l1.121)
r2 = iterate(g)[](#l1.122)
for i in range(1000):[](#l1.123)
if r1 == r2:[](#l1.124)
break[](#l1.125)
# Use Floyd's cycle-finding algorithm to avoid being trapped[](#l1.126)
# in a cycle.[](#l1.127)
# https://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare[](#l1.128)
r1 = iterate(r1)[](#l1.129)
r2 = iterate(iterate(r2))[](#l1.130)
else:[](#l1.131)
# If the guess is particularly bad, the above may fail to[](#l1.132)
# converge in any reasonable time.[](#l1.133)
raise RuntimeError('nth-root failed to converge')[](#l1.134)
return r2[](#l1.135)
- def decimal_nroot(x, n):
"""Handle nth root of Decimals."""[](#l1.138)
assert isinstance(x, decimal.Decimal)[](#l1.139)
assert isinstance(n, int)[](#l1.140)
if x.is_snan():[](#l1.141)
# Signalling NANs always raise.[](#l1.142)
raise decimal.InvalidOperation('nth-root of snan')[](#l1.143)
if x.is_qnan():[](#l1.144)
# Quiet NANs only raise if the context is set to raise,[](#l1.145)
# otherwise return a NAN.[](#l1.146)
ctx = decimal.getcontext()[](#l1.147)
if ctx.traps[decimal.InvalidOperation]:[](#l1.148)
raise decimal.InvalidOperation('nth-root of nan')[](#l1.149)
else:[](#l1.150)
# Preserve the input NAN.[](#l1.151)
return x[](#l1.152)
if x.is_infinite():[](#l1.153)
return x[](#l1.154)
# FIXME this hasn't had the extensive testing of the float[](#l1.155)
# version _iterated_nroot so there's possibly some buggy[](#l1.156)
# corner cases buried in here. Can it overflow? Fail to[](#l1.157)
# converge or get trapped in a cycle? Converge to a less[](#l1.158)
# accurate root?[](#l1.159)
np = n - 1[](#l1.160)
def iterate(r):[](#l1.161)
return (np*r + x/r**np)/n[](#l1.162)
r0 = x**(decimal.Decimal(1)/n)[](#l1.163)
assert isinstance(r0, decimal.Decimal)[](#l1.164)
r1 = iterate(r0)[](#l1.165)
while True:[](#l1.166)
if r1 == r0:[](#l1.167)
return r1[](#l1.168)
r0, r1 = r1, iterate(r1)[](#l1.169)
- def bignum_nroot(x, n):
"""Return the nth root of a positive huge number."""[](#l1.172)
assert x > 0[](#l1.173)
# I state without proof that ⁿ√x ≈ ⁿ√2·ⁿ√(x//2)[](#l1.174)
# and that for sufficiently big x the error is acceptible.[](#l1.175)
# We now halve x until it is small enough to get the root.[](#l1.176)
m = 0[](#l1.177)
while True:[](#l1.178)
x //= 2[](#l1.179)
m += 1[](#l1.180)
try:[](#l1.181)
y = float(x)[](#l1.182)
except OverflowError:[](#l1.183)
continue[](#l1.184)
break[](#l1.185)
a = _nroot_NS.nroot(y, n)[](#l1.186)
# At this point, we want the nth-root of 2**m, or 2**(m/n).[](#l1.187)
# We can write that as 2**(q + r/n) = 2**q * ⁿ√2**r where q = m//n.[](#l1.188)
q, r = divmod(m, n)[](#l1.189)
b = 2**q * _nroot_NS.nroot(2**r, n)[](#l1.190)
return a * b[](#l1.191)
+ + +# This is the (private) function for calculating nth roots: +_nth_root = _nroot_NS.nth_root +assert type(_nth_root) is type(lambda: None) + + +def _product(values):
- """Return product of values as (exponent, mantissa)."""
- errmsg = 'mixed Decimal and float is not supported'
- prod = 1
- for x in values:
if isinstance(x, float):[](#l1.204)
break[](#l1.205)
prod *= x[](#l1.206)
- else:
return (0, prod)[](#l1.208)
- if isinstance(prod, Decimal):
raise TypeError(errmsg)[](#l1.210)
Since floats can overflow easily, we calculate the product as a
sort of poor-man's BigFloat. Given that:
- #
x = 2**p * m # p == power or exponent (scale), m = mantissa
- #
we can calculate the product of two (or more) x values as:
- #
x1x2 = 2**p1m1 * 2p2*m2 = 2(p1+p2)(m1m2)
- #
- mant, scale = 1, 0 #math.frexp(prod) # FIXME
- for y in chain([x], values):
if isinstance(y, Decimal):[](#l1.222)
raise TypeError(errmsg)[](#l1.223)
m1, e1 = math.frexp(y)[](#l1.224)
m2, e2 = math.frexp(mant)[](#l1.225)
scale += (e1 + e2)[](#l1.226)
mant = m1*m2[](#l1.227)
- return (scale, mant)
=== Measures of central tendency (averages) ===
def mean(data): @@ -331,6 +555,49 @@ def mean(data): return _convert(total/n, T) +def geometric_mean(data):
- The geometric mean is appropriate when averaging quantities which
- are multiplied together rather than added, for example growth rates.
- Suppose an investment grows by 10% in the first year, falls by 5% in
- the second, then grows by 12% in the third, what is the average rate
- of growth over the three years?
- giving an average growth of 5.385%. Using the arithmetic mean will
- give approximately 5.667%, which is too high.
StatisticsError
will be raised ifdata
is empty, or any- element is less than zero.
- """
- if iter(data) is data:
data = list(data)[](#l1.257)
- errmsg = 'geometric mean does not support negative values'
- n = len(data)
- if n < 1:
raise StatisticsError('geometric_mean requires at least one data point')[](#l1.261)
- elif n == 1:
x = data[0][](#l1.263)
if isinstance(g, (numbers.Real, Decimal)):[](#l1.264)
if x < 0:[](#l1.265)
raise StatisticsError(errmsg)[](#l1.266)
return x[](#l1.267)
else:[](#l1.268)
raise TypeError('unsupported type')[](#l1.269)
- else:
scale, prod = _product(_fail_neg(data, errmsg))[](#l1.271)
r = _nth_root(prod, n)[](#l1.272)
if scale:[](#l1.273)
p, q = divmod(scale, n)[](#l1.274)
s = 2**p * _nth_root(2**q, n)[](#l1.275)
else:[](#l1.276)
s = 1[](#l1.277)
return s*r[](#l1.278)
+ + def harmonic_mean(data): """Return the harmonic mean of data.
--- a/Lib/test/test_statistics.py +++ b/Lib/test/test_statistics.py @@ -1010,6 +1010,291 @@ class FailNegTest(unittest.TestCase): self.assertEqual(errmsg, msg) +class Test_Product(NumericTestCase):
- def test_ints(self):
data = [1, 2, 5, 7, 9][](#l2.11)
self.assertEqual(statistics._product(data), (0, 630))[](#l2.12)
self.assertEqual(statistics._product(data*100), (0, 630**100))[](#l2.13)
- def test_floats(self):
data = [1.0, 2.0, 4.0, 8.0][](#l2.16)
self.assertEqual(statistics._product(data), (8, 0.25))[](#l2.17)
- def test_overflow(self):
# Test with floats that overflow.[](#l2.20)
data = [1e300]*5[](#l2.21)
self.assertEqual(statistics._product(data), (5980, 0.6928287951283193))[](#l2.22)
- def test_fractions(self):
F = Fraction[](#l2.25)
data = [F(14, 23), F(69, 1), F(665, 529), F(299, 105), F(1683, 39)][](#l2.26)
exp, mant = statistics._product(data)[](#l2.27)
self.assertEqual(exp, 0)[](#l2.28)
self.assertEqual(mant, F(2*3*7*11*17*19, 23))[](#l2.29)
self.assertTrue(isinstance(mant, F))[](#l2.30)
# Mixed Fraction and int.[](#l2.31)
data = [3, 25, F(2, 15)][](#l2.32)
exp, mant = statistics._product(data)[](#l2.33)
self.assertEqual(exp, 0)[](#l2.34)
self.assertEqual(mant, F(10))[](#l2.35)
self.assertTrue(isinstance(mant, F))[](#l2.36)
- @unittest.expectedFailure
- def test_decimal(self):
D = Decimal[](#l2.40)
data = [D('24.5'), D('17.6'), D('0.025'), D('1.3')][](#l2.41)
assert False[](#l2.42)
- def test_mixed_decimal_float(self):
# Test that mixed Decimal and float raises.[](#l2.45)
self.assertRaises(TypeError, statistics._product, [1.0, Decimal(1)])[](#l2.46)
self.assertRaises(TypeError, statistics._product, [Decimal(1), 1.0])[](#l2.47)
+ + +class Test_Nth_Root(NumericTestCase):
- def test_float_NAN(self):
# Test that the root of a float NAN is a float NAN.[](#l2.59)
NAN = float('nan')[](#l2.60)
for n in range(2, 9):[](#l2.61)
with self.subTest(n=n):[](#l2.62)
result = self.nroot(NAN, n)[](#l2.63)
self.assertTrue(math.isnan(result))[](#l2.64)
- def test_decimal_QNAN(self):
# Test the behaviour when taking the root of a Decimal quiet NAN.[](#l2.67)
NAN = decimal.Decimal('nan')[](#l2.68)
with decimal.localcontext() as ctx:[](#l2.69)
ctx.traps[decimal.InvalidOperation] = 1[](#l2.70)
self.assertRaises(decimal.InvalidOperation, self.nroot, NAN, 5)[](#l2.71)
ctx.traps[decimal.InvalidOperation] = 0[](#l2.72)
self.assertTrue(self.nroot(NAN, 5).is_qnan())[](#l2.73)
- def test_decimal_SNAN(self):
# Test that taking the root of a Decimal sNAN always raises.[](#l2.76)
sNAN = decimal.Decimal('snan')[](#l2.77)
with decimal.localcontext() as ctx:[](#l2.78)
ctx.traps[decimal.InvalidOperation] = 1[](#l2.79)
self.assertRaises(decimal.InvalidOperation, self.nroot, sNAN, 5)[](#l2.80)
ctx.traps[decimal.InvalidOperation] = 0[](#l2.81)
self.assertRaises(decimal.InvalidOperation, self.nroot, sNAN, 5)[](#l2.82)
- def test_inf(self):
# Test that the root of infinity is infinity.[](#l2.85)
for INF in (float('inf'), decimal.Decimal('inf')):[](#l2.86)
for n in range(2, 9):[](#l2.87)
with self.subTest(n=n, inf=INF):[](#l2.88)
self.assertEqual(self.nroot(INF, n), INF)[](#l2.89)
- def testNInf(self):
# Test that the root of -inf is -inf for odd n.[](#l2.92)
for NINF in (float('-inf'), decimal.Decimal('-inf')):[](#l2.93)
for n in range(3, 11, 2):[](#l2.94)
with self.subTest(n=n, inf=NINF):[](#l2.95)
self.assertEqual(self.nroot(NINF, n), NINF)[](#l2.96)
FIXME: need to check Decimal zeroes too.
- def test_zero(self):
# Test that the root of +0.0 is +0.0.[](#l2.100)
for n in range(2, 11):[](#l2.101)
with self.subTest(n=n):[](#l2.102)
result = self.nroot(+0.0, n)[](#l2.103)
self.assertEqual(result, 0.0)[](#l2.104)
self.assertEqual(sign(result), +1)[](#l2.105)
FIXME: need to check Decimal zeroes too.
- def test_neg_zero(self):
# Test that the root of -0.0 is -0.0.[](#l2.109)
for n in range(2, 11):[](#l2.110)
with self.subTest(n=n):[](#l2.111)
result = self.nroot(-0.0, n)[](#l2.112)
self.assertEqual(result, 0.0)[](#l2.113)
self.assertEqual(sign(result), -1)[](#l2.114)
- def check_result_type(self, x, n, outtype):
self.assertIsInstance(self.nroot(x, n), outtype)[](#l2.119)
class MySubclass(type(x)):[](#l2.120)
pass[](#l2.121)
self.assertIsInstance(self.nroot(MySubclass(x), n), outtype)[](#l2.122)
- def testDecimal(self):
# Test that Decimal arguments return Decimal results.[](#l2.125)
self.check_result_type(decimal.Decimal('33.3'), 3, decimal.Decimal)[](#l2.126)
- def testFloat(self):
# Test that other arguments return float results.[](#l2.129)
for x in (0.2, Fraction(11, 7), 91):[](#l2.130)
self.check_result_type(x, 6, float)[](#l2.131)
- def testBadOrderTypes(self):
# Test that nroot raises correctly when n has the wrong type.[](#l2.136)
for n in (5.0, 2j, None, 'x', b'x', [], {}, set(), sign):[](#l2.137)
with self.subTest(n=n):[](#l2.138)
self.assertRaises(TypeError, self.nroot, 2.5, n)[](#l2.139)
- def testBadOrderValues(self):
# Test that nroot raises correctly when n has a wrong value.[](#l2.142)
for n in (1, 0, -1, -2, -87):[](#l2.143)
with self.subTest(n=n):[](#l2.144)
self.assertRaises(ValueError, self.nroot, 2.5, n)[](#l2.145)
- def testBadTypes(self):
# Test that nroot raises correctly when x has the wrong type.[](#l2.148)
for x in (None, 'x', b'x', [], {}, set(), sign):[](#l2.149)
with self.subTest(x=x):[](#l2.150)
self.assertRaises(TypeError, self.nroot, x, 3)[](#l2.151)
- def testNegativeEvenPower(self):
# Test negative x with even n raises correctly.[](#l2.154)
x = random.uniform(-20.0, -0.1)[](#l2.155)
assert x < 0[](#l2.156)
for n in range(2, 9, 2):[](#l2.157)
with self.subTest(x=x, n=n):[](#l2.158)
self.assertRaises(ValueError, self.nroot, x, n)[](#l2.159)
- def check_error_is_no_worse(self, x, n):
y = math.pow(x, n)[](#l2.164)
with self.subTest(x=x, n=n, y=y):[](#l2.165)
err1 = abs(self.nroot(y, n) - x)[](#l2.166)
err2 = abs(math.pow(y, 1.0/n) - x)[](#l2.167)
self.assertLessEqual(err1, err2)[](#l2.168)
- def testCompareWithPowSmall(self):
# Compare nroot with pow for small values of x.[](#l2.171)
for i in range(200):[](#l2.172)
x = random.uniform(1e-9, 1.0-1e-9)[](#l2.173)
n = random.choice(range(2, 16))[](#l2.174)
self.check_error_is_no_worse(x, n)[](#l2.175)
- def testCompareWithPowMedium(self):
# Compare nroot with pow for medium-sized values of x.[](#l2.178)
for i in range(200):[](#l2.179)
x = random.uniform(1.0, 100.0)[](#l2.180)
n = random.choice(range(2, 16))[](#l2.181)
self.check_error_is_no_worse(x, n)[](#l2.182)
- def testCompareWithPowLarge(self):
# Compare nroot with pow for largish values of x.[](#l2.185)
for i in range(200):[](#l2.186)
x = random.uniform(100.0, 10000.0)[](#l2.187)
n = random.choice(range(2, 16))[](#l2.188)
self.check_error_is_no_worse(x, n)[](#l2.189)
- def testCompareWithPowHuge(self):
# Compare nroot with pow for huge values of x.[](#l2.192)
for i in range(200):[](#l2.193)
x = random.uniform(1e20, 1e50)[](#l2.194)
# We restrict the order here to avoid an Overflow error.[](#l2.195)
n = random.choice(range(2, 7))[](#l2.196)
self.check_error_is_no_worse(x, n)[](#l2.197)
- def testExactPowers(self):
# Test that small integer powers are calculated exactly.[](#l2.202)
for i in range(1, 51):[](#l2.203)
for n in range(2, 16):[](#l2.204)
if (i, n) == (35, 13):[](#l2.205)
# See testExpectedFailure35p13[](#l2.206)
continue[](#l2.207)
with self.subTest(i=i, n=n):[](#l2.208)
x = i**n[](#l2.209)
self.assertEqual(self.nroot(x, n), i)[](#l2.210)
- def testExactPowersNegatives(self):
# Test that small negative integer powers are calculated exactly.[](#l2.213)
for i in range(-1, -51, -1):[](#l2.214)
for n in range(3, 16, 2):[](#l2.215)
if (i, n) == (-35, 13):[](#l2.216)
# See testExpectedFailure35p13[](#l2.217)
continue[](#l2.218)
with self.subTest(i=i, n=n):[](#l2.219)
x = i**n[](#l2.220)
assert sign(x) == -1[](#l2.221)
self.assertEqual(self.nroot(x, n), i)[](#l2.222)
- def testExpectedFailure35p13(self):
# Test the expected failure 35**13 is almost exact.[](#l2.225)
x = 35**13[](#l2.226)
err = abs(self.nroot(x, 13) - 35)[](#l2.227)
self.assertLessEqual(err, 0.000000001)[](#l2.228)
err = abs(self.nroot(-x, 13) + 35)[](#l2.229)
self.assertLessEqual(err, 0.000000001)[](#l2.230)
- def testOne(self):
# Test that the root of 1.0 is 1.0.[](#l2.233)
for n in range(2, 11):[](#l2.234)
with self.subTest(n=n):[](#l2.235)
self.assertEqual(self.nroot(1.0, n), 1.0)[](#l2.236)
- def testFraction(self):
# Test Fraction results.[](#l2.239)
x = Fraction(89, 75)[](#l2.240)
self.assertEqual(self.nroot(x**12, 12), float(x))[](#l2.241)
- def testInt(self):
# Test int results.[](#l2.244)
x = 276[](#l2.245)
self.assertEqual(self.nroot(x**24, 24), x)[](#l2.246)
- def testBigInt(self):
# Test that ints too big to convert to floats work.[](#l2.249)
bignum = 10**20 # That's not that big...[](#l2.250)
self.assertEqual(self.nroot(bignum**280, 280), bignum)[](#l2.251)
# Can we make it bigger?[](#l2.252)
hugenum = bignum**50[](#l2.253)
# Make sure that it is too big to convert to a float.[](#l2.254)
try:[](#l2.255)
y = float(hugenum)[](#l2.256)
except OverflowError:[](#l2.257)
pass[](#l2.258)
else:[](#l2.259)
raise AssertionError('hugenum is not big enough')[](#l2.260)
self.assertEqual(self.nroot(hugenum, 50), float(bignum))[](#l2.261)
- def testDecimal(self):
# Test Decimal results.[](#l2.264)
for s in '3.759 64.027 5234.338'.split():[](#l2.265)
x = decimal.Decimal(s)[](#l2.266)
with self.subTest(x=x):[](#l2.267)
a = self.nroot(x**5, 5)[](#l2.268)
self.assertEqual(a, x)[](#l2.269)
a = self.nroot(x**17, 17)[](#l2.270)
self.assertEqual(a, x)[](#l2.271)
- def testFloat(self):
# Test float results.[](#l2.274)
for x in (3.04e-16, 18.25, 461.3, 1.9e17):[](#l2.275)
with self.subTest(x=x):[](#l2.276)
self.assertEqual(self.nroot(x**3, 3), x)[](#l2.277)
self.assertEqual(self.nroot(x**8, 8), x)[](#l2.278)
self.assertEqual(self.nroot(x**11, 11), x)[](#l2.279)
+ + +class Test_NthRoot_NS(unittest.TestCase):
- def test_class_cannot_be_instantiated(self):
# Test that _nroot_NS cannot be instantiated.[](#l2.286)
# It should be a namespace, like in C++ or C#, but Python[](#l2.287)
# lacks that feature and so we have to make do with a class.[](#l2.288)
self.assertRaises(TypeError, statistics._nroot_NS)[](#l2.289)