背景
M5stackCore2でランダムな配列が欲しかったため、std:: shuffleを使おうとした
その結果がシャッフルされていない。
9; 6; 3; 1; 2; 5; 7; 10; 8; 4;
4; 2; 3; 10; 5; 6; 7; 9; 1; 8;
8; 5; 3; 9; 6; 2; 7; 4; 10; 1;
1; 6; 3; 4; 2; 5; 7; 8; 9; 10;
10; 2; 3; 8; 5; 6; 7; 1; 4; 9;
9; 5; 3; 1; 6; 2; 7; 10; 8; 4; //ここからほぼ同じ結果
4; 6; 3; 10; 2; 5; 7; 9; 1; 8;
8; 2; 3; 9; 5; 6; 7; 4; 10; 1;
1; 5; 3; 4; 6; 2; 7; 8; 9; 10;
10; 6; 3; 8; 2; 5; 7; 1; 4; 9;
#include <M5Unified.h>
#include <algorithm>
#include <iostream>
#include <random>
template <typename T>
void printVector(std::vector<T> v) {
for (const auto &item : v) {
std::cout << item << "; ";
}
std::cout << std::endl;
}
std::vector<int> numList={1,2,3,4,5,6,7,8,9,10};
void setup(void) {
auto cfg = M5.config();
M5.begin(cfg);
Serial.begin(9600);
}
void loop(void) {
M5.update();
std::random_device seed_gen;
std::mt19937 engine(seed_gen());
std::shuffle(numList.begin(), numList.end(), engine);
printVector(numList);
}
開発環境
- Windows11 + platformIO
- M5Stack Core2
解説
ESP32 random_deviceでググったところ以下の記事が出てきた。こちらの記事を参考に解説していく。
cppreferenceのrandom_deviceの解説によると、
std::random_device may be implemented in terms of an implementation-defined pseudo-random number engine if a non-deterministic source (e.g. a hardware device) is not available to the implementation. In this case each std::random_device object may generate the same number sequence.
random_deviceは非決定的なソースが利用できない場合に疑似乱数で実装されるため、同じ数列を生成する。非決定的なソースとは、主にPCの温度など。
疑似乱数を使っている場合はエントロピーが0になるので、試しに、マイコンなどでも非決定的なソースは存在するが、マイコンで使用されることは想定されてないようだ。
Serial.println(seed_gen.entropy());
でエントロピーを表示すると0.0
になっていた。
Serial.println(seed_gen());
してランダムシードの値をプリントすると、
3499211612
3499211612
3499211612
3499211612
3499211612
....
ずっと同じ数列が生成されている。これでは確かにランダムにはならない。
では、どうするかというと、ESP32にはハードウェア乱数を生成するAPIesp_random()
があるため、こちらを使ってくださいとのこと。WifiやBluetoothのノイズや、SoCs上のノイズをADCで読みだしたものなどを使って乱数が生成される。
#include <M5Unified.h>
#include <algorithm>
#include <iostream>
#include <random>
template <typename T>
void printVector(std::vector<T> v) {
for (const auto &item : v) {
std::cout << item << "; ";
}
std::cout << std::endl;
}
std::vector<int> numList={1,2,3,4,5,6,7,8,9,10};
void setup(void) {
auto cfg = M5.config();
M5.begin(cfg);
Serial.begin(9600);
}
void loop(void) {
M5.update();
std::mt19937 engine(esp_random());
std::shuffle(numList.begin(), numList.end(), engine);
printVector(numList);
}
10; 9; 2; 8; 6; 7; 3; 4; 5; 1;
3; 4; 10; 8; 5; 6; 1; 7; 2; 9;
8; 6; 7; 4; 10; 1; 3; 2; 5; 9;
8; 10; 7; 3; 1; 4; 2; 6; 9; 5;
10; 8; 2; 4; 6; 7; 1; 9; 3; 5;
3; 8; 2; 4; 9; 10; 7; 5; 1; 6;
8; 1; 2; 6; 7; 3; 5; 4; 9; 10;
10; 3; 4; 9; 2; 6; 1; 5; 7; 8;
6; 2; 10; 9; 7; 4; 8; 5; 1; 3;
1; 9; 5; 7; 8; 3; 2; 4; 6; 10;
2; 1; 4; 3; 8; 7; 5; 6; 9; 10;
4; 5; 1; 6; 10; 7; 9; 3; 2; 8;
5; 10; 6; 9; 4; 1; 3; 7; 2; 8;
3; 5; 4; 10; 7; 6; 9; 2; 1; 8;
5; 3; 7; 4; 10; 2; 8; 6; 9; 1;
結論
シード値としてstd::random_device
ではなくesp_random()
を使おう
コメント追記
最新のESP32の開発環境ではハードウェアランダムに対応しているのではないか?というコメントをいただきました
色々調査してみたところ、M5stackCore2の開発環境では、randomの内蔵されているパッケージのバージョンがtoolchain-xtensa-esp32 @ 8.4.0+2021r2-patch5となっており、
試しに、貼っていただいたリンクのバージョンに近いtoolchain-xtensa-esp32@12.2.0+20230208を入れてみましたが、コンパイルエラーで動かずでした。