FastLED-masterにある8bit乱数関数
※前記事
・2021年1月30日:8bit乱数関数 random8()
・2021年1月28日:Arduinoの乱数関数「random」に違和感
カラーLEDテープの制御に絡んで出会った
「FastLED-master\src\lib8tion\random8.h」
の乱数ライブラリ。
これを使った時の乱数出現頻度についてちょいと調べてみました。
Arduino-UNO上で実行。
こんなスケッチで4つの乱数発生について調べました。
・ダウンロード - test_random8_1.txt
※ファイルタイプをtxtにしてます。
inoに変更して。
乱数関数で「0~99」の値を出してその出現頻度を見ます。
10万回実行。
100種類ですんで、平均すると一つの数あたり1000回カウント
されるはずです。
・一つ目
Arduinoに備わっているrand()と剰余で8bit値を得ます。
uint8_t randr(uint8_t n)
{
return rand() % n; // rand()で
}
・二つ目
FastLED-masterのライブラリから、rand8()と乗算/256で。
uint8_t randm(uint8_t lim)
{
uint8_t r = rand8(); // rand8()で乱数
r = (r*lim) >> 8; // 乗じて1/256
return r;
}
・三つ目
rand()+乗算/256で。
uint8_t randr1(uint8_t n)
{
uint8_t r = rand(); // rand()で
r = (r * n) >> 8; // 乗じて1/256
return r;
}
・四つ目
rand8()と剰余で。
uint8_t randm1(uint8_t n)
{
return rand8() % n; // 乱数はrand8()で発生
}
出てきた100個の数字の出現頻度、こんな具合になりました。
一つ目の「rand()と剰余」以外は、「こりゃあかんのちゃう」
という結果です。
Rand test
# cnt-r cnt-m rm 8r
0 947 1194 1235 1176
1 969 1186 1113 1169
2 1001 790 805 1144
3 1016 1170 1159 1198
4 1069 780 848 1186
5 957 1174 1203 1165
6 985 797 800 1194
7 982 1173 1150 1180
8 1023 1192 1143 1146
9 1014 772 733 1178
10 1023 1169 1157 1161
11 1010 799 766 1182
12 1009 1181 1157 1167
13 951 772 736 1160
14 1004 1163 1197 1186
15 991 789 800 1161
16 1006 1181 1187 1162
17 1016 1164 1118 1146
18 942 786 742 1147
19 970 1172 1153 1177
20 967 781 832 1188
21 1024 1155 1169 1161
22 996 800 810 1168
23 1046 1183 1120 1185
24 1023 774 762 1179
25 960 1183 1183 1161
26 1052 1161 1157 1182
27 1057 784 825 1147
28 985 1170 1140 1166
29 1003 810 764 1168
30 1024 1182 1209 1173
31 1046 780 783 1147
32 977 1172 1155 1170
33 1031 1176 1218 1202
34 989 768 764 1160
35 1033 1201 1182 1189
36 971 773 758 1203
37 988 1180 1146 1173
38 973 781 798 1178
39 992 1171 1159 1173
40 992 772 806 1173
41 1053 1155 1181 1181
42 943 1182 1168 1168
43 1026 789 847 1181
44 954 1180 1179 1178
45 966 794 813 1192
46 1055 1191 1165 1193
47 994 775 769 1177
48 968 1196 1167 1158
49 1031 786 810 1174
50 974 1149 1178 1193
51 990 1164 1188 1173
52 997 769 738 1161
53 1009 1166 1125 1187
54 1006 784 808 1175
55 1045 1181 1140 1183
56 958 767 784 792
57 1056 1168 1208 785
58 972 1156 1139 774
59 1029 793 767 787
60 1020 1163 1167 780
61 997 757 789 763
62 946 1174 1137 788
63 1006 788 795 770
64 1024 1179 1223 765
65 1019 769 795 795
66 1009 1168 1188 782
67 1001 1181 1245 788
68 946 770 821 787
69 971 1181 1163 787
70 972 791 753 769
71 979 1192 1221 777
72 986 767 796 770
73 1001 1159 1194 777
74 1011 793 713 789
75 1009 1161 1161 779
76 1012 1178 1170 769
77 1063 768 734 779
78 962 1174 1134 773
79 1037 747 768 772
80 1008 1160 1179 796
81 1027 784 758 770
82 996 1168 1242 787
83 993 1165 1177 766
84 952 791 744 774
85 1017 1147 1209 785
86 971 786 763 788
87 1042 1165 1159 788
88 993 782 772 793
89 1040 1175 1169 781
90 1003 794 834 770
91 966 1180 1141 763
92 974 1164 1198 780
93 1005 776 831 776
94 999 1168 1154 777
95 1004 771 792 775
96 1010 1154 1176 784
97 1051 784 754 776
98 976 1145 1109 780
99 962 775 756 789
Stop
rand rand8 rand rand8
剰余 乗算 乗算 剰余
65us 3.3us 53us 8.2us
エクセルに食わせて昇順にソートしてグラフ化。
一つ目は「1000」が中心になっているのが見えますが、
残り3つは「1000」付近が飛んでいます。
rand()と乗算/256のもアウト。
rand() と 剰余 OK 65us
rand8()と 乗算/256 NG 3.3us
rand() と 乗算/256 NG 53us
rand8()と 剰余 NG 8.2us
※標準偏差がどうのはまた
とりあえずソートして傾向をみただけ
「rand8()と剰余」では値55と56を境にして極端に出現頻度の
傾向が変化しています。
これもけったいな。
「rand8()+剰余」でもおかしな結果ですんで、FastLED-master
のrand8()乱数そのものがということなのかと、8bit値0~255での
出現頻度を調べてみました。
すると、rand()では最小値が896で最大が1087。
中央値が1000。
対し、rand8()では985~1011と1000回に近い値が出てきます。
rand8()が出す数字そのもはうまく散っていますが、それを
特定の数字範囲にした時(剰余、乗算/256どちらでも)に偏り
が出てくるということなのかと。
ところが、「rand() と 乗算/256」の手法でも似たような偏りが
発生。
乱数の周期性と絡んでくるのか。
「0~99」だからなのか、このあたりは調べれば切りがありません。
どうしたものでしょか・・・
もう一つが実行速度。
rand8()はrand()に比べるとむちゃ早いです。
rand 剰余 65us
rand8 乗算 3.3us
rand 乗算 53us
rand8 剰余 8.2us
| 固定リンク
「Arduino」カテゴリの記事
- おっと。map関数の計算桁に注意(2024.10.06)
- DDS方式の2相パルス発生回路、周波数スキャン機能を付ける(2024.10.05)
- 1クロックでも速くしたい 割込を「ISR_NAKED」で(2024.09.30)
- 1クロックでも速くしたい DDS方式の2相パルス発生器(2024.09.27)
- 秋月のI2C接続液晶 AQM1602XAを基板に直付け(2024.09.24)
コメント
この話、注目していたのですが、あまり詳しくないので傍観を決め込んでました。
Arduinoの rand()関数はC++を使っているのでメルセンヌツイスター(MT)で生成していると思いますが、この乱数の質は最高に良いです。
一方で、 rand8()はプログラムを見ると合同法で計算している感じです。しかも上位を切り捨てて8ビットにしている、つまり剰余を使っていることになるので、かなり無理がある気がします。
このあたりの話は以下の資料が参考になると思います。(私が全部理解しているわけではありません)
http://www.math.sci.hiroshima-u.ac.jp/~m-mat/TEACH/ichimura-sho-koen.pdf
256を100で割ると2.56。この半端の0.56を100倍すると56になるので、55と56の間にギャップがあるように見えているのだと想像しますがどうなんでしょう。
投稿: ラジオペンチ | 2021年2月 3日 (水) 17時47分
リンクの記事、むちゃ面白い。
コメント、ありがとうございました。
学術的というのか理論的というのか・・・
目の前にあるものを使ってなんとかする・・・のとは違うご苦労(誰かがせなあかん)、思い知らされます。
半端の0.56x100、これもありそうな話・・・
ついつい「動けばエエ」が先になってしまいます。
投稿: 居酒屋ガレージ店主(JH3DBO) | 2021年2月 3日 (水) 21時06分
いつも楽しく読ませていただいています。
ハンダ付けも乱数発生アルゴリズムも全くの素人ですが、「乱数の剰余を取る」
というのが気になっていたところ、新しい記事になっていたのでコメントします。
(外れていたら、ごめんなさい。)
四つ目
8ビットの乱数(0~255)の出現が一様だとしても、剰余を取って0~99にすると、
0,100,200 の3通り→0
1,101,201 の3通り→1
(中略)
55,155,255 の3通り→55
56,156 の2通り→56
57,157 の2通り→57
(中略)
99,199 の2通り→99
となり、0~55の出現は、56~99の出現の1.5倍になります。
実行時間は増えるけれど、「200以上の時は捨てて、それ以外の時剰余を取る」のはいかがでしょう?
投稿: まいくろ@札幌 | 2021年2月 4日 (木) 00時20分
後ろ3つの乱数発生、乱数の元が8bitというのがおかしくなる原因?
4つ目のrand8()+剰余、まさにまいくろ@札幌 さんご指摘のとおりです。
ありがとうございます。
投稿: 居酒屋ガレージ店主(JH3DBO) | 2021年2月 4日 (木) 08時42分
Arduinoの乱数生成アルゴリズムが本当にMTなのかが気になったのでプログラムを探したのですが、発見できませんでした。math.hにもrandが入っていないんですよね、どこにあるんだろう。
MTで乱数を発生させるためにはメモリーがいっぱい必要なのでATmega328では無理で、合同法で計算しているのかも知れません。
その場合でもたぶん32ビットの乱数を発生させているでしょうから、割り算した時の余りの問題が目立たなくなりそうです。それに(2^32)-1を100で割ると余り僅かなのも好都合です。
投稿: ラジオペンチ | 2021年2月 4日 (木) 09時19分
rand()はstdlib.hで定義されてます。
※ソースはどこだろう。
戻り値は16bitで0~0x7FFF。
そして、32bitのrandom()を使えと。
/** Highest number that can be generated by rand(). */
#define RAND_MAX 0x7FFF
/**
The rand() function computes a sequence of pseudo-random integers in the
range of 0 to \c RAND_MAX (as defined by the header file <stdlib.h>).
The srand() function sets its argument \c seed as the seed for a new
sequence of pseudo-random numbers to be returned by rand(). These
sequences are repeatable by calling srand() with the same seed value.
If no seed value is provided, the functions are automatically seeded with
a value of 1.
In compliance with the C standard, these functions operate on
\c int arguments. Since the underlying algorithm already uses
32-bit calculations, this causes a loss of precision. See
\c random() for an alternate set of functions that retains full
32-bit precision.
*/
extern int rand(void);
投稿: 居酒屋ガレージ店主(JH3DBO) | 2021年2月 4日 (木) 09時44分
random()は「arduino.h」の中。
投稿: 居酒屋ガレージ店主(JH3DBO) | 2021年2月 4日 (木) 09時47分
おはようございます。
2つ目と3つ目の乗算/256の場合も、
元:0~255 の256枚のカードを計算式に従って
変換後:0~99 の100人に配ると、
・3枚もらう人:0,1,3,5,…,98
・2枚もらう人:2,4,6,9,…,99
と差が1.5倍になります。(エクセルで確認しました。)
個人的な結論は、
「十分な数量のカードが無い時は、公平性を保てない余ったカードは勿体ないけど捨てる」です。
投稿: まいくろ@札幌 | 2021年2月 4日 (木) 10時11分
乱数元が8bitで「乗算/256」での処理(2つ目と3つ目)、
これは「100/256」という割り算の「切り捨て」問題
が原因かと。
発生した乱数が0,1,2ならr=0、3,4,5はr=1。
次の6,7がr=2。 8,9,10だとR=3。
r=2の所が少なくなる。
これが順繰りに。
出てくる100種の数、それが44:56の比か。
あかんやん、FastLED-masterの乱数の考え方。
早いかもしれんけど。
投稿: 居酒屋ガレージ店主(JH3DBO) | 2021年2月 4日 (木) 10時43分
3つ目のをこのように。
rand()の16bit値を生かす。
1/256ではなく1/32768に。
/***** rand()+乗算による乱数 *****/
uint8_t randr1(uint8_t n)
{
uint32_t r = rand(); // rand()で 0~0x7FFF(32767)
r = (r * n) / 32768; // 乗じて1/32768
return r;
}
すると1つ目のに近くなりました。
出た数値の範囲、
1つ目 942~1069
新3つ目:927~1087
に。
投稿: 居酒屋ガレージ店主(JH3DBO) | 2021年2月 4日 (木) 11時05分
結局、8bit出力の乱数関数rand8()を使って0~99の乱数を得ようとしたのがスカタンだったのかと。
これが0~7あたりだったら、「早うてエエやん」になったかも。
投稿: 居酒屋ガレージ店主(JH3DBO) | 2021年2月 5日 (金) 08時35分