Duże liczby

Czasami programista staje przed zadaniem obliczenia wartości leżącej poza zakresem standardowych typów danych. Załóżmy, że chcemy wyznaczyć wartość 21000. Jeśli zastosujemy największy typ całkowity unsigned long long int, to największą liczbą, którą może on przedstawić, jest 264 - 1. To jest niestety zbyt mało. Co zatem możemy zrobić? Odpowiedź jest jedna - zaprojektować działania arytmetyczne na liczbach o dowolnej długości. Do tego celu wykorzystamy znany wszystkich algorytm dodawania liczb dziesiętnych.

 

Dodawanie dużych liczb

Liczby będziemy reprezentowali jako łańcuchy znakowe, które zawierają poszczególne cyfry. Dodawanie rozpoczynamy od ostatnich cyfr każdego z łańcuchów:

 

"157766872237632767623867637"
         "779723827372323329"
 

Wyznaczamy ostatnie cyfry 2 i 3, i wykonujemy ich dodawanie, otrzymując  wynik w.

 

7 + 9 = 16
 

Ostatnia cyfra w przechodzi do wyniku dodawania.

 

  157766872237632767623867637
 +         779723827372323329
-----------------------------
                            6
 

Jeśli wynik dodawania cyfr jest większy od 9, to otrzymujemy przeniesienie 1 do następnej kolumny. Przeniesienie to należy dodać do wyniku sumowania cyfr w kolejnej kolumnie. Operację przerywamy, gdy przetworzymy wszystkie cyfry najdłuższej liczby - brakujące cyfry krótszej liczby zastępujemy zerem. Jeśli po dodaniu wszystkich cyfr przeniesienie wynosi 1, to dopisujemy je do wyniku.

Ostatnią cyfrę w otrzymamy jako

w % 10

Przeniesienie do następnej kolumny otrzymamy jako

w / 10 (dzielenie całkowitoliczbowe)

Musimy jeszcze pamiętać, iż cyfry wyniku otrzymujemy od tyłu, zatem muszą one być wstawiane na początek tekstu w łańcuchu wynikowym.

 

Na podstawie tej metody tworzymy pierwszy program, który dodaje liczby zapisane w postaci łańcuchów cyfr.

 

// Dodawanie dowolnych liczb dziesiętnych
// (C)2011 ILO w Tarnowie
// KOŁO INFORMATYCZNE
//-----------------------

#include <iostream>
#include <string>

using namespace std;

// funkcja zwraca ciąg znaków będący wynikiem dodawania argumentów
//----------------------------------------------------------------

string dodaj(string a, string b)
{
    // wyznaczamy pozycję ostatniej cyfry w obu łańcuchach

    int pa = a.length() - 1;
    int pb = b.length() - 1;

    // zerujemy łańcuch wynikowy

    string wynik = "";

    // na początku przeniesienie wynosi 0

    int p = 0;

    // w pętli dodajemy kolejne cyfry a i b

    while(pa >= 0 || pb >= 0)
    {
        // wyliczamy wartości cyfr w łańcuchach a i b

        int ca, cb, w;

        if(pa >= 0) ca = a[pa] - 48; else ca = 0;
        if(pb >= 0) cb = b[pb] - 48; else cb = 0;

        // obliczamy sumę cyfr z a i b, uwzględniając przeniesienie

        w = ca + cb + p;

        // do łańcucha wynikowego wstawiamy ostatnią cyfrę w

        wynik = "0" + wynik;
        wynik[0] = 48 + w % 10;

        // obliczamy przeniesienie do następnej kolumny

        p = w / 10;

        // modyfikujemy indeksy kolumn w łańcuchu a i b

        pa--; pb--;
    }

    // jeśli przeniesienie wynosi 1, dopisujemy cyfrę 1 do wyniku

    if(p) wynik = "1" + wynik;

    return wynik;
}

int main()
{
    string a, b;

    cin >> a >> b;

    cout << a << " + " << b << " = " << dodaj(a,b) << endl;

    return 0;
}

 

Duże potęgi liczby 2

Kolejne potęgi liczby 2 możemy obliczać jako sumy:

 

