Проверьте простое число с библиотекой GMP в C - PullRequest
1 голос
/ 28 апреля 2020

Впервые я использую библиотеку gmp и хочу проверить, является ли число простым или нет, с помощью теста Ферма и Рабина Миллера. Программа хорошо работает с числами из 20 цифр, но я хочу протестировать очень большие числа с 300 и 500 цифрами или даже более, и с очень большими числами это ошибки, я не знаю почему!

вот код

#include <gmp.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>

void S_and_M(mpz_t a,mpz_t n,mpz_t h, mpz_t r) // square and multiply
{
    char * bin = mpz_get_str(NULL,2,h);  
    int i;                               
    mpz_set(r,a); 
    for(i = 1; i < strlen(bin);i++) 
    {                             
        mpz_mul(r,r,r);
        mpz_mod(r,r,n);
        if(bin[i] == '1')
        {
            mpz_mul(r,r,a);
            mpz_mod(r,r,n);
        }
    }
}

void M2Pow(mpz_t s,mpz_t S_pow)
{
  int i = 0;
  mpz_set_ui(S_pow,1); 
  while(mpz_cmp_si(s,i) > 0 ) 
  {
    mpz_mul_ui(S_pow,S_pow,2);
    i++;
  }
}

void Decomp(mpz_t x,mpz_t s,mpz_t t) // Function to decompose  x in 2^s * t
{                                    
    mpz_t y,S_pow;
    mpz_init(y);
    mpz_set_ui(y,0);

    mpz_init(S_pow);

    while(mpz_cmp(y,x)!=0) // while we don't find 2^s * t = x
    {
        mpz_set_ui(t,1); //we restart with t = 1
        mpz_mul(y,S_pow,t);// y = 2^s * t (we test the values)
        while(mpz_cmp(y,x) < 0)// we stop when 2^s * t > x
        {                      
            mpz_add_ui(t,t,2); 
            M2Pow(s,S_pow);
            mpz_mul(y,S_pow,t);
        }
        mpz_add_ui(s,s,1); 
    }

    mpz_sub_ui(s,s,1);
    mpz_clear(y);
    mpz_clear(S_pow);
}

void testFermat(mpz_t n, mpz_t rep)
{
    gmp_randstate_t state;  
    gmp_randinit_mt(state);
    gmp_randseed_ui(state, time(NULL));

    mpz_t i;
    mpz_init(i);
    mpz_set_si(i,1);

    mpz_t n2;
    mpz_init(n2);
    mpz_sub_ui(n2,n,1);

    mpz_t a;
    mpz_init(a);

    mpz_t r;
    mpz_init(r);

    mpz_t n3;
    mpz_init(n3);
    mpz_sub_ui(n3,n,3);

    while(mpz_cmp(i,rep)<=0 && mpz_cmp_si(n,2)!= 0  && mpz_cmp_si(n,3)!=0)
    {
        mpz_urandomm(a,state,n3);
        mpz_add_ui(a,a,2);
        S_and_M(a,n,n2,r);
        if(mpz_cmp_si(r,1)!=0)
        {
            printf("The number is composite \n");
            mpz_clear(i);mpz_clear(n2);
            mpz_clear(a);mpz_clear(r);
            mpz_clear(n3);gmp_randclear(state);
            return ;
        }
        mpz_add_ui(i,i,1);
    }

    printf("The number is prime \n");
    mpz_clear(i);mpz_clear(n2);
    mpz_clear(a);mpz_clear(r);
    mpz_clear(n3);gmp_randclear(state);
}

void Miller_Rabin(mpz_t n, mpz_t rep)
{
    if(mpz_get_ui(n) % 2 == 0)   
    {
        if(mpz_cmp_ui(n,2) == 0)
            printf("The number is prime \n");
        else
            printf("The number is composite \n");
        return;
    }

    int i=1;
    mpz_t a,y,s,t,n1,n2,deux;

    gmp_randstate_t state;    
    gmp_randinit_mt(state);
    gmp_randseed_ui(state, time(NULL));

    mpz_init(a);

    mpz_init(y);

    mpz_init(s);
    mpz_set_ui(s,1);

    mpz_init(deux);
    mpz_set_ui(deux,2);

    mpz_init(t);

    mpz_init(n1);
    mpz_sub_ui(n1,n,1);

    mpz_init(n2);
    mpz_sub_ui(n2,n,2);

    Decomp(n1,s,t);
    mpz_sub_ui(s,s,1);
    while(mpz_cmp_ui(rep,i)>=0)
    {
        mpz_urandomm(a,state,n1);
        mpz_add_ui(a,a,1);
        S_and_M(a,n,t,y);
        if(mpz_cmp_si(y,1)!=0 && mpz_cmp(y,n1)!=0) 
        {                                           
            for(int j=1;mpz_cmp_ui(s,j)>=0;j++)
            {
                mpz_set(n2,y);
                S_and_M(y,n,deux,y);
                if(mpz_cmp_si(y,1)==0) 
                {
                    printf("The number is composite\n");
                    mpz_clear(a);mpz_clear(y);
                    mpz_clear(s);mpz_clear(t);
                    mpz_clear(n1);mpz_clear(n2);
                    mpz_clear(deux);gmp_randclear(state);
                    return;
                }
                if(mpz_cmp(y,n1)==0) //Si y congrue a -1 mod n on sort de la boucle
                break;
            }
            if(mpz_cmp(y,n1)!=0)  
            {
                printf("The number is composiste \n");
                mpz_clear(a);mpz_clear(y);
                mpz_clear(s);mpz_clear(t);
                mpz_clear(n1);mpz_clear(n2);
                mpz_clear(deux);gmp_randclear(state);
                return;
            }

        }
        i++;
    }
    printf("The number is prime \n");

    mpz_clear(a);mpz_clear(y);
    mpz_clear(s);mpz_clear(t);
    mpz_clear(n1);mpz_clear(n2);
    mpz_clear(deux);gmp_randclear(state);
}

