จะคำนวณตัวเลขของจำนวนอตรรกยะทีละตัวได้อย่างไร?

ฉันต้องการอ่านทศนิยมของ 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);
}

แต่วิธีนี้จะเก็บผลลัพธ์ไว้ในตัวแปร float และฉันไม่ต้องการขึ้นอยู่กับขีดจำกัดของประเภท float เนื่องจากฉันต้องการแยกตัวเลขประมาณ 10,000 หลัก เป็นต้น ฉันยังลองใช้ฟังก์ชัน Native sqrt() แล้วส่งไปที่หมายเลขสตริงโดยใช้ วิธีนี้ แต่ฉันก็เผชิญสิ่งเดียวกัน ปัญหา.


person harrison4    schedule 20.02.2020    source แหล่งที่มา
comment
คุณต้องมีไลบรารี Arbitrary precision arithmetic   -  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
วิธีการของคุณประมาณรากที่สองจนกว่าจะถึงความแม่นยำสูงสุดของการลอยตัว เช่น จนกว่า sqrt จะไม่เปลี่ยนแปลงอีกต่อไป หากต้องการความแม่นยำมากขึ้น คุณจะต้องใช้วิธีอื่น   -  person Paul Ogilvie    schedule 20.02.2020
comment
เป็นไปไม่ได้ที่จะคำนวณตัวเลขของ sqrt(5) อย่างไม่มีกำหนดโดยใช้จำนวนบิตที่จำกัดสำหรับการคำนวณ เนื่องจากจำนวนบิตที่จำกัดมีจำนวนสถานะที่จำกัดเท่านั้น ดังนั้นพฤติกรรมจะต้องเริ่มทำซ้ำ ณ จุดใดจุดหนึ่ง ดังนั้นจึงต้องสร้าง การทำซ้ำเลขทศนิยม เลขฐานสิบที่ซ้ำกันนั้นเป็นจำนวนตรรกยะ แต่ sqrt(5) นั้นไม่มีเหตุผล ดังนั้น โปรแกรมที่จะพิมพ์ตัวเลข sqrt(5) “forever” จะต้องใช้การคำนวณที่แม่นยำตามอำเภอใจ โดยใช้จำนวนหน่วยความจำที่เพิ่มขึ้นเมื่อเวลาผ่านไป (ซึ่งหมายความว่าหน่วยความจำจะต้องหมดความสามารถในโลกที่มีขอบเขตจำกัดเช่นกัน)   -  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 เมื่อ สูตรสำหรับ pi ถูกค้นพบซึ่งช่วยให้สามารถคำนวณเลขฐานสองหลักที่ 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: ในโมเดล transdichotomous - person R.. GitHub STOP HELPING ICE; 20.02.2020
comment
อืมฉันไม่แน่ใจ ในแบบจำลอง transdichotomous ตามที่อธิบายไว้ในวิกิพีเดีย ค่าอินพุตไม่ขึ้นอยู่กับจำนวนอินพุต (n) โดยสิ้นเชิง ยกเว้นแต่มีข้อกำหนดว่าขนาดคำต้องมีอย่างน้อย log[2](n) ซึ่งฉันไม่แน่ใจว่ามีการระบุไว้อย่างดี . (แน่นอนว่าอย่างน้อยที่สุดก็ต้องเป็นเช่นนั้นเพื่อให้รายการสามารถมีรายการที่แตกต่างกันได้ n รายการที่จะเรียงลำดับ และนั่นอาจเป็นจุดประสงค์เดียวของข้อกำหนดนั้น แต่จริงๆ แล้วเราต้องการบิตที่เพียงพอเพื่อจัดการกับค่าอินพุตใดก็ตามที่เป็นอยู่จริง) ใน ผลรวมของเบลีย์-บอร์ไวน์-พลูฟฟ์ในการคำนวณเลขหลัก π จะใช้ผลรวมของ k โดยที่ k ได้รับผลกระทบจาก n - person Eric Postpischil; 20.02.2020
comment
@EricPostpischil: ขนาดอินพุตในปัญหานี้คือจำนวนบิตที่จะแทน n โดยที่ n คือตำแหน่งหลักที่ต้องการ แบบจำลอง Transdichotomous หมายความว่าขนาดคำของเครื่องของคุณเพียงพอที่จะแสดงถึง 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)) ลอการิทึม ฯลฯ ตราบใดที่คุณรู้วิธีแสดงมันเป็นเศษส่วนต่อเนื่อง สำหรับการใช้งาน โปรดดูที่ Digits of pi และ Python เครื่องกำเนิดไฟฟ้า. ดูเพิ่มเติมที่โค้ดสำหรับสร้าง e ทีละหลัก.

person Peter O.    schedule 07.05.2020