Как вычислить цифры иррационального числа одну за другой?

Я хочу прочитать цифру за цифрой десятичных знаков sqrt из 5 в C. Квадратный корень из 5 равен 2,23606797749979..., так что это будет ожидаемый результат:

2
3
6
0
6
7
9
7
7
...

Я нашел следующий код:

#include<stdio.h>

void main()
{
    int number;

    float temp, sqrt;

    printf("Provide the number: \n");

    scanf("%d", &number);

    // store the half of the given number e.g from 256 => 128
    sqrt = number / 2;
    temp = 0;

    // Iterate until sqrt is different of temp, that is updated on the loop
    while(sqrt != temp){
        // initially 0, is updated with the initial value of 128
        // (on second iteration = 65)
        // and so on
        temp = sqrt;

        // Then, replace values (256 / 128 + 128 ) / 2 = 65
        // (on second iteration 34.46923076923077)
        // and so on
        sqrt = ( number/temp + temp) / 2;
    }

    printf("The square root of '%d' is '%f'", number, sqrt);
}

Но этот подход сохраняет результат в переменной с плавающей запятой, и я не хочу зависеть от ограничений типов с плавающей запятой, так как я хотел бы извлечь, например, около 10 000 цифр. Я также пытался использовать встроенную функцию sqrt() и привести ее к номеру строки, используя этот метод, но столкнулся с тем же проблема.


person harrison4    schedule 20.02.2020    source источник
comment
Вам нужна библиотека арифметики произвольной точности.   -  person pmg    schedule 20.02.2020
comment
У вас есть две альтернативы: 1) преобразовать число в строку и вывести каждый символ в строке один за другим; Или 2) Используйте десятичную арифметику, чтобы получить каждую цифру одну за другой (усечение целых чисел и умножение на 10 - это все, что вам нужно).   -  person Some programmer dude    schedule 20.02.2020
comment
В своем вопросе вы заявляете, что хотите «читать» цифру за цифрой sqrt(5), но мне кажется, что вы действительно хотите написать программу для «вычисления» этого числа, а затем распечатать его цифра за цифрой. Обратите внимание, что компьютеры обычно работают с фиксированным числом значащих цифр, и вам нужна специальная библиотека, чтобы иметь возможность работать с любым количеством знаков после запятой, как отмечает @pmg. -- компьютеры обычно фиксируются на установленном количестве цифр, чтобы сохранить память или, по крайней мере, сделать ее управляемой, чтобы компьютер знал, сколько памяти выделить для каждого числа....   -  person tom    schedule 20.02.2020
comment
Ваш метод аппроксимирует квадратный корень до тех пор, пока не будет достигнута максимальная точность float, т.е. пока sqrt больше не изменится. Для большей точности вам понадобится другой метод.   -  person Paul Ogilvie    schedule 20.02.2020
comment
Невозможно бесконечно вычислять цифры sqrt(5), используя конечное число битов для вычислений, потому что конечное число битов имеет только конечное число состояний, поэтому поведение должно начать повторяться в какой-то момент и, следовательно, должно производить повторяющееся десятичное число. Повторяющееся десятичное число рационально, а sqrt(5) иррационально. Таким образом, программа, которая будет печатать цифры sqrt(5) «навсегда», должна использовать арифметику произвольной точности, используя все больший объем памяти с течением времени (что означает, что она также должна исчерпать свои возможности в конечном мире).   -  person Eric Postpischil    schedule 20.02.2020
comment
Альтернативный вопрос может заключаться в том, как лучше всего использовать наши ресурсы — каков хороший алгоритм для вывода как можно большего количества цифр sqrt(5), прежде чем мы достигнем границ определенного количества битов?   -  person Eric Postpischil    schedule 20.02.2020


Ответы (3)


Как уже отмечалось, алгоритм нужно изменить на поразрядный (примеры есть в страница Википедии о методах вычисления квадратных корней) и использовать арифметическую библиотеку произвольной точности для выполнения вычислений (например, GMP).

В следующем фрагменте я реализовал ранее упомянутый алгоритм, используя GMP (но не функцию квадратного корня, которую предоставляет библиотека). Вместо вычисления одной десятичной цифры за раз, эта реализация использует большее основание, наибольшее кратное 10, которое помещается внутри unsigned long, так что она может производить 9 или 18 десятичных цифр на каждой итерации.

Он также использует адаптированный метод Ньютона для нахождения фактической «цифры».

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <gmp.h>

unsigned long max_ul(unsigned long a, unsigned long b)
{
    return a < b ? b : a;   
}