int main()
{
    int t=1;
    mpz_t n;
    mpz_init(n);
    mpz_t rep;
    mpz_init(rep);
    printf("########## Primality test ##########\n");
    while(t==1)
    {
        printf("\n");
        printf("Choose an integer to test : ");
        gmp_scanf("%Zd", &n);
        if(mpz_cmp_ui(n,1)<=0)
        {   
            printf("\n choose an integer bigger than 1 !");
        }
        else
        {
            printf("Choose the number of repetitions : ");
            gmp_scanf("%Zd", &rep);
            printf("#########################################################\n");
            printf("Miller_Rabin : ");
            Miller_Rabin(n,rep);
            printf("\n");
            printf("Fermat : ");
            testFermat(n,rep);
            printf("#########################################################\n");
            printf("tape 1 for an other test !");
            scanf("%d",&t);
        }   
    }
    mpz_clear(n);   
    mpz_clear(rep);
    return 0;
}

помогите пожалуйста.

1 Ответ

2 голосов
/ 29 апреля 2020

Во-первых; вы можете / должны поместить туда дополнительную печать (например, «printf(" Round %d\n", i);» в вашем Miller_Rabin()), чтобы определить, работает ли код и что он делает.

Некоторые советы по повышению производительности (если ваш код просто так долго, что вы предположили, что он заблокирован, а он нет):

  • Много времени будет потрачено на поиск модуля (например, в вашем S_and_M() ). Поскольку делитель всегда один и тот же, его можно ускорить, найдя обратную величину один раз (с достаточной точностью) и выполнив «modulo = X - (X * reciprocal); if(modulo => divisor) { modulo -= divisor}; return modulo;» (вместо использования mpz_mod()). Примечание: чтобы использовать только целые числа, вы действительно хотите сделать «shifted_reciprocal = (1 << bits) / divisor», чтобы получить обратную величину, сдвинутую влево на bits бит; и затем выполните «modulo = X - ((X * shifted_reciprocal) >> bits);».

  • При умножении больших чисел вы в конечном итоге умножаете каждое «di git» (слово, 32-битный фрагмент, ...) первое число с каждым «di git» второго числа. Для возведения в квадрат оба числа имеют одинаковые цифры, поэтому промежуточные результаты (умножающихся цифр) могут быть переработаны, что вдвое снижает стоимость этих промежуточных умножений. Это означает, что код, предназначенный для возведения в квадрат числа, может быть почти в два раза быстрее общего умножения c (например, mpz_mul()).

  • Я понятия не имею, что ваш Decomp() думает, что это делает (я подозреваю, что вы написали это, думая, что s должен обрабатывать числа, которые больше, чем количество бит оперативной памяти, которое имеет ваш компьютер, что абсурдно). В общем случае вы хотите посчитать количество «младших значащих битов» (чтобы найти s), а затем выполните одно правое смещение, чтобы найти t (например, «t = X >> s»). Для этого проверьте документацию для библиотеки GMP (я думаю, что все Decomp() может / должно быть просто "s = mpz_scan1(x, 0); mpz_tdiv_q_2exp(t, x, s);").

  • Используйте намного больше пробного деления. Для больших чисел вы можете найти модуль относительно быстро, если делитель меньше, чем «di git» (слово, 32-битный фрагмент, ...). Кроме того, вы можете объединить несколько маленьких делителей в одно «большое число по модулю», умножив их, а затем использовать меньшие (целочисленные) модули результата. Например, вместо «if( (mpz_mod_ui(dummy, big_number, 3) == 0) || (mpz_mod_ui(dummy, big_number, 5) == 0) || (mpz_mod_ui(dummy, big_number, 7) == 0) || (mpz_mod_ui(dummy, big_number, 11) == 0)» вы можете сделать «temp = mpz_mod_ui(dummy, big_number, 3*5*7*11); if( (temp%3 == 0) || (temp%5 == 0) || (temp%7 == 0) || (temp%11 == 0) )»; что значительно быстрее. Это может быть использовано в дальнейшем, имея (предварительно вычисленные) таблицы «групп делителей пробного деления», предназначенные для нахождения максимального числа делителей, которые при умножении вместе поместятся в «di git» (слово, 32-битный фрагмент, ...). Чтобы максимизировать эффективность (максимизировать количество записей в таблице до того, как продукты не помещаются в «di git», и мне нужно уменьшить количество делителей в записи таблицы), я генерирую эти таблицы, используя «маленькие и большие» "метод (например, 19 и 137 маленькие, 421 и 587 большие, а 19 * 137 * 421 * 587 = 0x26578B9D = число, достаточно маленькое, чтобы поместиться в 32 бита).

  • Вам не нужны большие целые числа (mpz), чтобы отслеживать количество раундов Миллера Рабина (while(mpz_cmp_ui(rep,i)>=0) l oop). Преобразуйте rep в int перед запуском l oop и вместо этого выполните while(i <= int_rep. Обратите внимание, что (для таких вещей, как поиск простых чисел для RSA-4096), 256 раундов Миллера Рабина - это ненужное излишество.

  • Миллер Рабин смущающе параллелен. Если у вас 8 процессоров, то вы хотите использовать 8 потоков и хотите выполнять (до) 8 раундов Миллера Рабина параллельно. Примечание. Если вы не выполните достаточное количество пробных делений, то первая итерация Миллера Рабина будет часто называться «составной», и в этом случае вы можете выполнить первую итерацию Миллера Рабина с одним потоком (и использовать только несколько потоков). после первой итерации).

...