20 = 1
21 = 2 = 20 + 20
22 = 4 = 21 + 21
23 = 8 = 22 + 22
24 = 16 = 23 + 23

 

Zatem znając wartość potęgi 2n, następną policzymy jako:

 

2n + 1 = 2n + 2n

 

Na tej podstawie tworzymy następny program, który liczy dowolną (rozsądnie dużą) potęgę liczby 2. Rozpoczyna on od 20 = 1, a następnie w pętli wykonywanej n razy sumuje tę wartość ze sobą. Do obliczeń wykorzystujemy liczby w postaci łańcuchów cyfr oraz funkcję dodaj().

 

// Obliczanie potęgi liczby 2
// (C)2011 ILO w Tarnowie
// KOŁO INFORMATYCZNE
//-----------------------

#include <iostream>
#include <string>

using namespace std;

// funkcja zwraca ciąg znaków będący wynikiem dodawania argumentów
//----------------------------------------------------------------

string dodaj(string a, string b)
{
    // wyznaczamy pozycję ostatniej cyfry w obu łańcuchach

    int pa = a.length() - 1;
    int pb = b.length() - 1;

    // zerujemy łańcuch wynikowy

    string wynik = "";

    // na początku przeniesienie wynosi 0

    int p = 0;

    // w pętli dodajemy kolejne cyfry a i b

    while(pa >= 0 || pb >= 0)
    {
        // wyliczamy wartości cyfr w łańcuchach a i b

        int ca, cb, w;

        if(pa >= 0) ca = a[pa] - 48; else ca = 0;
        if(pb >= 0) cb = b[pb] - 48; else cb = 0;

        // obliczamy sumę cyfr z a i b, uwzględniając przeniesienie

        w = ca + cb + p;

        // do łańcucha wynikowego wstawiamy ostatnią cyfrę w

        wynik = "0" + wynik;
        wynik[0] = 48 + w % 10;

        // obliczamy przeniesienie do następnej kolumny

        p = w / 10;

        // modyfikujemy indeksy kolumn w łańcuchu a i b

        pa--; pb--;
    }

    // jeśli przeniesienie wynosi 1, dopisujemy cyfrę 1 do wyniku

    if(p) wynik = "1" + wynik;

    return wynik;
}

int main()
{
    string a = "1";
    int i, n;

    cin >> n;  // odczytujemy wykładnik

    // w pętli sumujemy a z a n razy

    for(i = 0; i < n; i++) a = dodaj(a, a);

    // wyświetlamy wynik

    cout << "2^" << n << " = " << a << endl << endl;

    return 0;
}

 

Dla dużych n  czas obliczeń może być bardzo długi. Oto jeden z wyników pracy tego programu:

 

10000
2^10000 = 1995063116880758384883742162683585083823496831886192454852008949852943
88302219466319199616840361945978993311294232091242715564913494137811175937859320
96323957855730046793794526765246551266059895520550086918193311542508608460618104
68550907486608962488809048989483800925394163325785062156830947390255691238806522
50966438744410467598716269854532228685381616943157756296407628368807607322285350
91641476183956381458969463899410840960536267821064621427333394036525565649530603
14268023496940033593431665145929777327966577560617258203140799419817960737824568
37622800373028854872519008344645814546505579296014148339216157345881392570953797
69119277800826957735674444123062018757836325502728323789270710373802866393031428
13324140162419567169057406141965434232463880124885614730520743199225961179625013
09928602417083408076059323201612684922884962558413128440615367389514871142563151
11089745514203313820202931640957596464756010405845841566072044962867016515061920
63100418642227590867090057460641785695191145605506825125040600751984226189805923
71180544447880729063952425483392219827074044731623767608466130337787060398034131
97133493654622700563169937455508241780972810983291314403571877524768509857276937
92643322159939987688666080836883783802764328277517227365757274478411229438973381
08616074232532919748131201976041782819656974758981645312584341359598627841301281
85406283476649088690521047580882615823961985770122407044330583075869039319604603
40497315658320867210591330090375282341553974539439771525745529051021231094732161
07534748257407752739863482984983407569379556466386218745694992790165721037013644
33135817214311791398222983845847334440270964182851005072927748364550578634501100
85298781238947392869954083434615880704395911898581514577917714361969872813145948
37832020814749821718580113890712282509058268174362205774759214176537156877256149
04582904992461028630081535583308130101987675856234343538955409175623400844887526
16264356864883351946372037729324009445624692325435040067802727383775537640672689
86362410374914109667185570507590981002467898801782719259533812824219540283027594
08448955014676668389697996886241636313376393903373455801407636741877711055384225
73949911018646821969658165148513049422236994771476306915546821768287620036277725
77237813653316111968112807926694818872012986436607685516398605346022978715575179
47385246369446923087894265948217008051120322365496288169035739121368338393591756
41873385051097027161391543959099159815465441733631165693603112224993796999922678
17323580231118626445752991357581750081998392362846152498810889602322443621737716
18086357015468484058622329792853875623486556440536962622018963571028812361567512
54333830327002909766865056855715750551672751889919412971133769014991618131517154
40077286505731895574509203301853048471138183154073240533190384620840364217637039
11550639789000742853672196280903477974533320468368795868580237952218629120080742
81955131794815762444829851846150970488802727472157468813159475040973211508049819
0455803416826949787141316063210686391511681774304792596709376

 

