如何一一计算非理性数字?

问题描述 投票:0回答:2

我想逐位读取C中5的sqrt的小数。5的平方根是2,23606797749979 ...,所以这将是预期的输出:

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

我找到了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);
}

但是这种方法将结果存储在float变量中,并且我不想依赖float类型的限制,例如,我想提取10,000个数字。我也尝试使用本机sqrt()函数,并使用this method将其强制转换为字符串号,但是我遇到了同样的问题。

c math casting floating-point sqrt
2个回答
1
投票

如前所述,您需要将算法更改为一位数字(Wikipedia page about the methods of computing of the square roots中有一些示例),并使用任意精度算术库来执行计算(例如,GMP) 。

在以下代码段中,我使用GMP(但未提供库提供的平方根函数)实现上述算法。此实现不是使用一次较大的基数,而是一次计算一个十进制数,而是将其乘以unsigned long内的10的最大倍数,以便每次迭代可以生成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);
}

您可以看到输出的数字here


3
投票

您所问的是一个[[非常困难问题,是否甚至可以“一个一个”地完成(即没有工作空间要求随您想走多远而缩放)的问题,取决于两者您希望它代表的特定非理性数字和基数。例如,在1995年的formula for pi was discovered that allows computing the nth binary digit in O(1) space时,这确实是一件大事。人们期望这不可能。

如果您愿意接受O(n)空间,那么您提到的某些情况就很简单。例如,如果您将数字的平方根的前n个数字作为十进制字符串,则可以简单地尝试将每个数字附加在0到9之间,然后对字符串进行长乘运算(与您在小学时所学的一样),并选择不会超调的最后一个。当然,这很慢,但是很简单。使速度更快(但仍渐近地也很糟糕)的简单方法是使用任意精度的数学库代替字符串。要做得好得多,需要采用更高级的方法,一般而言是不可能的。
© www.soinside.com 2019 - 2024. All rights reserved.