Skip to content

Commit bc8f107

Browse files
committed
Use Melissa E. O'Neill's pcg for rng and Lemire's unbiased bounded rand
Mersenne Twister's state size is huge (kb's of state), instead we'll swap with a pcg. The pcg's state is 128 bits and has a equidistributed 2^64 period (plenty for a quick rng). To replicate std::distribution we use lemire's unbiased bounded random method.
1 parent 7128fc0 commit bc8f107

2 files changed

Lines changed: 52 additions & 13 deletions

File tree

src/displayapp/screens/Dice.cpp

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,31 @@
55
#include "components/motor/MotorController.h"
66
#include "components/motion/MotionController.h"
77

8+
namespace PCG {
9+
uint32_t pcg32_random_r(pcg32_random_t* rng) {
10+
uint64_t oldstate = rng->state;
11+
// Advance internal state
12+
rng->state = oldstate * 6364136223846793005ULL + (rng->inc | 1);
13+
// Calculate output function (XSH RR), uses old state for max ILP
14+
uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
15+
uint32_t rot = oldstate >> 59u;
16+
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
17+
}
18+
19+
// Lemire's Method (slight rewrite) [0, range)
20+
uint32_t bounded_rand(pcg32_random_t& rng, uint32_t range) {
21+
uint64_t m;
22+
uint32_t t = (-range) % range;
23+
uint32_t l;
24+
do {
25+
uint32_t x = pcg32_random_r(&rng);
26+
m = uint64_t(x) * uint64_t(range);
27+
l = uint32_t(m);
28+
} while (l < t);
29+
return m >> 32;
30+
}
31+
};
32+
833
using namespace Pinetime::Applications::Screens;
934

1035
namespace {
@@ -43,11 +68,12 @@ Dice::Dice(Controllers::MotionController& motionController,
4368
Controllers::MotorController& motorController,
4469
Controllers::Settings& settingsController)
4570
: motorController {motorController}, motionController {motionController}, settingsController {settingsController} {
46-
std::seed_seq sseq {static_cast<uint32_t>(xTaskGetTickCount()),
47-
static_cast<uint32_t>(motionController.X()),
48-
static_cast<uint32_t>(motionController.Y()),
49-
static_cast<uint32_t>(motionController.Z())};
50-
gen.seed(sseq);
71+
rng.state = (static_cast<uint64_t>(xTaskGetTickCount()) << 32) ^ (static_cast<uint64_t>(motionController.NbSteps()) << 16) ^ (uint64_t) &rng;
72+
rng.inc = (static_cast<uint64_t>(motionController.X()) << 32) ^ (static_cast<uint64_t>(motionController.Y()) << 16) ^
73+
static_cast<uint64_t>(motionController.Z());
74+
rng.inc ^= (uint64_t) PCG::pcg32_random_r(&rng);
75+
rng.inc ^= (uint64_t) PCG::pcg32_random_r(&rng) << 32;
76+
rng.state = (uint64_t) PCG::pcg32_random_r(&rng) << 32 ^ PCG::pcg32_random_r(&rng);
5177

5278
lv_obj_t* nCounterLabel = MakeLabel(&jetbrains_mono_bold_20,
5379
LV_COLOR_WHITE,
@@ -79,8 +105,7 @@ Dice::Dice(Controllers::MotionController& motionController,
79105
lv_obj_align(dCounter.GetObject(), dCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
80106
dCounter.SetValue(6);
81107

82-
std::uniform_int_distribution<> distrib(0, resultColors.size() - 1);
83-
currentColorIndex = distrib(gen);
108+
currentColorIndex = PCG::bounded_rand(rng, resultColors.size());
84109

85110
resultTotalLabel = MakeLabel(&jetbrains_mono_42,
86111
resultColors[currentColorIndex],
@@ -157,12 +182,10 @@ void Dice::Refresh() {
157182
void Dice::Roll() {
158183
uint8_t resultIndividual;
159184
uint16_t resultTotal = 0;
160-
std::uniform_int_distribution<> distrib(1, dCounter.GetValue());
161-
162185
lv_label_set_text(resultIndividualLabel, "");
163186

164187
if (nCounter.GetValue() == 1) {
165-
resultTotal = distrib(gen);
188+
resultTotal = PCG::bounded_rand(rng, dCounter.GetValue()) + 1;
166189
if (dCounter.GetValue() == 2) {
167190
switch (resultTotal) {
168191
case 1:
@@ -175,7 +198,7 @@ void Dice::Roll() {
175198
}
176199
} else {
177200
for (uint8_t i = 0; i < nCounter.GetValue(); i++) {
178-
resultIndividual = distrib(gen);
201+
resultIndividual = PCG::bounded_rand(rng, dCounter.GetValue()) + 1;
179202
resultTotal += resultIndividual;
180203
lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, std::to_string(resultIndividual).c_str());
181204
if (i < (nCounter.GetValue() - 1)) {

src/displayapp/screens/Dice.h

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,23 @@
77
#include "Symbols.h"
88

99
#include <array>
10-
#include <random>
10+
#include <cstdint>
11+
12+
namespace PCG {
13+
// *Really* minimal PCG32 code / (c) 2014 M.E. O'Neill / pcg-random.org
14+
// Licensed under Apache License 2.0 (NO WARRANTY, etc. see website)
15+
// Website: https://www.pcg-random.org/download.html
16+
// See: https://www.apache.org/licenses/GPL-compatibility.html
17+
typedef struct {
18+
uint64_t state;
19+
uint64_t inc;
20+
} pcg32_random_t;
21+
22+
uint32_t pcg32_random_r(pcg32_random_t* rng);
23+
24+
// Lemire's Method (slight rewrite) [0, range)
25+
uint32_t bounded_rand(pcg32_random_t& rng, uint32_t range);
26+
};
1127

1228
namespace Pinetime {
1329
namespace Applications {
@@ -29,7 +45,7 @@ namespace Pinetime {
2945
lv_task_t* refreshTask;
3046
bool enableShakeForDice = false;
3147

32-
std::mt19937 gen;
48+
PCG::pcg32_random_t rng;
3349

3450
std::array<lv_color_t, 3> resultColors = {LV_COLOR_YELLOW, LV_COLOR_MAGENTA, LV_COLOR_AQUA};
3551
uint8_t currentColorIndex;

0 commit comments

Comments
 (0)