Duże liczby ciągu Fibonacciego

Ciąg liczb Fibonacciego jest ciągiem rekurencyjnym, który definiujemy następująco:

 

fib0  = 0
fib1  = 1
fibn  = fibn-2 + fibn-1, dla n  > 1

 

Kolejna liczba Fibonacciego (za wyjątkiem pierwszych dwóch) powstaje jako suma dwóch liczb poprzednich. Oto kilka początkowych liczb Fibonacciego:

 

0 1 1 2 3 5 8 13 21 34 55 89 114 233 377 610 987 ...

 

Liczby Fibonacciego znajdują bardzo wiele zastosowań w informatyce i matematyce - na pewno spotkasz się z nimi jeszcze wielokrotnie. Ich wzrost jest bardzo szybki. Na przykład:

 

fib49  = 7,778,742,049

 

Jeśli chcemy policzyć fib10000, to okaże się, iż wynik przekroczy szybko dopuszczalny zakres standardowych typów danych. Rozwiązaniem jest nasze podejście. Algorytm efektywnego obliczania liczb Fibonacciego opiera się na tak zwanym programowaniu dynamicznym. Polega ono na tym, iż do wyliczenia kolejnych wartości wykorzystujemy wartości policzone już wcześniej. W naszym przypadku będziemy zapamiętywać dwie ostatnio wyliczone liczby Fibonacciego, a kolejną liczbę otrzymamy jako ich sumę. Do rachunków wykorzystamy łańcuchy znakowe oraz naszą funkcję dodaj().

 

// Obliczanie wartości liczb Fibonacciego
// (C)2011 ILO w Tarnowie
// KOŁO INFORMATYCZNE
//-----------------------

#include <iostream>
#include <string>

using namespace std;

// funkcja zwraca ciąg znaków będący wynikiem dodawania argumentów
//----------------------------------------------------------------

string dodaj(string a, string b)
{
    // wyznaczamy pozycję ostatniej cyfry w obu łańcuchach

    int pa = a.length() - 1;
    int pb = b.length() - 1;

    // zerujemy łańcuch wynikowy

    string wynik = "";

    // na początku przeniesienie wynosi 0

    int p = 0;

    // w pętli dodajemy kolejne cyfry a i b

    while(pa >= 0 || pb >= 0)
    {
        // wyliczamy wartości cyfr w łańcuchach a i b

        int ca, cb, w;

        if(pa >= 0) ca = a[pa] - 48; else ca = 0;
        if(pb >= 0) cb = b[pb] - 48; else cb = 0;

        // obliczamy sumę cyfr z a i b, uwzględniając przeniesienie

        w = ca + cb + p;

        // do łańcucha wynikowego wstawiamy ostatnią cyfrę w

        wynik = "0" + wynik;
        wynik[0] = 48 + w % 10;

        // obliczamy przeniesienie do następnej kolumny

        p = w / 10;

        // modyfikujemy indeksy kolumn w łańcuchu a i b

        pa--; pb--;
    }

    // jeśli przeniesienie wynosi 1, dopisujemy cyfrę 1 do wyniku

    if(p) wynik = "1" + wynik;

    return wynik;
}

