1

I want to read digit by digit the decimals of the sqrt of 5 in C. The square root of 5 is 2,23606797749979..., so this'd be the expected output:

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

I've found the following code:

#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);
}

But this approach stores the result in a float variable, and I don't want to depend on the limits of the float types, as I would like to extract like 10,000 digits, for instance. I also tried to use the native sqrt() function and casting it to string number using this method, but I faced the same issue.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
mllamazares
  • 7,876
  • 17
  • 61
  • 89
  • 3
    You need an [Arbitrary precision arithmetic](https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic) library – pmg Feb 20 '20 at 10:46
  • You have two alternatives: 1) Convert the number to a string, and print each character in the string one by one; Or 2) Use decimal arithmetic to get each digit one by one (integer truncation and multiplication with `10` is all you need). – Some programmer dude Feb 20 '20 at 10:48
  • In your question you state that you want to 'read' digit by digit `sqrt(5)` but it seems to me that you really want to write a program to 'calculate' this number and then print it digit by digit. Note that computers normally work to a fixed number of significant figures and you need a special library to be able to work to as many decimal places as you want - as noted by @pmg. -- computers are normally fixed to a set number of digits to conserve memory, or at least make it manageable so that the computer knows how much memory to assign to each number.... – tom Feb 20 '20 at 10:58
  • Your method approximates the square root until the maximum precision of float has been reached, .e. until `sqrt` does not change anymore. To have more precision, you will need another method. – Paul Ogilvie Feb 20 '20 at 11:05
  • 2
    It is impossible to compute the digits of sqrt(5) indefinitely using a finite number of bits for computing, because a finite number of bits has only a finite number of states, so the behavior must start repeating at some point and therefore must produce a repeating decimal numeral. A repeating decimal numeral is rational, but sqrt(5) is irrational. Therefore, a program to print the digits of sqrt(5) “forever” must use arbitrary precision arithmetic, using an increasing amount of memory over time (which means it too must exhaust its capability in a finite world). – Eric Postpischil Feb 20 '20 at 12:13
  • An alternate question might be what is the best use we can make of our resources—what is a good algorithm for printing as many digits of sqrt(5) as we can before we reach the boundaries of a certain number of bits? – Eric Postpischil Feb 20 '20 at 12:14

3 Answers3

4

What you've asked about is a very hard problem, and whether it's even possible to do "one by one" (i.e. without working space requirement that scales with how far out you want to go) depends on both the particular irrational number and the base you want it represented in. For example, in 1995 when a formula for pi was discovered that allows computing the nth binary digit in O(1) space, this was a really big deal. It was not something people expected to be possible.

If you're willing to accept O(n) space, then some cases like the one you mentioned are fairly easy. For example, if you have the first n digits of the square root of a number as a decimal string, you can simply try appending each digit 0 to 9, then squaring the string with long multiplication (same as you learned in grade school), and choosing the last one that doesn't overshoot. Of course this is very slow, but it's simple. The easy way to make it a lot faster (but still asymptotically just as bad) is using an arbitrary-precision math library in place of strings. Doing significantly better requires more advanced approaches and in general may not be possible.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • Maybe O(1) space in “practical complexity” (assuming things never overflow some fundamental word size), but it cannot be O(1) in theoretical complexity; you can’t calculate with an arbitrary input *n* in O(1) space. – Eric Postpischil Feb 20 '20 at 14:47
  • @EricPostpischil: In [transdichotomous model](https://en.wikipedia.org/wiki/Transdichotomous_model). – R.. GitHub STOP HELPING ICE Feb 20 '20 at 15:07
  • Hmm, I am not sure. In the transdichotomous model as explained at Wikipedia, the input values are completely independent of the number of inputs (n), except it has a stipulation that word size is at least log[2](n) which I am not sure is well stated. (Certainly it must be at least that so that a list can contain n distinct items to be sorted, and that may be the only purpose of that stipulation, but really we want enough bits to handle whatever the input values actually are.) In the Bailey-Borwein-Plouffe sums for calculating a digit of π, a sum on k, with k affected by n, is used. – Eric Postpischil Feb 20 '20 at 20:13
  • @EricPostpischil: Input size in this problem is the number of bits to represent n, where n is the desired digit position. Transdichotomous model just means that your machine's word size is sufficient to represent such n. If that's the case then you can compute the result (the desired digit) in O(1) space. – R.. GitHub STOP HELPING ICE Feb 20 '20 at 22:23
  • 2
    Just a minor tweak: *'appending each digit'* – I'd do in binary search manner, though: start with 5, if square larger, continue with 2, else 7, ... – Aconcagua Feb 21 '20 at 10:17
1

As already noted, you need to change the algorithm into a digit-by-digit one (there are some examples in the Wikipedia page about the methods of computing of the square roots) and use an arbitrary precision arithmetic library to perform the calculations (for instance, GMP).

In the following snippet I implemented the before mentioned algorithm, using GMP (but not the square root function that the library provides). Instead of calculating one decimal digit at a time, this implementation uses a larger base, the greatest multiple of 10 that fits inside an unsigned long, so that it can produce 9 or 18 decimal digits at every iteration.

It also uses an adapted Newton method to find the actual "digit".

#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);
}

You can see the outputted digits here.

Bob__
  • 12,361
  • 3
  • 28
  • 42
1

Your title says:

How to compute the digits of an irrational number one by one?

Irrational numbers are not limited to most square roots. They also include numbers of the form log(x), exp(z), sin(y), etc. (transcendental numbers). However, there are some important factors that determine whether or how fast you can compute a given irrational number's digits one by one (that is, from left to right).

  • Not all irrational numbers are computable; that is, no one has found a way to approximate them to any desired length (whether by a closed form expression, a series, or otherwise).
  • There are many ways numbers can be expressed, such as by their binary or decimal expansions, as continued fractions, as series, etc. And there are different algorithms to compute a given number's digits depending on the representation.
  • Some formulas compute a given number's digits in a particular base (such as base 2), not in an arbitrary base.

For example, besides the first formula to extract the digits of π without computing the previous digits, there are other formulas of this type (known as BBP-type formulas) that extract the digits of certain irrational numbers. However, these formulas only work for a particular base, not all BBP-type formulas have a formal proof, and most importantly, not all irrational numbers have a BBP-type formula (essentially, only certain log and arctan constants do, not numbers of the form exp(x) or sqrt(x)).

On the other hand, if you can express an irrational number as a continued fraction (which all real numbers have), you can extract its digits from left to right, and in any base desired, using a specific algorithm. What is more, this algorithm works for any real number constant, including square roots, exponentials (e and exp(x)), logarithms, etc., as long as you know how to express it as a continued fraction. For an implementation see "Digits of pi and Python generators". See also Code to Generate e one Digit at a Time.

Peter O.
  • 32,158
  • 14
  • 82
  • 96