/*==============================================================================
  Лабораторна робота №3. Варіант 19.
  Тема: циклічні обчислювальні процеси.

  Умова (таблиця 3.1, варіант 19): обчислити суму ряду

            5     inf     (-1)^k * x^k
           SUM   SUM   ---------------------
           x=1   k=0    (k + 2)^3 * sqrt(k)

  Вимоги методичних вказівок до реалізації:
    - зовнішню суму (параметр 1..5) обчислювати циклом for;
    - внутрішню суму (параметр від 0 до нескінченності) — циклом while
      або do...while;
    - точність обчислення поточного елемента ряду задає користувач;
    - підсумовування припиняється при досягненні заданої точності;
    - степінь обчислювати за рекурентним співвідношенням (без pow());
    - для запобігання переповненню виходити з внутрішнього циклу при
      досягненні поточним елементом ряду значень 1e-38 або 1e+38;
    - результат подати таблицею з чотирьох колонок.

  ЗАУВАЖЕННЯ 1 (нижня межа внутрішньої суми).
      В умові варіанта внутрішнє підсумовування починається з k = 0, проте
      при k = 0 знаменник містить множник sqrt(0) = 0, тобто елемент ряду
      не визначений (ділення на нуль). Оскільки доозначити його довільним
      значенням не можна, підсумовування починається з k = 1. Про це
      програма повідомляє під час запуску.

  ЗАУВАЖЕННЯ 2 (збіжність).
      Модуль елемента ряду поводиться як x^k / k^3.5, тому ряд збігається
      лише при |x| <= 1. Для x = 2, 3, 4, 5 ряд розбігається, і елементи
      необмежено зростають. Для цього випадку і потрібен контроль
      переповнення, передбачений методичними вказівками: внутрішній цикл
      припиняється за порогом 1e+38, а програма повідомляє причину зупинки.

  Виконав: Одарчук Олексій, КНУ імені Тараса Шевченка, ФІТ, група ІПЗ-11.

  Компілятор: gcc -std=c17
==============================================================================*/

#include <stdio.h>
#include <stdbool.h>
#include <math.h>

/* Межі контролю переповнення комірки пам'яті, задані умовою варіанта. */
const double OVERFLOW_LIMIT = 1e+38;
const double UNDERFLOW_LIMIT = 1e-38;

/* Верхня межа кількості доданків — страховка від зациклення у випадку,
   коли елемент ряду прямує до сталої, не досягаючи жодного з порогів. */
const int MAX_TERMS = 100000;

/* Причина припинення внутрішнього циклу. У мові C ім'я переліку саме по собі
   не є іменем типу, тому оголошення загорнуто в typedef. */
typedef enum {
    STOP_NONE,      /* підсумовування триває                     */
    STOP_PRECISION, /* досягнуто заданої точності — ряд збігся   */
    STOP_OVERFLOW,  /* елемент перевищив 1e+38 — ряд розбігається */
    STOP_UNDERFLOW, /* елемент став меншим за 1e-38 — машинний нуль */
    STOP_LIMIT      /* вичерпано ліміт кількості доданків          */
} StopReason;

/*------------------------------------------------------------------------------
  utf8Width — ширина рядка в символах, а не в байтах.

  У кодуванні UTF-8 кирилична літера займає два байти, тому специфікатор
  формату виду %-16s, який рахує байти, вирівнює заголовки таблиць
  неправильно. Функція рахує лише початкові байти символів: у продовжувальних
  байтах старші біти дорівнюють 10 у двійковій системі.

  Параметри: s [вхідний] — рядок у кодуванні UTF-8.
  Повертає : кількість символів рядка.
------------------------------------------------------------------------------*/
int utf8Width(const char *s)
{
    int width = 0;

    for (const unsigned char *p = (const unsigned char *)s; *p != '\0'; ++p)
        /* Продовжувальний байт UTF-8 має вигляд 10xxxxxx: маска 0xC0 лишає
           два старші біти, і якщо вони не дорівнюють 10, це початок символу. */
        if ((*p & 0xC0) != 0x80)
            ++width;

    return width;
}