int main()
{
    string fib;
    int n, i;

    cin >> n; // odczytujemy numer liczby Fib

    // programowaniem dynamicznym wyznaczamy n-tą liczbę Fib

    if(n == 0) fib = "0";
    else if(n == 1) fib = "1";
    else
    {
        string fib0 = "0";
        string fib1 = "1";

        for(i = 2; i <= n; i++)
        {
            // wyznaczamy kolejną liczbę Fib jako sumę dwóch poprzednich

            fib2 = dodaj(fib0, fib1);

            // zapamiętujemy w fib0 i fib1 dwie ostatnie liczby

            fib0 = fib1;
            fib1 = fib2;
        }
    }

    // wyświetlamy wyniki

    cout << "fib " << n << " = " << fib << endl << endl;

    return 0;
}

 

10000
fib 10000 = 33644764876431783266621612005107543310302148460680063906564769974680
08144216666236815559551363373402558206533268083615937373479048386526826304089246
30564318873545443695598274916066020998841839338646527313000888302692356736131351
17579297437854413752130520504347701602264758318906527890855154366159582987279682
98751063120057542878345321551510387081829896979161312785626503319548714021428753
26981879620469360978799003509623022910263681314931952756302278376284415403605844
02572114334961180023091208287046088923962328835461505776583271252546093591128203
92528539343462090424524892940390170623388899108584106518317336043747073790855263
17643257339937128719375877468974799263058370657428301616374089691784263786242128
35258112820516370298089332099905707920064367426202389783111470054074998459250360
63356093388383192338678305613643535189213327973290813373264265263398976392272340
78829281779535805709936910491754708089318410561463223382174656373212482263830921
03297701648054726243842374862411453093812206564914032751086643394517512161526545
36133311131404243685480510676584349352383695965342807176877532834823434555736671
97313927462736291082106792807847180353291311767789246590899386354593278945237776
74406192240337638674004021330343297496902028328145933418826817683893072003634795
62311710310129195316979460763273758925353077255237594378843450406771555577905645
04430166401194625809722167297586150269684431469520346149322911059706762432685159
92834709891284706740862008587135016260312071903172086094081298321581077282076353
18662461127824553720853236530577595643007251774431505153960090516860322034916322
26408852488524331580515348496224348482993809050704834824493274537326245677558790
89187190803662058009594743150052402532709746995318770724376825907419939632265984
14749819360928522394503970716544315642132815768890805878318340491743455627052022
35648464951961124602683139709750693826487066132645076650746115126775227486215986
42530711298441182622661057163515069260029861704945425047491378115154139941550671
25627119713325276363193960690289565028826860836224108205056243070179497617112123
3066073310059947366875

 

Mnożenie dużej liczby przez małą (do 64 bitów)

Posiadamy funkcję dodającą dwie duże liczby. Zastanówmy się, czy możemy ją wykorzystać również przy mnożeniu. W tym celu musimy zauważyć prostą zależność. Załóżmy, że chcemy pomnożyć pewną liczbę a przez 26:

 

w = 26 × a

 

Rozłóżmy liczbę 26 na sumę potęg liczby 2:

 

26 = 16 + 8 + 2 = 24 + 23 + 21

 

Teraz nasz iloczyn możemy przedstawić następująco:

 

w = 26 × a = (16 + 8 + 2) × a = 16a + 8a + 2a

 

Aby policzyć 26 × a wystarczy dodać do siebie 16a, 8a, 2a. Wyznaczenie kolejnych iloczynów a, 2a, 4a, 8a, 16a jest bardzo proste - wystarczy dodawać do siebie kolejne wyrażenia:

 

a  
2a  = a + a
4a  = 2a + 2a
8a  = 4a + 4a
16a  = 8a + 8a

 

W trakcie wyznaczania tych iloczynów dodajemy do wyniku odpowiedni iloczyn 2i × a, jeśli 2i  wchodzi w skład rozkładu liczby 26. Rozkład na potęgi liczby 2 jest równoważny zapisowi binarnemu liczby 26. Kolejne bity od końca liczby binarnej posiadają wagi równe potęgom liczby 2:

 

  16 8 4 2 1
