Рассмотрим задачу отыскания всех простых чисел в интервале от 2 до некоторого заданного числа $$n$$. Выделим в исходном списке $$2,3, \dots , n$$ первое число, которое будет простым, и затем зачеркнем как само число 2, так и все числа в списке, кратные ему. Чтобы зачеркнуть эти кратные числа, достаточно зачеркнуть каждое второе число в списке, начиная с числа 2. Затем выделяем первое незачеркнутое число в качестве очередного простого - им будет число 3, и проводим аналогичную процедуру, зачеркивая каждое третье число, начиная с числа 3. Продолжаем эту процедуру до тех пор, пока не дойдем до очередного простого числа $$p$$, такого что $$p^2 \ge n$$. Легко видеть, что все оставшиеся незачеркнутыми числа в интервале $$p, \dots, n$$ являются простыми. Описанный метод нахождения простых чисел носит название
В этом разделе будет описан параллельный вариант алгоритма
Будем рассматривать задачу нахождения количества простых чисел в интервале от 2 до $$n$$ ( $$n \ge 2$$ ). (В действительности, в программе будет иметься специальный ключ, задание которого будет приводить также к распечатке всех найденных простых чисел).
Идея алгоритма состоит в разбиении поиска на 2 этапа: на 1-ом этапе, который выполняется последовательно, находятся все простые числа в диапазоне $$2\dots\sqrt{n} $$ с помощью классического метода
В приведенном ниже алгоритме учитываются также следующие свойства задачи:
Назначение первой стадии ? отыскать все простые числа в поддиапазоне $$2\dots\sqrt{n} $$, которые затем будут использоваться для нахождения простых чисел в следующих поддиапазонах.
В начале 1-ой стадии, размещаются массивы is_composite и prime_steps размерности $$m/2$$, где первый массив служит для процедуры просеивания, а во втором массиве сохраняются найденный на этой стадии простые числа. В массиве is- элемент с номером $$i$$ соответствует нечетному числу $$2_i+1$$. Сам алгоритм состоит в обычном просеивании Эратосфена, начиная с элемента массива is_composite с номером $$i=1$$, т.е., простого числа 3 (простое число 2 учитывается специальным образом).
Вторая стадия алгоритма заключается в одновременной обработке $$m$$ поддиапазонов, укладывающихся в интервале $$\sqrt{n}\dots n $$, с помощью нескольких потоков.
Каждый поток, в общем случае, обрабатывает несколько поддиапазонов, сохраняя количество найденных в них простых чисел в соответствующем элементе массива count_primes. После окончания работы всех потоков (т.е., после выхода из цикла count_primes, которая добавляется к количеству найденных простых чисел на первом этапе. Это значение - totalCountPrimes и становится результатом решения задачи.
Ключевым моментом 2-ой стадии является обработка потоком одного поддиапазона, начинающегося с числа start, и имеющего размер $$m$$. Для начала непосредственного просеивания в этом поддиапазоне, необходимо для каждого простого числа $$f$$, найденного на первой стадии, вычислить смещение от начала данного поддиапазона ? т.е., позицию, начиная с которой будет происходить зачеркивание чисел с определенным шагом, равным $$f$$.
Вычисление данного смещения содержательно можно разбить на 4 шага:
поскольку в каждом поддиапазоне проверяются на простоту только нечетные числа, то позиция (отсчитываемая от начала предыдущего поддиапазона) следующего нечетного числа (т.е., числа с которого начнется просеивание в текущем поддиапазоне) определяется как
$$l = p + f,$$ если $$p$$ четно,
и $$l = p + 2*f$$, если $$p$$ нечетно
Рассмотрим пример вычисления смещения в соответствии с шагами, представленными выше.
Пусть $$n = 100$$, тогда $$m = 10$$, и простыми числами, найденными в первом поддиапазоне, являются 3,5,7.
Рассмотрим, допустим, третий по счету поддиапазон, задаваемый, соответственно параметрами $$start = 20$$ и $$f = 3$$. Тогда$$k = ( 20 -1 ) / 3 * 3 = 18$$
Т.е., последнее число, кратное 3, в предыдущем поддиапазоне есть число 18, которое отстоит от начала предыдущего поддиапазона на расстоянии
$$p = 18 % 10 = 8$$Т.к. $$p$$ четно, то позиция следующего нечетного числа определяется как
$$l = 8 + 3 = 11$$(легко заметить, что эта позиция соответствует числу 21). Тогда, число 21, с которого мы начнем зачеркивание чисел в третьем поддиапазоне с шагом 3, имеет смещение относительно начала этого диапазона
$$fpos = 11 / 2 - 10 / 2 = 0$$Обязательными ParallelCountPrimes,
Полный текст программы с комментариями приведен ниже.
//Программа реализует алгоритм поиска простых чисел, подобный алгоримту,
//реализованному в примерах библиотеки Intel TBB (tbb21_20080605).
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace Sieve
{
class Program
{
static bool PrintPrimes;
class Multiples
{
// хранит флаг простоты нечетного числа в данном поддиапазоне
private bool[] is_composite;
// простые числа найденные на первом этапе алгоритма
private int[] prime_steps;
//индекс в поддиапазоне is_composite, с которого нужно начать вычеркивание с шагом prime_steps[k]
private int[] to_strike;
// число найденных на первом этапе простых чисел
public int n_prime_steps;
public int m;
// Конструктор копирования
public Multiples(Multiples rhs)
{
prime_steps = new int[rhs.prime_steps.Length];
rhs.prime_steps.CopyTo(prime_steps, 0);
n_prime_steps = rhs.n_prime_steps;
m = rhs.m;
}
public Multiples(int n)
{
m = (int)Math.Sqrt(n);
m += m1;
// Для работы алгоритма требуется меньшее или равное m/2 = (|_sqrt(n)_|)/2 количество ячеек в массиве
// так как:
// 1) ищутся только простые числа, меньшие либо равные sqrt(n)
// 2) простых чисел не может быть больше половины всех чисел (т.к. каждое второе число четное)
is_composite = new bool[m/2];
prime_steps = new int[m/2];
n_prime_steps = 0;
// найдем все простые числа меньшие m: m=to_even(sqrt(n))
// перебираем все нечетные числа в поддиапазоне [3;m-1]
// (т.к. четные числа автоматически вычеркнуты)
for (int i = 3; i<= m; i += 2)
{
//если число ранее не было признано составным (т.е. это число простое)
if( !is_composite[i/2] )
{
if( PrintPrimes ) Console.WriteLine(i);
// с шагом равному простому числу перемещаемся по интервалу [простое число/2; m/2]
// в результате вычеркнем все числа, делящиеся на простое число i (модифицируем массив is_composite)
strike( i/2, m/2, i );
// запомним шаг\новое простое число
prime_steps[n_prime_steps++] = i;
}
}
}
// Процедура strike вычеркивает составные числа в интервале [start;limit) с шагом step.
// Возвращает значение limit%step==0? limit: limit-step.
int strike(int start, int limit, int step)
{
for (; start<= limit; start += step)
is_composite[start] = true;
return start;
}
// Поиск простых чисел в пддиапазоне [start,window_size).
Возвращает число найденных простых чисел
// В процессе работы изменяет массив to_strike
public int find_primes_in_window( int start, int window_size )
{
for( uint k=0; kle;n_prime_steps; ++k )
to_strike[k] = strike( to_strike[k]-m/2, window_size/2, prime_steps[k] );
int count = 0;
for( int k=0; kle;window_size/2; ++k )
{
if (!is_composite[k])
{
if( PrintPrimes ) Console.WriteLine(start+2*k+1);
//нашли простое число, увеличим счетчик на 1
count=count+1;
}
}
return count;
}
// Функция расчета смещений для каждого шага (простого числа).
Смещения указываются от начала поддиапазона
public void initialize(int start)
{
is_composite = new bool[m / 2];
to_strike = new int[m / 2];
for (uint k = 0; k<= n_prime_steps; ++k)
{
// выберем простое число f (шаг)
int f = prime_steps[k];
// p - расстояние от начала предыдущего поддиапазона,
на котором остановилось вычеркивание с данным шагом f
int p = (start - 1) / f * f % m;
to_strike[k] = (Convert.ToBoolean(p 1) - p + 2 * f : p + f) / 2;
}
}
}
class Sieve
{
public Multiples multiples;
// Число простых чисел, найденных данным решетом
public int count = 0;
// Конструктор копирования
public Sieve(Sieve rhs)
{
multiples = new Multiples(rhs.multiples);
}
public Sieve( int n )
{
multiples = new Multiples(n);
}
// Поиск простых чисел в подиапазоне [start;start+window_size]
public int calc(int start, int window_size, int n)
{
// вычисляем начальные смещения для данного поддиапазона (для всех шагов)
multiples.initialize(start);
int tt = window_size;
// контролируем выход за границы общего диапазона
if (start + window_size > n)
tt = n - start;
// ищем простые числа в данном поддиапазоне
return multiples.find_primes_in_window(start, tt);
}
// Поиск простых чисел в интервале для данного потока
public int ThreadSieving(int window_size, int start, int fin, int n)
{
Sieve s1 = new Sieve(this);
int count_primes = 0;
for (int j = start; j<= fin; j += window_size)
count_primes += s1.calc(j, window_size, n);
return count_primes;
}
}
public static int ParallelCountPrimes(int n, ref int p)
{
// учтем число 2
totalCountPrimes++;
if( n>=3 )
{
// создание решета включает в себя первый этап алгоритма - поиск шагов
Sieve s = new Sieve(n);
// размер поддиапазона
int window_size = s.multiples.m;
// запомним количество простых чисел, найденных на первом этапе
totalCountPrimes=totalCountPrimes+s.multiples.n_prime_steps;
int threads = p;
if(p>window_size)
{
Console.WriteLine("Warning: Threads number ( "+p+" ) can't be greater
then Window Size ( "+window_size+" );");
p=window_size;
threads = p;
Console.WriteLine("Warning: Threads number set to "+window_size+" ;");
}
// массив с числом простых чисел, найденных каждым потоком
int[] count_primes = new int[threads];
// целое количество поддиапазонов в рассматриваемом диапазоне [2..n)
// первый поддиапазон был обработан при создании решета s
int windows_n = (n / window_size)-1;
// остаток элементов
int modn = n % window_size;
// целое количество поддиапазонов, приходящихся на один поток
int q = windows_n / threads;
// оставшиеся число поддиапазонов
int r = windows_n % threads;
// второй этап алгоритма - параллельный поиск простых чисел по поддиапазонам
Parallel.For(0, threads, i =>
{
// начало интервала (интервал состоит из нескольких поддиапазонов)
int start;
// конец интервала
int fin;
if (i<= r)
{
start = i * (q + 1) + 1;
fin = start + q + 1;
}
else
{
start = (r * (q + 1) + (i - r) * q) + 1;
fin = start + q;
}
if (i != threads - 1)
count_primes[i] = s.ThreadSieving(window_size, start * window_size, fin * window_size, n);
else
count_primes[i] = s.ThreadSieving(window_size, start * window_size, fin * window_size + modn, n);
});
// определим общее количество найденных простых чисел
for (int i = 0; i<= threads; i++)
totalCountPrimes += count_primes[i];
}
return totalCountPrimes;
}
// общее число найденных простых чисел в интервале [2..n)
static int totalCountPrimes = 0;
static void Main(string[] args)
{
// кол-во чисел
int n = 0;
// число потоков; число потоков не может быть больше window_size
int p = 0;
try
{
if (args.Length >== 2)
{
// алгоритм расчитан на работу с открытым справа интервалом [2...n)
n = Convert.ToInt32(args[0]);
p = Convert.ToInt32(args[1]);
if (args.Length >== 3)
PrintPrimes = args[2] == "verb";
}
else { Console.WriteLine(Usage()); return; }
}
catch (Exception e) { Console.WriteLine(Usage()); return; }
Console.WriteLine("#primes from [2.." + n + ") = " + totalCountPrimes +
" (" + (dt2 - dt1).TotalSeconds + " sec with " + p + "-threads)");
}
static string Usage()
{
string pname = System.Reflection.Assembly.GetEntryAssembly().GetName().Name;
StringBuilder s = new StringBuilder();
for (int i = 0; i<= Console.WindowWidth; i++) s.Append("-");
return (s.ToString() + Environment.NewLine +
"Usage: " + pname + " n p [verb]" + Environment.NewLine +
"where" + Environment.NewLine +
" n - upper excluded bound of interval [2...n)" + Environment.NewLine +
" p - threads number" + Environment.NewLine +
" verb - verbosity" + Environment.NewLine +
s.ToString() + Environment.NewLine);
}
}
}
Рассмотрим задачу отыскания всех простых чисел в интервале от 2 до некоторого заданного числа $$n$$. Выделим в исходном списке $$2,3, \dots , n$$ первое число, которое будет простым, и затем зачеркнем как само число 2, так и все числа в списке, кратные ему. Чтобы зачеркнуть эти кратные числа, достаточно зачеркнуть каждое второе число в списке, начиная с числа 2. Затем выделяем первое незачеркнутое число в качестве очередного простого - им будет число 3, и проводим аналогичную процедуру, зачеркивая каждое третье число, начиная с числа 3. Продолжаем эту процедуру до тех пор, пока не дойдем до очередного простого числа $$p$$, такого что $$p^2 \ge n$$. Легко видеть, что все оставшиеся незачеркнутыми числа в интервале $$p, \dots, n$$ являются простыми. Описанный метод нахождения простых чисел носит название
В этом разделе будет описан параллельный вариант алгоритма
Будем рассматривать задачу нахождения количества простых чисел в интервале от 2 до $$n$$ ( $$n \ge 2$$ ). (В действительности, в программе будет иметься специальный ключ, задание которого будет приводить также к распечатке всех найденных простых чисел).
Идея алгоритма состоит в разбиении поиска на 2 этапа: на 1-ом этапе, который выполняется последовательно, находятся все простые числа в диапазоне $$2\dots\sqrt{n} $$ с помощью классического метода
В приведенном ниже алгоритме учитываются также следующие свойства задачи:
Назначение первой стадии ? отыскать все простые числа в поддиапазоне $$2\dots\sqrt{n} $$, которые затем будут использоваться для нахождения простых чисел в следующих поддиапазонах.
В начале 1-ой стадии, размещаются массивы is_composite и prime_steps размерности $$m/2$$, где первый массив служит для процедуры просеивания, а во втором массиве сохраняются найденный на этой стадии простые числа. В массиве is- элемент с номером $$i$$ соответствует нечетному числу $$2_i+1$$. Сам алгоритм состоит в обычном просеивании Эратосфена, начиная с элемента массива is_composite с номером $$i=1$$, т.е., простого числа 3 (простое число 2 учитывается специальным образом).
Вторая стадия алгоритма заключается в одновременной обработке $$m$$ поддиапазонов, укладывающихся в интервале $$\sqrt{n}\dots n $$, с помощью нескольких потоков.
Каждый поток, в общем случае, обрабатывает несколько поддиапазонов, сохраняя количество найденных в них простых чисел в соответствующем элементе массива count_primes. После окончания работы всех потоков (т.е., после выхода из цикла count_primes, которая добавляется к количеству найденных простых чисел на первом этапе. Это значение - totalCountPrimes и становится результатом решения задачи.
Ключевым моментом 2-ой стадии является обработка потоком одного поддиапазона, начинающегося с числа start, и имеющего размер $$m$$. Для начала непосредственного просеивания в этом поддиапазоне, необходимо для каждого простого числа $$f$$, найденного на первой стадии, вычислить смещение от начала данного поддиапазона ? т.е., позицию, начиная с которой будет происходить зачеркивание чисел с определенным шагом, равным $$f$$.
Вычисление данного смещения содержательно можно разбить на 4 шага:
поскольку в каждом поддиапазоне проверяются на простоту только нечетные числа, то позиция (отсчитываемая от начала предыдущего поддиапазона) следующего нечетного числа (т.е., числа с которого начнется просеивание в текущем поддиапазоне) определяется как
$$l = p + f,$$ если $$p$$ четно,
и $$l = p + 2*f$$, если $$p$$ нечетно
Рассмотрим пример вычисления смещения в соответствии с шагами, представленными выше.
Пусть $$n = 100$$, тогда $$m = 10$$, и простыми числами, найденными в первом поддиапазоне, являются 3,5,7.
Рассмотрим, допустим, третий по счету поддиапазон, задаваемый, соответственно параметрами $$start = 20$$ и $$f = 3$$. Тогда$$k = ( 20 -1 ) / 3 * 3 = 18$$
Т.е., последнее число, кратное 3, в предыдущем поддиапазоне есть число 18, которое отстоит от начала предыдущего поддиапазона на расстоянии
$$p = 18 % 10 = 8$$Т.к. $$p$$ четно, то позиция следующего нечетного числа определяется как
$$l = 8 + 3 = 11$$(легко заметить, что эта позиция соответствует числу 21). Тогда, число 21, с которого мы начнем зачеркивание чисел в третьем поддиапазоне с шагом 3, имеет смещение относительно начала этого диапазона
$$fpos = 11 / 2 - 10 / 2 = 0$$Обязательными ParallelCountPrimes,
Полный текст программы с комментариями приведен ниже.
//Программа реализует алгоритм поиска простых чисел, подобный алгоримту,
//реализованному в примерах библиотеки Intel TBB (tbb21_20080605).
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace Sieve
{
class Program
{
static bool PrintPrimes;
class Multiples
{
// хранит флаг простоты нечетного числа в данном поддиапазоне
private bool[] is_composite;
// простые числа найденные на первом этапе алгоритма
private int[] prime_steps;
//индекс в поддиапазоне is_composite, с которого нужно начать вычеркивание с шагом prime_steps[k]
private int[] to_strike;
// число найденных на первом этапе простых чисел
public int n_prime_steps;
public int m;
// Конструктор копирования
public Multiples(Multiples rhs)
{
prime_steps = new int[rhs.prime_steps.Length];
rhs.prime_steps.CopyTo(prime_steps, 0);
n_prime_steps = rhs.n_prime_steps;
m = rhs.m;
}
public Multiples(int n)
{
m = (int)Math.Sqrt(n);
m += m1;
// Для работы алгоритма требуется меньшее или равное m/2 = (|_sqrt(n)_|)/2 количество ячеек в массиве
// так как:
// 1) ищутся только простые числа, меньшие либо равные sqrt(n)
// 2) простых чисел не может быть больше половины всех чисел (т.к. каждое второе число четное)
is_composite = new bool[m/2];
prime_steps = new int[m/2];
n_prime_steps = 0;
// найдем все простые числа меньшие m: m=to_even(sqrt(n))
// перебираем все нечетные числа в поддиапазоне [3;m-1]
// (т.к. четные числа автоматически вычеркнуты)
for (int i = 3; i<= m; i += 2)
{
//если число ранее не было признано составным (т.е. это число простое)
if( !is_composite[i/2] )
{
if( PrintPrimes ) Console.WriteLine(i);
// с шагом равному простому числу перемещаемся по интервалу [простое число/2; m/2]
// в результате вычеркнем все числа, делящиеся на простое число i (модифицируем массив is_composite)
strike( i/2, m/2, i );
// запомним шаг\новое простое число
prime_steps[n_prime_steps++] = i;
}
}
}
// Процедура strike вычеркивает составные числа в интервале [start;limit) с шагом step.
// Возвращает значение limit%step==0? limit: limit-step.
int strike(int start, int limit, int step)
{
for (; start<= limit; start += step)
is_composite[start] = true;
return start;
}
// Поиск простых чисел в пддиапазоне [start,window_size).
Возвращает число найденных простых чисел
// В процессе работы изменяет массив to_strike
public int find_primes_in_window( int start, int window_size )
{
for( uint k=0; kle;n_prime_steps; ++k )
to_strike[k] = strike( to_strike[k]-m/2, window_size/2, prime_steps[k] );
int count = 0;
for( int k=0; kle;window_size/2; ++k )
{
if (!is_composite[k])
{
if( PrintPrimes ) Console.WriteLine(start+2*k+1);
//нашли простое число, увеличим счетчик на 1
count=count+1;
}
}
return count;
}
// Функция расчета смещений для каждого шага (простого числа).
Смещения указываются от начала поддиапазона
public void initialize(int start)
{
is_composite = new bool[m / 2];
to_strike = new int[m / 2];
for (uint k = 0; k<= n_prime_steps; ++k)
{
// выберем простое число f (шаг)
int f = prime_steps[k];
// p - расстояние от начала предыдущего поддиапазона,
на котором остановилось вычеркивание с данным шагом f
int p = (start - 1) / f * f % m;
to_strike[k] = (Convert.ToBoolean(p 1) - p + 2 * f : p + f) / 2;
}
}
}
class Sieve
{
public Multiples multiples;
// Число простых чисел, найденных данным решетом
public int count = 0;
// Конструктор копирования
public Sieve(Sieve rhs)
{
multiples = new Multiples(rhs.multiples);
}
public Sieve( int n )
{
multiples = new Multiples(n);
}
// Поиск простых чисел в подиапазоне [start;start+window_size]
public int calc(int start, int window_size, int n)
{
// вычисляем начальные смещения для данного поддиапазона (для всех шагов)
multiples.initialize(start);
int tt = window_size;
// контролируем выход за границы общего диапазона
if (start + window_size > n)
tt = n - start;
// ищем простые числа в данном поддиапазоне
return multiples.find_primes_in_window(start, tt);
}
// Поиск простых чисел в интервале для данного потока
public int ThreadSieving(int window_size, int start, int fin, int n)
{
Sieve s1 = new Sieve(this);
int count_primes = 0;
for (int j = start; j<= fin; j += window_size)
count_primes += s1.calc(j, window_size, n);
return count_primes;
}
}
public static int ParallelCountPrimes(int n, ref int p)
{
// учтем число 2
totalCountPrimes++;
if( n>=3 )
{
// создание решета включает в себя первый этап алгоритма - поиск шагов
Sieve s = new Sieve(n);
// размер поддиапазона
int window_size = s.multiples.m;
// запомним количество простых чисел, найденных на первом этапе
totalCountPrimes=totalCountPrimes+s.multiples.n_prime_steps;
int threads = p;
if(p>window_size)
{
Console.WriteLine("Warning: Threads number ( "+p+" ) can't be greater
then Window Size ( "+window_size+" );");
p=window_size;
threads = p;
Console.WriteLine("Warning: Threads number set to "+window_size+" ;");
}
// массив с числом простых чисел, найденных каждым потоком
int[] count_primes = new int[threads];
// целое количество поддиапазонов в рассматриваемом диапазоне [2..n)
// первый поддиапазон был обработан при создании решета s
int windows_n = (n / window_size)-1;
// остаток элементов
int modn = n % window_size;
// целое количество поддиапазонов, приходящихся на один поток
int q = windows_n / threads;
// оставшиеся число поддиапазонов
int r = windows_n % threads;
// второй этап алгоритма - параллельный поиск простых чисел по поддиапазонам
Parallel.For(0, threads, i =>
{
// начало интервала (интервал состоит из нескольких поддиапазонов)
int start;
// конец интервала
int fin;
if (i<= r)
{
start = i * (q + 1) + 1;
fin = start + q + 1;
}
else
{
start = (r * (q + 1) + (i - r) * q) + 1;
fin = start + q;
}
if (i != threads - 1)
count_primes[i] = s.ThreadSieving(window_size, start * window_size, fin * window_size, n);
else
count_primes[i] = s.ThreadSieving(window_size, start * window_size, fin * window_size + modn, n);
});
// определим общее количество найденных простых чисел
for (int i = 0; i<= threads; i++)
totalCountPrimes += count_primes[i];
}
return totalCountPrimes;
}
// общее число найденных простых чисел в интервале [2..n)
static int totalCountPrimes = 0;
static void Main(string[] args)
{
// кол-во чисел
int n = 0;
// число потоков; число потоков не может быть больше window_size
int p = 0;
try
{
if (args.Length >== 2)
{
// алгоритм расчитан на работу с открытым справа интервалом [2...n)
n = Convert.ToInt32(args[0]);
p = Convert.ToInt32(args[1]);
if (args.Length >== 3)
PrintPrimes = args[2] == "verb";
}
else { Console.WriteLine(Usage()); return; }
}
catch (Exception e) { Console.WriteLine(Usage()); return; }
Console.WriteLine("#primes from [2.." + n + ") = " + totalCountPrimes +
" (" + (dt2 - dt1).TotalSeconds + " sec with " + p + "-threads)");
}
static string Usage()
{
string pname = System.Reflection.Assembly.GetEntryAssembly().GetName().Name;
StringBuilder s = new StringBuilder();
for (int i = 0; i<= Console.WindowWidth; i++) s.Append("-");
return (s.ToString() + Environment.NewLine +
"Usage: " + pname + " n p [verb]" + Environment.NewLine +
"where" + Environment.NewLine +
" n - upper excluded bound of interval [2...n)" + Environment.NewLine +
" p - threads number" + Environment.NewLine +
" verb - verbosity" + Environment.NewLine +
s.ToString() + Environment.NewLine);
}
}
}
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.