Русская Википедия:Линейный конгруэнтный метод

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Линейный конгруэнтный метод — один из методов генерации псевдослучайных чисел. Применяется в простых случаях и не обладает криптографической стойкостью. Входит в стандартные библиотеки различных компиляторов.

Описание

Линейный конгруэнтный метод был предложен Д. Г. Лемером в 1949 году.[1] Суть метода заключается в вычислении последовательности случайных чисел <math>X_{n}</math>, полагая

<math>X_{n+1} = (a X_n + c)~~\bmod~~m,</math>

где <math>m</math> — модуль (натуральное число, относительно которого вычисляет остаток от деления; <math>m\geqslant 2</math>), <math>a</math> — множитель (<math>0 \leqslant a < m</math>), <math>c</math> — приращение (<math>0 \leqslant c < m</math>), <math>X_{0}</math> — начальное значение (<math>0 \leqslant X_{0} < m</math>).

Эта последовательность называется линейной конгруэнтной последовательностью. Например, для <math>m = 10, X_{0} = a = c = 7</math> получим последовательность <math>7, 6, 9, 0, 7, 6, 9, 0, \dots</math>[2]

Свойства

Линейная конгруэнтная последовательность, определенная числами <math>m</math>, <math>a</math>, <math>c</math> и <math>X_{0}</math> периодична с периодом, не превышающим <math>m</math>. При этом длина периода равна <math>m</math> тогда и только тогда, когда[3]:

  1. Числа <math>c</math> и <math>m</math> взаимно простые;
  2. <math>b = a - 1</math> кратно <math>p</math> для каждого простого <math>p</math>, являющегося делителем <math>m</math>;
  3. <math>b</math> кратно <math>4</math>, если <math>m</math> кратно <math>4</math>.

Наличие этого свойства для случая <math>m = 2^e</math>, где <math>e</math> — число битов в машинном слове, было доказано М. Гринбергом (Шаблон:Lang-en).[4] Наличие этого свойства для общего случая и достаточность условий были доказаны Т. Е. Халлом (Шаблон:Lang-en) и А. Р. Добеллом (Шаблон:Lang-en).[5]

Метод генерации линейной конгруэнтной последовательности при <math>c = 0</math> называют мультипликативным конгруэнтным методом, а при <math>c \neq 0</math> — смешанным конгруэнтным методом. При <math>c = 0</math> генерируемые числа будут иметь меньший период, чем при <math>c \neq 0</math>, но при определенных условиях можно получить период длиной <math>m - 1</math>, если <math>m</math> — простое число. Тот факт, что условие <math>c \neq 0</math> может приводить к появлению более длинных периодов, был установлен В. Е. Томсоном (Шаблон:Lang-en) и независимо от него А. Ротенбергом (Шаблон:Lang-en).[2] Чтобы гарантировать максимальность цикла повторения последовательности при <math>c = 0</math>, необходимо в качестве значения параметра <math>m</math> выбирать простое число. Самым известным генератором подобного рода является так называемый минимальный стандартный генератор случайных чисел, предложенный Стивеном Парком (Шаблон:Lang-en) и Кейтом Миллером (Шаблон:Lang-en) в 1988 году. Для него <math>a = 16807</math>, а <math>m = 2147483647</math>.[6][7]

Наиболее часто практикуемым методом генерации последовательностей псевдослучайных чисел является смешанный конгруэнтный метод.Шаблон:Нет АИ

Часто используемые параметры

При выборе числа <math>m</math> необходимо учитывать следующие условия:

1) число <math>m</math> должно быть довольно большим, так как период не может иметь больше <math>m</math> элементов;

2) значение числа <math>m</math> должно быть таким, чтобы <math>( a X_{n} + c)\mod m</math> вычислялось быстро.

На практике при реализации метода исходя из указанных условий чаще всего выбирают <math>m = 2^e</math>, где <math>e</math> — число битов в машинном слове. При этом стоит учитывать, что младшие двоичные разряды сгенерированных таким образом случайных чисел демонстрируют поведение, далёкое от случайного, поэтому рекомендуется использовать только старшие разряды. Подобная ситуация не возникает, когда <math>m = w \pm 1</math>, где <math>w</math> — длина машинного слова. В таком случае младшие разряды <math>X_{n}</math> ведут себя так же случайно, как и старшие.[2] Выбор множителя <math>a</math> и приращения <math>c</math> в основном обусловлен необходимостью выполнения условия достижения периода максимальной длины.

Шаблон:Начало скрытого блока

Все приведенные константы обеспечивают работу генератора с максимальным периодом. Таблица упорядочена по максимальному произведению, которое не вызывает переполнение в слове указанной длины.[8]

Переполняется при a c m
220 106 1283 6075
221 211 1663 7875
222 421 1663 7875
223 430 2531 11979
223 936 1399 6655
223 1366 1283 6075
224 171 11213 53125
224 859 2531 11979
224 419 6173 29282
224 967 3041 14406
225 141 28411 134456
225 625 6571 31104
225 1541 2957 14000
225 1741 2731 12960
225 1291 4621 21870
225 205 29573 139968
226 421 17117 81000
226 1255 6173 29282
226 281 28411 134456
227 1093 18257 86436
227 421 54773 259200
227 1021 24631 116640
228 1277 24749 117128
228 2041 25673 121500
229 2311 25367 120050
229 1597 51749 244944
229 2661 36979 175000
229 4081 25673 121500
229 3661 30809 145800
230 3877 29573 139968
230 3613 45289 214326
230 1366 150889 714025
231 8121 28411 134456
231 4561 51349 243000
231 7141 54773 259200
232 9301 49297 233280
232 4096 150889 714025
233 2416 374441 1771875
234 17221 107839 510300
234 36261 66037 312500
235 84589 45989 217728

Шаблон:Конец скрытого блока

Печально известен «неудачный» (с точки зрения качества выходной последовательности) алгоритм RANDU, на протяжении многих десятилетий использовавшийся в самых разных компиляторах.

Для улучшения статистических свойств числовой последовательности во многих генераторах псевдослучайных чисел используется только часть битов результата. Например, в стандарте ISO/IEC 9899 на язык Си приведен (но не указан в качестве обязательного) пример функции rand(), принудительно отбрасывающей младшие 16 и один старший разряд.

#define RAND_MAX 32767 

static unsigned long int next = 1;

int rand(void)
{
  next = next * 1103515245 + 12345;
  return (unsigned int)(next/65536) % (RAND_MAX + 1);
}

void srand(unsigned int seed)
{
  next = seed;
}

Именно в таком виде функция rand() используется в компиляторах Watcom C/C++. Известны числовые параметры иных алгоритмов, применяемых в различных компиляторах и библиотеках. Шаблон:Список примеров

Source m множитель a слагаемое c используемые биты
Numerical Recipes[9] 232 1664525 1013904223
Borland C/C++ 232 22695477 1 bits 30..16 in rand(), 30..0 in lrand()
glibc (used by GCC)[10] 231 1103515245 12345 bits 30..0
ANSI C: Watcom, Digital Mars, CodeWarrior, IBM VisualAge C/C++[11] 231 1103515245 12345 bits 30..16
C99, C11: Suggestion in the ISO/IEC 9899[12] 232 1103515245 12345 bits 30..16
Borland Delphi, Virtual Pascal 232 134775813 1 bits 63..32 of (seed * L)
Microsoft Visual/Quick C/C++ 232 214013 (343FD16) 2531011 (269EC316) bits 30..16
Microsoft Visual Basic (6 and earlier)[13] 224 1140671485 (43FD43FD16) 12820163 (C39EC316)
RtlUniform from Native API[14] 231 − 1 2147483629 (7FFFFFED16) 2147483587 (7FFFFFC316)
Apple CarbonLib, C++11's minstd_rand0[15] 231 − 1 16807 0 see MINSTD
C++11's minstd_rand[15] 231 − 1 48271 0 see MINSTD
MMIX by Donald Knuth 264 6364136223846793005 1442695040888963407
Newlib 264 6364136223846793005 1 bits 63…32
VAX's MTH$RANDOM,[16] old versions of glibc 232 69069 1
Java 248 25214903917 11 bits 47…16
Ранее во многих компиляторах:
RANDU 231   65539 0

Возможность использования в криптографии

Хотя линейный конгруэнтный метод порождает статистически хорошую псевдослучайную последовательность чисел, он не является криптографически стойким. Генераторы на основе линейного конгруэнтного метода являются предсказуемыми, поэтому их нельзя использовать в криптографии. Впервые генераторы на основе линейного конгруэнтного метода были взломаны Джимом Ридсом (Jim Reeds), а затем Джоан Бойяр. Ей удалось также вскрыть квадратические и кубические генераторы. Другие исследователи расширили идеи Бояр, разработав способы вскрытия любого полиномиального генератора. Таким образом, была доказана бесполезность генераторов на основе конгруэнтных методов для криптографии. Однако генераторы на основе линейного конгруэнтного метода сохраняют свою полезность для некриптографических приложений, например, для моделирования. Они эффективны и в большинстве используемых эмпирических тестов демонстрируют хорошие статистические характеристики[8].

См. также

Примечания

Шаблон:Примечания

Литература

Ссылки

  1. D. H. Lehmer, Mathematical methods in large-scale computing units, Proceedings of a Second Symposium on Large-Scale Digital Calculating Machinery, 1949, Harvard University Press, Cambridge, Mass., 1951, pp. 141—146. MR 0044899 (13,495f)[1] Шаблон:Wayback
  2. 2,0 2,1 2,2 Шаблон:Книга
  3. Кнут Д. Э., Искусство программирования. Том 2. Получисленные методы — Вильямс. 2001. с.21-37
  4. M. Greenberger, Method in randomness, Comm. ACM 8 (1965), 177—179.[2] Шаблон:Wayback
  5. T.E. Hull and A.R. Dobell «Random Number Generators»,SIAM Review 4-3(1962),230-254 [3] Шаблон:Wayback
  6. "Бакнелл Д. М. Фундаментальные алгоритмы и структуры данных в Delphi. Библиотека программиста. 2002 год. журнал Delphi Informant Magazine. Глава 6.
  7. Stephen K. Park and Keith W. Miller (1988). Random Number Generators: Good Ones Are Hard To Find. Communications of the ACM 31 (10): 1192—1201[4] Шаблон:Wayback
  8. 8,0 8,1 Шаблон:Книга[5] Шаблон:Wayback
  9. Numerical recipies in C. The art of scientific computing. 2-nd edition. — Cambridge University Press, 1992. — 925 pp.
  10. The GNU C library’s rand() in stdlib.h uses a simple (single state) linear congruential generator only in case that the state is declared as 8 bytes. If the state is larger (an array), the generator becomes an additive feedback generator and the period increases. See the simplified code Шаблон:Wayback that reproduces the random sequence from this library.
  11. Шаблон:Cite web
  12. Шаблон:Cite web
  13. Шаблон:Cite web
  14. In spite of documentation on MSDN Шаблон:Wayback, RtlUniform uses LCG, and not Lehmer’s algorithm, implementations before Windows Vista are flawed, because the result of multiplication is cut to 32 bits, before modulo is applied
  15. 15,0 15,1 Шаблон:Cite web
  16. Шаблон:Cite web