2610 = 1 1 0 1 0

 

Jeśli biti ma wartość 1, to rozkład liczby zawiera potęgę 2i. Pozycje bitów numerujemy od strony prawej do lewej poczynając od 0. W pamięci komputera liczby są przechowywane binarnie, zatem nic nie musimy wyliczać, wystarczy przeglądać poszczególne bity mnożnika za pomocą operacji & maska, gdzie maska posiada tylko jeden bit ustawiony na 1. Jeśli wynik tej operacji będzie różny od 0, to bit mnożnika odpowiadający bitowi maski jest ustawiony na 1. W przeciwnym razie jest on ustawiony na 0.

Wynika stąd prosta zasada mnożenia:

 

Zerujemy wynik. Ustawiamy mnożną a. W pętli przeglądamy kolejne bity mnożnika poczynając od najmłodszego i idąc w kierunku najstarszego. Jeśli bit jest ustawiony na 1, to do wyniku dodajemy mnożną. Mnożną dodajemy do siebie i wykonujemy następny obieg pętli. Gdy przejrzymy wszystkie bity mnożnika, w wyniku będziemy mieli wartość iloczynu.

 

// Mnożenie dużych liczb
// (C)2011 ILO w Tarnowie
// KOŁO INFORMATYCZNE
//-----------------------

#include <iostream>
#include <string>

using namespace std;

// funkcja zwraca ciąg znaków będący wynikiem dodawania argumentów
//----------------------------------------------------------------

string dodaj(string a, string b)
{
    // wyznaczamy pozycję ostatniej cyfry w obu łańcuchach

    int pa = a.length() - 1;
    int pb = b.length() - 1;

    // zerujemy łańcuch wynikowy

    string wynik = "";

    // na początku przeniesienie wynosi 0

    int p = 0;

    // w pętli dodajemy kolejne cyfry a i b

    while(pa >= 0 || pb >= 0)
    {
        // wyliczamy wartości cyfr w łańcuchach a i b

        int ca, cb, w;

        if(pa >= 0) ca = a[pa] - 48; else ca = 0;
        if(pb >= 0) cb = b[pb] - 48; else cb = 0;

        // obliczamy sumę cyfr z a i b, uwzględniając przeniesienie

        w = ca + cb + p;

        // do łańcucha wynikowego wstawiamy ostatnią cyfrę w

        wynik = "0" + wynik;
        wynik[0] = 48 + w % 10;

        // obliczamy przeniesienie do następnej kolumny

        p = w / 10;

        // modyfikujemy indeksy kolumn w łańcuchu a i b

        pa--; pb--;
    }

    // jeśli przeniesienie wynosi 1, dopisujemy cyfrę 1 do wyniku

    if(p) wynik = "1" + wynik;

    return wynik;
}

// Funkcja mnoży małą liczbę 32 bitową przez dużą
//-----------------------------------------------

string mnoz(unsigned a, string b)
{
    string wynik = "0";
    unsigned maska;

    for(maska = 1; maska; maska <<= 1)
    {
        if(a & maska) wynik = dodaj(wynik, b);
        b = dodaj(b, b);
    }

    return wynik;  
}
 
int main()
{
    unsigned a;
    string b;

    cin >> a >> b;

    cout << a << " x " << b << " = " << mnoz(a, b) << endl << endl;

    return 0;
}

 

Silnia

Silnia n  jest iloczynem kolejnych liczb naturalnych od 1 do n:

 

1! = 1
2! = 1 × 2 = 2
3! = 1 × 2 × 3 = 6
4! = 1 × 2 × 3 × 4 = 24

 

Silnia rośnie bardzo szybko. Dla maksymalnego typu unsigned long long int już 21! przekroczy dopuszczalny zakres liczb. A my zamierzamy policzyć wartość 1000! Wykorzystamy oczywiście naszą funkcję mnoz().

 

// Silnia!
// (C)2011 ILO w Tarnowie
// KOŁO INFORMATYCZNE
//-----------------------

#include <iostream>
#include <string>

