Knihovna GMP: umocňování, výpočet modulu a funkce z teorie čísel

26. 11. 2024
Doba čtení: 3 minuty

Sdílet

 Autor: Depositphotos
GMP (GNU MP nebo též libgmp) je otevřená knihovna pro práci s čísly v libovolně přesné aritmetice. Ve druhém článku se podíváme na umocňování, výpočet modulu nebo různé funkce z teorie čísel.

Pokud bychom chtěli zjistit, kolik paměti nám zabírá které číslo, bude se nám hodit funkce mpz_sizeinbase, které předáme jako druhý parametr základ 2 (chceme velikost v binární soustavě). Výsledek pro základ 2 je vždy přesný.

mpz_set_str(num, "1000000", 10);

printf("bits = %zu\n", mpz_sizeinbase(num, 2)); /* vypíše bits = 20 */

Dále se podíváme na umocňování. Výsledky umocňování mají tendenci růst do závratných výšin. Proto se dá mpz_t umocnit jen na unsigned long. Například 1021 spočteme takhle:

mpz_t num;

mpz_init_set_ui(num, 10UL);

mpz_pow_ui(num, num, 21UL); /* num = 10^21 */

mpz_clear(num);

V příkladu také vidíme, že inicializaci a přiřazení hodnoty lze spojit do jediné funkce  mpz_init_set_ui.

Pokud se zabýváte teorií čísel, mohl by pro vás být zajímavý výpočet operace modulo. Ten můžeme realizovat samostatnou funkcí mpz_mod, kde jsou dělenec i dělitel typu  mpz_t.

mpz_set_ui(n, 11UL);
mpz_set_ui(d,  5UL);

mpz_mod(r, n, d); /* r = n mod d */

gmp_printf("r = %Zi\n", r); /* vypíše r = 1, protože n = 2 * 5 + 1 */

Když se teď opět vrátíme k umocňování, můžeme rovnou využít funkci mpz_powm, která spočte mocninu v daném modulu.

mpz_set_ui(b, 10UL);
mpz_set_ui(e, 21UL);
mpz_set_ui(m,  3UL);

mpz_powm(r, b, e, m); /* spočítá 10^21 mod 3 */

gmp_printf("r = %Zi\n", r); /* vypíše zbytek po dělení 10^21 / 3, což je 1 */

Tentokrát vidíme, že mpz_t lze umocnit na mpz_t, ale pohybujeme se v nějakém omezeném modulu.

Dále se podíváme na výpočet největšího společného dělitele dvou čísel. K tomu slouží funkce mpz_gcd, jejíž použití je velmi jednoduché:

mpz_set_ui(a, 37107UL); /* 37107 = 3 * 3 * 7 * 19 * 31 */
mpz_set_ui(b, 1254UL);  /* 1254  = 2 * 3 * 11 * 19 */;

mpz_gcd(r, a, b);

gmp_printf("r = %Zi\n", r); /* vypíše 57 = 3 * 19 */

A co třeba výpočet faktoriálu? Ten můžeme realizovat funkcí  mpz_fac_ui:

mpz_fac_ui(r, 25UL);

gmp_printf("r = %Zi\n", r); /* vypíše 15 511 210 043 330 985 984 000 000 (bez mezer) */

Teď se podíváme na generování pseudonáhodných čísel. Klíčem je proměnná typu gmp_randstate_t. Tu je třeba nejprve inicializovat pomocí funkce gmp_randinit_default, která použije jako generátor Mersenne Twister. Následně nastavíme náhodné semínko pomocí volání gmp_randseed_ui. Jednotlivá náhodná čísla v rozmezí 0 až n − 1 se pak generují voláním mpz_urandomm, přičemž n předáme jako poslední parametr.

gmp_randstate_t state;

gmp_randinit_default(state);

gmp_randseed_ui(state, 42UL);

mpz_set_ui(n, 100UL); /* rozsah [0; 100-1] */

for (int i = 0; i < 10; ++i) {
    mpz_urandomm(r, state, n);
    gmp_printf("%Zi\n", r);
}

Nyní se podíváme na manipulaci s bity. S čísly můžeme pracovat jako ve dvojkovém doplňku (ačkoli ve skutečnosti takto nejsou v paměti uložena). Můžeme použít bitové operace AND, OR a XOR. Například pro operaci AND máme k dispozici funkci mpz_and. Použití vypadá následovně.

bitcoin školení listopad 24

mpz_set_ui(a, 85UL); /* 1010101 */
mpz_set_ui(b, 31UL); /*   11111 */;

mpz_and(r, a, b);

gmp_printf("r = %Zi\n", r); /* vypíše r = 21, což je 10101 */

Podobně existují i funkce mpz_ior a mpz_xor. Stejně tak máme k dispozici třeba operaci popcount (počet bitů, které jsou nastaveny na 1) pomocí funkce  mpz_popcount.

mp_bitcnt_t pcnt;

mpz_set_ui(a, 85UL); /* 1010101 */

pcnt = mpz_popcount(a);

printf("%zu\n", (size_t)pcnt); /* vypíše 4 */

Na závěr malý příklad. K tomu budeme potřebovat funkci mpz_mul_2exp, která spočítá bitový posun doleva neboli první operand krát dvě na druhý operand, tedy op1 × 2op2. Příklad hledá kandidáty na Mersennova prvočísla pomocí Fermatova testu prvočíselnosti se základem 3:

/* přes všechny liché exponenty */
for (unsigned long k = 1; ; k += 2) {
    mpz_set_ui(Mp, 1UL); /* začneme s číslem 1 */
    mpz_mul_2exp(Mp, Mp, k); /* spočteme 2^k */
    mpz_sub_ui(Mp, Mp, 1UL); /* spočteme 2^k - 1 */

    mpz_sub_ui(Mp_1, Mp, 1UL); /* dále spočteme 2^k - 2 */

    mpz_set_ui(b, 3UL); /* základ bude 3 */

    mpz_powm(x, b, Mp_1, Mp); /* vypočítat Fermatův test */

    /* je-li výsledek kongruentní s 1 */
    if (mpz_cmp_ui(x, 1UL) == 0) {
        /* máme kandidáta na prvočíslo */
        printf("PRP: %lu\n", k);
    }
}

Reference

Seriál: Knihovna GMP
ikonka

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.

Autor článku

Autor vystudoval Fakultu informačních technologií VUT v Brně, kde nyní pracuje jako vědecký pracovník. Zajímá se o multimédia a na svých strojích používá výhradně Gentoo Linux.