int main(int argc, char *argv[])
{
    // The GMP functions accept 'unsigned long int' values as parameters.
    // The algorithm implemented here can work with bases other than 10,
    // so that it can evaluate more than one decimal digit at a time.
    const unsigned long base = sizeof(unsigned long) > 4
                             ? 1000000000000000000
                             : 1000000000;
    const unsigned long decimals_per_digit = sizeof(unsigned long) > 4 ? 18 : 9;

    // Extract the number to be square rooted and the desired number of decimal
    // digits from the command line arguments. Fallback to 0 in case of errors.
    const unsigned long number = argc > 1 ? atoi(argv[1]) : 0;
    const unsigned long n_digits = argc > 2 ? atoi(argv[2]) : 0;

    // All the variables used by GMP need to be properly initialized before use.
    // 'c' is basically the remainder, initially set to the original number
    mpz_t c;
    mpz_init_set_ui(c, number);

    // At every iteration, the algorithm "move to the left" by two "digits"
    // the reminder, so it multplies it by base^2.
    mpz_t base_squared;
    mpz_init_set_ui(base_squared, base);
    mpz_mul(base_squared, base_squared, base_squared);

    // 'p' stores the digits of the root found so far. The others are helper variables
    mpz_t p;
    mpz_init_set_ui(p, 0UL);    
    mpz_t y;
    mpz_init(y);
    mpz_t yy;
    mpz_init(yy);
    mpz_t dy;
    mpz_init(dy);
    mpz_t dx;
    mpz_init(dx);
    mpz_t pp;    
    mpz_init(pp);

    // Timing, for testing porpuses
    clock_t start = clock(), diff;

    unsigned long x_max = number;
    // Each "digit" correspond to some decimal digits
    for (unsigned long i = 0,
         last = (n_digits + decimals_per_digit) / decimals_per_digit + 1UL;
         i < last; ++i)
    {
        // Find the greatest x such that:  x * (2 * base * p + x) <= c
        // where x is in [0, base), using a specialized Newton method

        // pp = 2 * base * p
        mpz_mul_ui(pp, p, 2UL * base);

        unsigned long x = x_max;
        for (;;)
        {            
            // y = x * (pp + x)
            mpz_add_ui(yy, pp, x);
            mpz_mul_ui(y, yy, x);

            // dy = y - c
            mpz_sub(dy, y, c);

            // If y <= c we have found the correct x
            if ( mpz_sgn(dy) <= 0 )
                break;

            // Newton's step:  dx = dy/y'  where  y' = 2 * x + pp            
            mpz_add_ui(yy, yy, x);
            mpz_tdiv_q(dx, dy, yy);

            // Update x even if dx == 0 (last iteration)
            x -= max_ul(mpz_get_si(dx), 1);
        }        
        x_max = base - 1;

        // The actual format of the printed "digits" is up to you       
        if (i % 4 == 0)
        {
            if (i == 0)
                printf("%lu.", x);
            putchar('\n');
        }
        else
            printf("%018lu", x);

        // p = base * p + x
        mpz_mul_ui(p, p, base);
        mpz_add_ui(p, p, x);

        // c = (c - y) * base^2
        mpz_sub(c, c, y);
        mpz_mul(c, c, base_squared);
    }

    diff = clock() - start;
    long int msec = diff * 1000L / CLOCKS_PER_SEC;
    printf("\n\nTime taken: %ld.%03ld s\n", msec / 1000, msec % 1000);

    // Final cleanup
    mpz_clear(c);
    mpz_clear(base_squared);
    mpz_clear(p);
    mpz_clear(pp);
    mpz_clear(dx);
    mpz_clear(y);
    mpz_clear(dy);
    mpz_clear(yy);
}

Выведенные цифры можно увидеть здесь.

person Bob__    schedule 20.02.2020
comment
Это потрясающе! Я также пробовал со 100K и тоже отлично работает! Не могли бы вы добавить несколько комментариев к коду? Спасибо! - person harrison4; 21.02.2020
comment
@harrison4 Я, конечно, сделаю, но позже сегодня. - person Bob__; 21.02.2020

То, о чем вы спрашивали, является очень сложной проблемой, и даже возможно ли это сделать "один за другим" (т.е. без требования к рабочему пространству, которое масштабируется в зависимости от того, как далеко вы хотите пойти) зависит от как конкретное иррациональное число и основание, в котором оно должно быть представлено. Например, в 1995 году, когда была обнаружена формула для числа пи, позволяющая вычислять n-ю двоичную цифру в пространстве O(1), это было действительно большое дело. Люди не ожидали, что это будет возможно.