using namespace std;

// funkcja zwraca ciąg znaków będący wynikiem dodawania argumentów
//----------------------------------------------------------------

string dodaj(string a, string b)
{
    // wyznaczamy pozycję ostatniej cyfry w obu łańcuchach

    int pa = a.length() - 1;
    int pb = b.length() - 1;

    // zerujemy łańcuch wynikowy

    string wynik = "";

    // na początku przeniesienie wynosi 0

    int p = 0;

    // w pętli dodajemy kolejne cyfry a i b

    while(pa >= 0 || pb >= 0)
    {
        // wyliczamy wartości cyfr w łańcuchach a i b

        int ca, cb, w;

        if(pa >= 0) ca = a[pa] - 48; else ca = 0;
        if(pb >= 0) cb = b[pb] - 48; else cb = 0;

        // obliczamy sumę cyfr z a i b, uwzględniając przeniesienie

        w = ca + cb + p;

        // do łańcucha wynikowego wstawiamy ostatnią cyfrę w

        wynik = "0" + wynik;
        wynik[0] = 48 + w % 10;

        // obliczamy przeniesienie do następnej kolumny

        p = w / 10;

        // modyfikujemy indeksy kolumn w łańcuchu a i b

        pa--; pb--;
    }

    // jeśli przeniesienie wynosi 1, dopisujemy cyfrę 1 do wyniku

    if(p) wynik = "1" + wynik;

    return wynik;
}

// Funkcja mnoży małą liczbę 32 bitową przez dużą
//-----------------------------------------------

string mnoz(unsigned a, string b)
{
    string wynik = "0";
    unsigned maska;

    for(maska = 1; maska; maska <<= 1)
    {
        if(a & maska) wynik = dodaj(wynik, b);
        b = dodaj(b, b);
    }

    return wynik;  
}
 
int main()
{
    int i,n;
    string silnia = "1";

    cin >> n;

    for(i = 1; i <= n; i++) silnia = mnoz(i,silnia);

    cout << n << "! = " << silnia << endl << endl;

    return 0;
}

 

1000
1000! = 402387260077093773543702433923003985719374864210714632543799910429938512
39862902059204420848696940480047998861019719605863166687299480855890132382966994
45909974245040870737599188236277271887325197795059509952761208749754624970436014
18278094646496291056393887437886487337119181045825783647849977012476632889835955
73543251318532395846307555740911426241747434934755342864657661166779739666882029
12073791438537195882498081268678383745597317461360853795345242215865932019280908
78297308431392844403281231558611036976801357304216168747609675871348312025478589
32076716913244842623613141250878020800026168315102734182797770478463586817016436
50241536913982812648102130927612448963599287051149649754199093422215668325720808
21333186116811553615836546984046708975602900950537616475847728421889679646244945
16076535340819890138544248798495995331910172335555660213945039973628075013783761
53071277619268490343526252000158885351473316117021039681759215109077880193931781
14194545257223865541461062892187960223838971476088506276862967146674697562911234
08243920816015378088989396451826324367161676217916890977991190375403127462228998
80051954444142820121873617459926429565817466283029555702990243241531816172104658
32036786906117260158783520751516284225540265170483304226143974286933061690897968
48259012545832716822645806652676995865268227280707578139185817888965220816434834
48259932660433676601769996128318607883861502794659551311565520360939881806121385
58600301435694527224206344631797460594682573103790084024432438465657245014402821
88525247093519062092902313649327349756551395872055965422874977401141334696271542
28458623773875382304838656889764619273838149001407673104466402598994902222217659
04339901886018566526485061799702356193897017860040811889729918311021171229845901
64192106888438712185564612496079872290851929681937238864261483965738229112312502
41866493531439701374285319266498753372189406942814341185201580141233448280150513
99694290153483077644569099073152433278288269864602789864321139083506217095002597
38986355427719674282224875758676575234422020757363056949882508796892816275384886
33969099598262809561214509948717012445164612603790293091208890869420285106401821
54399457156805941872748998094254742173582401063677404595741785160829230135358081
84009699637252423056085590370062427124341690900415369010593398383577793941097002
77534720000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000

 


   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2024 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe