Vấn đề cơ bản nhất của ứng dụng thử nghiệm của bạn là bạn gọi srand
một lần và sau đó gọi rand
một lần và thoát.
Toàn bộ điểm của srand
hàm là khởi tạo chuỗi các số giả ngẫu nhiên với một hạt ngẫu nhiên.
Điều đó có nghĩa là nếu bạn chuyển cùng một giá trị cho srand
hai ứng dụng khác nhau (có cùng srand
/ rand
thực hiện) thì bạn sẽ nhận được chính xác cùng một chuỗi các rand()
giá trị được đọc sau đó trong cả hai ứng dụng.
Tuy nhiên, trong ví dụ ứng dụng của bạn, chuỗi giả ngẫu nhiên chỉ bao gồm một phần tử - phần tử đầu tiên của chuỗi giả ngẫu nhiên được tạo từ hạt giống với thời gian second
chính xác hiện tại . Bạn mong đợi gì để thấy trên đầu ra sau đó?
Rõ ràng khi bạn tình cờ chạy ứng dụng trong cùng một giây - bạn sử dụng cùng một giá trị hạt giống - do đó, kết quả của bạn là như nhau tất nhiên (như Martin York đã đề cập trong một nhận xét cho câu hỏi).
Trên thực tế, bạn nên gọi srand(seed)
một lần và sau đó gọi rand()
nhiều lần và phân tích chuỗi đó - nó sẽ trông ngẫu nhiên.
BIÊN TẬP:
Ồ tôi hiểu rồi Rõ ràng mô tả bằng lời nói là không đủ (có thể là rào cản ngôn ngữ hoặc một cái gì đó ... :)).
ĐỒNG Ý. Ví dụ mã C lỗi thời dựa trên cùng các srand()/rand()/time()
chức năng đã được sử dụng trong câu hỏi:
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
int main(void)
{
unsigned long j;
srand( (unsigned)time(NULL) );
for( j = 0; j < 100500; ++j )
{
int n;
/* skip rand() readings that would make n%6 non-uniformly distributed
(assuming rand() itself is uniformly distributed from 0 to RAND_MAX) */
while( ( n = rand() ) > RAND_MAX - (RAND_MAX-5)%6 )
{ /* bad value retrieved so get next one */ }
printf( "%d,\t%d\n", n, n % 6 + 1 );
}
return 0;
}
^^ ^ RATNG chuỗi từ một lần chạy chương trình được cho là trông ngẫu nhiên.
EDIT2:
Khi sử dụng thư viện chuẩn C hoặc C ++, điều quan trọng là phải hiểu rằng cho đến nay không có một hàm tiêu chuẩn hoặc lớp nào tạo ra dữ liệu thực sự ngẫu nhiên một cách dứt khoát (được đảm bảo bởi tiêu chuẩn). Công cụ tiêu chuẩn duy nhất tiếp cận vấn đề này là std :: Random_device mà không may vẫn không đảm bảo tính ngẫu nhiên thực tế.
Tùy thuộc vào bản chất của ứng dụng, trước tiên bạn nên quyết định xem bạn có thực sự cần dữ liệu thực sự ngẫu nhiên (không thể đoán trước) hay không. Trường hợp đáng chú ý khi bạn chắc chắn cần sự ngẫu nhiên thực sự là bảo mật thông tin - ví dụ: tạo khóa đối xứng, khóa riêng không đối xứng, giá trị muối, mã thông báo bảo mật, v.v.
Tuy nhiên, số ngẫu nhiên cấp bảo mật là một ngành riêng biệt có giá trị một bài viết riêng.
Trong hầu hết các trường hợp, Trình tạo số giả ngẫu nhiên là đủ - ví dụ: cho các mô phỏng hoặc trò chơi khoa học. Trong một số trường hợp, chuỗi giả ngẫu nhiên được xác định nhất quán thậm chí còn được yêu cầu - ví dụ: trong các trò chơi, bạn có thể chọn tạo chính xác các bản đồ trong thời gian chạy để tránh lưu trữ nhiều dữ liệu.
Câu hỏi ban đầu và lặp lại vô số câu hỏi giống hệt / tương tự (và thậm chí nhiều câu trả lời sai cho họ) chỉ ra rằng trước hết, điều quan trọng là phải phân biệt các số ngẫu nhiên với các số giả ngẫu nhiên VÀ để hiểu chuỗi số giả ngẫu nhiên trong là gì? vị trí đầu tiên VÀ để nhận ra rằng các trình tạo số giả ngẫu nhiên KHÔNG được sử dụng giống như cách bạn có thể sử dụng các trình tạo số ngẫu nhiên thực sự.
Theo trực giác khi bạn yêu cầu số ngẫu nhiên - kết quả trả về không nên phụ thuộc vào các giá trị được trả lại trước đó và không nên phụ thuộc nếu có ai yêu cầu bất cứ điều gì trước đó và không nên phụ thuộc vào thời điểm nào và theo quy trình nào và trên máy tính nào và từ máy phát điện nào và trong thiên hà nào nó được yêu cầu. Rốt cuộc, đó là từ "ngẫu nhiên" nghĩa là gì - không thể đoán trước và độc lập với bất cứ điều gì - nếu không nó không còn là ngẫu nhiên nữa, phải không? Với trực giác này, việc tìm kiếm trên web một số phép thuật để sử dụng để có được số ngẫu nhiên như vậy trong bất kỳ bối cảnh có thể là điều tự nhiên.
^^^ RẰNG loại mong đợi trực quan là rất sai lầm và có hại trong mọi trường hợp liên quan đến Máy phát điện Pseudo-Random Number - mặc dù là hợp lý cho số ngẫu nhiên đúng.
Trong khi khái niệm có ý nghĩa về "số ngẫu nhiên" tồn tại - không có thứ gọi là "số giả ngẫu nhiên". Trình tạo số giả ngẫu nhiên thực sự tạo ra chuỗi số giả ngẫu nhiên .
Khi các chuyên gia nói về chất lượng của PRNG, họ thực sự nói về các thuộc tính thống kê của chuỗi được tạo (và các chuỗi con đáng chú ý của nó). Ví dụ: nếu bạn kết hợp hai PRNG chất lượng cao bằng cách sử dụng cả hai lần lượt - bạn có thể tạo ra chuỗi kết quả xấu - mặc dù chúng tạo ra các chuỗi tốt riêng biệt (hai chuỗi tốt đó có thể chỉ tương quan với nhau và do đó kết hợp xấu).
Trình tự giả ngẫu nhiên trong thực tế luôn luôn xác định (được xác định trước bởi thuật toán và các tham số ban đầu của nó) tức là thực sự không có gì ngẫu nhiên về nó.
Cụ thể rand()
/ srand(s)
cặp hàm cung cấp một chuỗi số giả ngẫu nhiên trên mỗi quy trình không an toàn (!) Được tạo bằng thuật toán xác định thực hiện. Hàm rand()
tạo các giá trị trong phạm vi [0, RAND_MAX]
.
Trích dẫn từ tiêu chuẩn C11:
Các srand
chức năng sử dụng lập luận như một hạt giống cho một chuỗi mới của số giả ngẫu nhiên để được trả lại bởi các cuộc gọi tiếp theo để rand
. Nếu
srand
sau đó được gọi với cùng giá trị hạt giống, chuỗi số giả ngẫu nhiên sẽ được lặp lại. Nếu rand
được gọi trước bất kỳ lệnh gọi nào srand
được thực hiện, trình tự tương tự sẽ được tạo như khi srand
lần đầu tiên được gọi với giá trị seed là 1.
Nhiều người kỳ vọng một cách hợp lý rằng rand()
sẽ tạo ra một chuỗi các số phân bố đồng nhất bán độc lập trong phạm vi 0
tới RAND_MAX
. Chà, chắc chắn là nó (nếu không nó vô dụng) nhưng thật không may, không chỉ tiêu chuẩn không yêu cầu điều đó - thậm chí còn có tuyên bố từ chối rõ ràng rằng "không có gì đảm bảo về chất lượng của chuỗi ngẫu nhiên được tạo ra" . Trong một số trường hợp lịch sử rand
/ srand
thực hiện có chất lượng rất xấu thực sự. Mặc dù trong các triển khai hiện đại rất có thể là đủ tốt - nhưng niềm tin đã bị phá vỡ và không dễ phục hồi. Bên cạnh tính chất không an toàn của luồng làm cho việc sử dụng an toàn trong các ứng dụng đa luồng trở nên khó khăn và hạn chế (vẫn có thể - bạn chỉ có thể sử dụng chúng từ một luồng chuyên dụng).
Mẫu lớp mới std :: mersenne_twister_engine <> (và typedefs tiện lợi của nó - std::mt19937
/ std::mt19937_64
với sự kết hợp tham số mẫu tốt) cung cấp trình tạo số giả ngẫu nhiên cho mỗi đối tượng được xác định trong tiêu chuẩn C ++ 11. Với cùng các tham số mẫu và cùng tham số khởi tạo, các đối tượng khác nhau sẽ tạo ra chính xác trình tự đầu ra cho mỗi đối tượng trên bất kỳ máy tính nào trong bất kỳ ứng dụng nào được xây dựng với thư viện chuẩn tuân thủ C ++ 11. Ưu điểm của lớp này là trình tự đầu ra chất lượng cao có thể dự đoán được và tính nhất quán đầy đủ trong các lần triển khai.
Ngoài ra, có nhiều công cụ PRNG được định nghĩa trong tiêu chuẩn C ++ 11 - std :: linear_congruential_engine <> (trước đây được sử dụng làm srand/rand
thuật toán chất lượng công bằng trong một số triển khai thư viện tiêu chuẩn C) và std :: subtract_with_carry_engine <> . Chúng cũng tạo ra các chuỗi đầu ra cho mỗi đối tượng phụ thuộc tham số được xác định đầy đủ.
Ví dụ hiện đại C ++ 11 thay thế cho mã C lỗi thời ở trên:
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
// seed value is designed specifically to make initialization
// parameters of std::mt19937 (instance of std::mersenne_twister_engine<>)
// different across executions of application
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
for( unsigned long j = 0; j < 100500; ++j )
/* ^^^Yes. Generating single pseudo-random number makes no sense
even if you use std::mersenne_twister_engine instead of rand()
and even when your seed quality is much better than time(NULL) */
{
std::mt19937::result_type n;
// reject readings that would make n%6 non-uniformly distributed
while( ( n = gen() ) > std::mt19937::max() -
( std::mt19937::max() - 5 )%6 )
{ /* bad value retrieved so get next one */ }
std::cout << n << '\t' << n % 6 + 1 << '\n';
}
return 0;
}
Phiên bản của mã trước đó sử dụng std :: thống_int_distribution <>
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
std::uniform_int_distribution<unsigned> distrib(1, 6);
for( unsigned long j = 0; j < 100500; ++j )
{
std::cout << distrib(gen) << ' ';
}
std::cout << '\n';
return 0;
}