Если вы готовы принять пространство O (n), то некоторые случаи, подобные упомянутому вами, довольно просты. Например, если у вас есть первые n цифр квадратного корня числа в виде десятичной строки, вы можете просто попробовать добавить каждую цифру от 0 до 9, а затем возвести строку в квадрат с помощью длинного умножения (так же, как вы учили в начальной школе), и выбор последнего, который не выходит за пределы. Конечно это очень медленно, но зато просто. Простой способ сделать это намного быстрее (но все же асимптотически такой же плохой) — использовать математическую библиотеку произвольной точности вместо строк. Чтобы добиться значительного улучшения, требуются более продвинутые подходы, и в целом это может оказаться невозможным.

person R.. GitHub STOP HELPING ICE    schedule 20.02.2020
comment
Может быть, O(1) пространство в «практической сложности» (при условии, что вещи никогда не выходят за пределы некоторого фундаментального размера слова), но это не может быть O(1) в теоретической сложности; вы не можете вычислить с произвольным вводом n в пространстве O(1). - person Eric Postpischil; 20.02.2020
comment
@EricPostpischil: В трансдихотомической модели. - person R.. GitHub STOP HELPING ICE; 20.02.2020
comment
Хм, я не уверен. В трансдихотомической модели, как объясняется в Википедии, входные значения полностью не зависят от количества входных данных (n), за исключением того, что в ней есть условие, что размер слова составляет не менее log[2](n), что, я не уверен, правильно сформулировано. . (Конечно, это должно быть по крайней мере так, чтобы список мог содержать n различных элементов для сортировки, и это может быть единственной целью этого условия, но на самом деле нам нужно достаточно битов для обработки любых входных значений.) Суммы Бейли-Борвейна-Плуффа для вычисления цифры π, суммы по k, где k зависит от n, используются. - person Eric Postpischil; 20.02.2020
comment
@EricPostpischil: размер входных данных в этой задаче — это количество битов для представления n, где n — желаемая позиция цифры. Трансдихотомическая модель просто означает, что размер слова вашей машины достаточен для представления такого n. Если это так, вы можете вычислить результат (нужную цифру) в пространстве O (1). - person R.. GitHub STOP HELPING ICE; 21.02.2020
comment
Небольшая поправка: 'добавление каждой цифры' - я бы сделал в стиле двоичного поиска: начните с 5, если квадрат больше, продолжите с 2, иначе 7, ... - person Aconcagua; 21.02.2020

Ваш заголовок говорит:

Как вычислить цифры иррационального числа одну за другой?

Иррациональные числа не ограничиваются большинством квадратных корней. К ним также относятся числа вида log(x), exp(z), sin(y) и т. д. (трансцендентные числа). Однако есть несколько важных факторов, которые определяют, сможете ли вы вычислить цифры данного иррационального числа одну за другой (то есть слева направо) и насколько быстро.

  • Не все иррациональные числа вычислимы; то есть никто не нашел способа аппроксимировать их до любой желаемой длины (будь то выражением в замкнутой форме, рядом или иным образом).
  • Существует множество способов выражения чисел, например, их двоичное или десятичное представление, цепные дроби, ряды и т. д. Существуют различные алгоритмы для вычисления цифр заданного числа в зависимости от представления.
  • Некоторые формулы вычисляют цифры заданного числа в определенном основании (например, в основании 2), а не в произвольном основании.

Например, помимо первой формулы для извлечения цифр числа π без вычисления предыдущих цифр, существуют другие формулы этого типа (известные как формулы типа BBP), которые извлекают цифры определенных иррациональных чисел. Однако эти формулы работают только для определенного основания, не все формулы типа BBP имеют формальное доказательство, и, что наиболее важно, не все иррациональные числа имеют формулу типа BBP (по существу, только некоторые константы логарифма и арктангенса, а не числа форма exp(x) или sqrt(x)).

С другой стороны, если вы можете выразить иррациональное число в виде непрерывной дроби (которую имеют все действительные числа), вы можете извлечь его цифры слева направо и в любом желаемом основании, используя определенный алгоритм. Более того, этот алгоритм работает для любой вещественной числовой константы, включая квадратные корни, экспоненты (e и exp(x)), логарифмы и т. д., если вы знаете, как выразить их в виде непрерывной дроби. Для реализации см. Цифры числа пи и Python генераторы. См. также Код для генерации одной цифры за раз.

person Peter O.    schedule 07.05.2020