# Число pi [1] с произвольной точностью можно вычислить через сумму
# бесконечного знакопеременного ряда: 
# 
#   pi = 4 * (1 - 1/3 + 1/5 - 1/7 + ...)

def calc_pi_series(n):
    """Вычисляет сумму ряда pi из n членов"""
    pi = 0   # начальное приближение pi
    sign = 1 # знак следующего члена ряда
    for i in range(1, 2 * n + 1, 2): # i проходит от 1 до 2*n+1 (не включая 
                                     # последний) с шагом 2
        d = 4 * (1.0 / i)
        pi += sign * d
        sign *= -1
    return pi

print("calc_pi_series(10) =", calc_pi_series(10)) # 3.04183961893
print("calc_pi_series(20) =", calc_pi_series(20)) # 3.09162380667
print("calc_pi_series(50) =", calc_pi_series(50)) # 3.12159465259

# Слишком мало точных знаков! Напишем функцию которая явно вычисляет pi с
# точностью до определённого знака.

# Нам понадобится функция count [2] из стандартной библиотеки из модуля
# itertools.  Импортируем её в наш модуль:
from itertools import count 
# count(st, step) генерирует числа [st, st + step, st + 2 * step, ...]

def calc_pi(digits):
    """Вычисляет pi с точностью до n-го знака.

    Погрешность +/- половина знака с номером digits.

    Возвращает кортеж (пи, число выполненных шагов).
    """
    pi = 0 # начальное приближение pi
    sign = 1
    for i in count(1, 2): # числа начиная с 1 и с шагом 2
        d = 4 * (1.0 / i)
        pi += sign * d
        sign *= -1

        if d < 0.5 * 10**-digits:
            # Колебания знакопеременного ряда меньше половины величины 
            # последней требующейся значащей цифры pi.
            break;
    # Возвращаем кортеж (tuple).
    return (pi, (i - 1) / 2)

for digit in range(6):
    print("calc_pi(" + str(digit) + ") =", calc_pi(digit))

# Задания:
# 
# 1. Напишите функцию calc_e_series(n), которая будет вычислять сумму первых n 
# членов ряда разложения числа e [3]:
#   e = 1/0! + 1/1! + 1/2! + 1/3! + 1/4! + ...
# где символом "!" обозначен факториал.
#
# 2. Напишите функцию calc_e(digits), которая будет вычислять e с точностью
# до определённого знака. Остаток ряда оценить значением последнего 
# вычисленного приращения, т.е. ряд нужно считать до первого члена, который
# окажется меньше 0.5 * 10**-digits.
#
# 3. Дана вещественная функция:
def f(x):
    return -x**5 + 30 * x**3 - 1
# f(x) принимает следующие значения:
#   f(-1) == -30
#   f(+1) == 28
# Из того, что функция на концах отрезка [-1, 1] принимает значения разного 
# знака следует, что в отрезке [-1, 1] f(x) содержит корень x0: f(x0) == 0.
#
# Необходимо написать функцию find_root(f, x_from, x_to, precision), которая 
# находит корень f(x) в отрезке [x_from, x_to] методом половинного деления [4] 
# с точностью precision.
#
# find_root должна сначала проверить, что на границах отрезка [x_from, x_to]
# f(x) принимает значения разного знака. Далее необходимо вычислить значение в
# середине отрезка x' = (x_from + x_to) / 2.0. Если f(x') == 0, то корень
# найден. Если f(x') того же знака, что и f(x_from), то корень находится в
# отрезке [x', x_to], иначе корень в отрезке [x_from, x'] --- длина отрезка,
# содержащего корень, сократилась в два раза. Будем продолжать искать корень на
# сокращённом отрезке до тех пор, пока длина отрезка не станет меньше
# precision, тогда в качестве корня можно вернуть значение середины последнего
# отрезка.
#
# Выведите найденное значение корня и значение функции в нём. Попробуйте искать
# корни различных несложных функций.

# Ссылки:
# [1] Число Пи: <https://ru.wikipedia.org/wiki/Пи_(число)>
# [2] itertools.count(): <http://docs.python.org/3/library/itertools.html#itertools.count>
# [3] Число e: <https://ru.wikipedia.org/wiki/E_(число)>
# [4] Метод половинного деления: <https://ru.wikipedia.org/wiki/Метод_половинного_деления>