/*------------------------------------------------------------------------------
  printPadded — вивести рядок, доповнивши його пропусками до заданої ширини.

  Параметри:
      s     [вхідний] — рядок, що виводиться;
      width [вхідний] — потрібна ширина поля в символах;
      left  [вхідний] — true: вирівнювання ліворуч; false: праворуч.

  Локальні змінні:
      pad — кількість пропусків, які треба додати.
------------------------------------------------------------------------------*/
void printPadded(const char *s, int width, bool left)
{
    const int pad = width - utf8Width(s);

    if (!left)
        for (int i = 0; i < pad; ++i)
            putchar(' ');

    printf("%s", s);

    if (left)
        for (int i = 0; i < pad; ++i)
            putchar(' ');
}

/*------------------------------------------------------------------------------
  stopReasonText — текстовий опис причини припинення підсумовування.

  Параметри: reason [вхідний] — код причини.
  Повертає : рядок-константу з поясненням.
------------------------------------------------------------------------------*/
const char *stopReasonText(StopReason reason)
{
    switch (reason) {
    case STOP_NONE:
        return "підсумовування не завершено";
    case STOP_PRECISION:
        return "досягнуто заданої точності (ряд збігся)";
    case STOP_OVERFLOW:
        return "елемент ряду перевищив 1e+38 (ряд розбігається)";
    case STOP_UNDERFLOW:
        return "елемент ряду став меншим за 1e-38";
    case STOP_LIMIT:
        return "вичерпано ліміт кількості доданків";
    }
    return "невідома причина";
}

/*------------------------------------------------------------------------------
  printTableHeader — вивести заголовок таблиці з чотирьох колонок: параметри
                     зовнішньої та внутрішньої сум, член ряду, накопичена сума.
------------------------------------------------------------------------------*/
void printTableHeader(void)
{
    printf("  ");
    printPadded("x", 6, false);
    printf(" | ");
    printPadded("k", 6, false);
    printf(" | ");
    printPadded("елемент ряду a(k)", 22, false);
    printf(" | ");
    printPadded("накопичена сума", 22, false);
    printf("\n");
    printf("  -------+--------+------------------------+------------------------\n");
}

/*------------------------------------------------------------------------------
  innerSum — обчислити внутрішню суму ряду для заданого параметра x.

  Використане рекурентне співвідношення. Елемент ряду
        a(k) = (-1)^k * x^k / ((k+2)^3 * sqrt(k))
  виражається через попередній, тож степінь x^k і знак (-1)^k не
  обчислюються наново:

        a(k+1)      (k+2)^3 * sqrt(k)
        ------ = -x * -------------------
         a(k)        (k+3)^3 * sqrt(k+1)

  Перший доданок a(1) = -x / (3^3 * sqrt(1)) = -x/27 обчислюється явно,
  оскільки рекурентний перехід від k = 0 неможливий (a(0) не визначено).

  Параметри:
      x       [вхідний]  — параметр зовнішньої суми (1..5);
      eps     [вхідний]  — задана користувачем точність;
      terms   [вихідний] — адреса лічильника обчислених доданків;
      reason  [вихідний] — адреса коду причини припинення циклу.

  Повертає: значення внутрішньої суми.

  Локальні змінні:
      sum      — накопичувана сума ряду;
      term     — значення поточного елемента ряду a(k);
      k        — номер поточного елемента ряду;
      ratio    — множник переходу до наступного елемента;
      kd       — номер елемента у дійсному вигляді (для формул).
------------------------------------------------------------------------------*/
double innerSum(int x, double eps, int *terms, StopReason *reason)
{
    double sum = 0.0;
    int k = 1;
    double term = -(double)x / 27.0; /* a(1) = -x / (1+2)^3 / sqrt(1) */

    *terms = 0;
    *reason = STOP_NONE;

    printTableHeader();

    /* Цикл з постумовою: перший доданок додається обов'язково, а кількість
       доданків наперед невідома. Цикл триває, доки не визначено причину
       зупинки. */
    do {
        sum += term;
        ++(*terms);

        printf("  %6d | %6d | %22.12e | %22.12e\n", x, k, term, sum);

        /* Умови припинення перевіряються до обчислення наступного елемента,
           щоб не втратити причину зупинки. */
        if (fabs(term) < eps) {
            *reason = STOP_PRECISION;
        } else if (fabs(term) > OVERFLOW_LIMIT) {
            *reason = STOP_OVERFLOW;
        } else if (fabs(term) < UNDERFLOW_LIMIT) {
            *reason = STOP_UNDERFLOW;
        } else if (*terms >= MAX_TERMS) {
            *reason = STOP_LIMIT;
        } else {
            /* Рекурентний перехід a(k) -> a(k+1). */
            const double kd = (double)k;
            const double numerator = (kd + 2.0) * (kd + 2.0) * (kd + 2.0) * sqrt(kd);
            const double denominator =
                (kd + 3.0) * (kd + 3.0) * (kd + 3.0) * sqrt(kd + 1.0);
            const double ratio = -(double)x * numerator / denominator;

            term *= ratio;
            ++k;
        }
    } while (*reason == STOP_NONE);

    return sum;
}

/*------------------------------------------------------------------------------
  readPositive — прочитати додатне дійсне число з контролем введення.

  Параметри:
      prompt [вхідний]  — текст запрошення;
      value  [вихідний] — адреса змінної для введеного числа.
  Повертає : true — число прочитано; false — вхідні дані вичерпано.

  Локальні змінні:
      c — черговий символ, що зчитується під час очищення буфера.
------------------------------------------------------------------------------*/
bool readPositive(const char *prompt, double *value)
{
    printf("%s", prompt);

    while (scanf("%lf", value) != 1 || *value <= 0.0) {
        if (feof(stdin))
            return false;

        int c;
        /* очищення буфера клавіатури */
        while ((c = getchar()) != '\n' && c != EOF) {
        }
        printf("Помилка: потрібне додатне дійсне число. Повторіть: ");
    }
    return true;
}

/*------------------------------------------------------------------------------
  Головна функція. Читає точність обчислень, обчислює зовнішню суму
  циклом for та виводить підсумкову таблицю.

  Локальні змінні:
      eps       — точність обчислення елемента ряду;
      total     — значення зовнішньої (загальної) суми;
      inner     — значення внутрішньої суми для поточного x;
      terms     — кількість доданків внутрішньої суми;
      reason    — причина припинення внутрішнього циклу;
      divergent — ознака того, що хоча б одна внутрішня сума розбіглася.
------------------------------------------------------------------------------*/
int main()
{
    printf("Лабораторна робота №3 (варіант 19)\n");
    printf("Виконав: студент групи ІПЗ-11 Одарчук Олексій\n");
    printf("Обчислення суми ряду  SUM(x=1..5) SUM(k=1..inf) (-1)^k*x^k / "
           "((k+2)^3*sqrt(k))\n\n");
    printf("Увага: при k = 0 елемент ряду не визначено (sqrt(0) = 0 у знаменнику),\n"
           "       тому внутрішнє підсумовування починається з k = 1.\n\n");

    double eps = 0.0;
    if (!readPositive("Уведіть точність обчислення елемента ряду (наприклад 1e-6): ",
                      &eps)) {
        printf("\nВхідні дані вичерпано.\n");
        return 1;
    }

    printf("\nЗадана точність: %.3e\n", eps);

    double total = 0.0;
    bool divergent = false;

    /* Зовнішня сума — цикл з лічильником (for), параметр x = 1..5. */
    for (int x = 1; x <= 5; ++x) {
        printf("\n=== Зовнішній параметр x = %d ===\n", x);

        int terms = 0;
        StopReason reason = STOP_NONE;
        const double inner = innerSum(x, eps, &terms, &reason);

        printf("  Доданків обчислено: %d; зупинка: %s\n", terms,
               stopReasonText(reason));
        printf("  Внутрішня сума при x = %d: %.12e\n", x, inner);

        if (reason == STOP_OVERFLOW)
            divergent = true;

        total += inner;
    }

    printf("\n============================================================\n");
    printf("Загальна сума ряду: %.12e\n", total);

    if (divergent) {
        printf("\nУВАГА: для деяких значень x внутрішній ряд розбігається\n"
               "(|a(k)| ~ x^k / k^3.5 необмежено зростає при x > 1),\n"
               "тому підсумовування припинено за порогом переповнення 1e+38.\n"
               "Загальна сума в цьому разі не має скінченного значення.\n");
    }

    return 0;
}
