|
1 |
| -// SPDX-FileCopyrightText: (c) 2021 Art Galkin <github.com/rtmigo> |
| 1 | +// SPDX-FileCopyrightText: (c) 2021-2022 Art Galkin <github.com/rtmigo> |
2 | 2 | // SPDX-License-Identifier: MIT
|
3 | 3 |
|
4 | 4 | import 'dart:math';
|
@@ -146,7 +146,7 @@ void testCommonRandom(RandomBase32 Function() createRandom,
|
146 | 146 | test('nextFloat', () => checkDoubles(createRandom(), false));
|
147 | 147 |
|
148 | 148 | test('bools', () => checkBooleans(createRandom()));
|
149 |
| - test('ints', () => checkIntegers(createRandom())); |
| 149 | + test('ints', () => check_nextInt_bounds(createRandom())); |
150 | 150 |
|
151 | 151 | test('ints when power of two', () {
|
152 | 152 | final r = createExpectedRandom();
|
@@ -243,10 +243,21 @@ void testCommonRandom(RandomBase32 Function() createRandom,
|
243 | 243 | expect(random2.nextRaw32(), b64.lower32());
|
244 | 244 | });
|
245 | 245 |
|
246 |
| - test('large ints uniformity', () { |
| 246 | + test('nextInt returns uniform result for max > 1<<32', () { |
247 | 247 | final r = createRandom();
|
248 |
| - checkUniformityForLargeInts(r); |
| 248 | + check_nextInt_is_uniform_for_large_max(r); |
249 | 249 | });
|
| 250 | + |
| 251 | + var r = Random(); |
| 252 | + for (int i=0; i<10; ++i) { |
| 253 | + // generating max from range 1000..(1<<32) |
| 254 | + int max = 0; |
| 255 | + while ((max = r.nextInt(0xFFFFFFFF+1)+1)<1000) {} |
| 256 | + |
| 257 | + test('nextInt returns uniform results for max=$max', () { |
| 258 | + check_nextInt_is_uniform(createRandom(), max); |
| 259 | + }); |
| 260 | + } |
250 | 261 | });
|
251 | 262 | }
|
252 | 263 |
|
@@ -289,7 +300,9 @@ void checkBooleans(Random r) {
|
289 | 300 | expect(countTrue, lessThan(N * 0.6));
|
290 | 301 | }
|
291 | 302 |
|
292 |
| -void checkUniformityForLargeInts(Random random) { |
| 303 | +void check_nextInt_is_uniform_for_large_max(Random random) { |
| 304 | + // checking whether nextInt results are uniform for max exceeding 31<<1 |
| 305 | + // |
293 | 306 | // eliminating the issue:
|
294 | 307 | // https://github.com/rtmigo/xrandom_dart/issues/3
|
295 | 308 |
|
@@ -318,8 +331,51 @@ void checkUniformityForLargeInts(Random random) {
|
318 | 331 | expect(upper, lessThan(expected + delta));
|
319 | 332 | }
|
320 | 333 |
|
| 334 | +void check_nextInt_is_uniform(Random random, int max) { |
| 335 | + // we will split range (0..max) to three equal bins: (0..a) (a..b) (b..max) |
| 336 | + // Then we generate random ints from (0..max), and counting how many results correspond |
| 337 | + // to particular bin. If the distribution is uniform, we'll get roughly the same count |
| 338 | + // of results in each bin. |
| 339 | + |
| 340 | + int a = (max * (1 / 3)).round(); |
| 341 | + int b = (max * (2 / 3)).round(); |
| 342 | + |
| 343 | + assert (0 < a); |
| 344 | + assert (a < b); |
| 345 | + assert (b < max); |
| 346 | + |
| 347 | + int countA=0, countB=0, countC=0; |
| 348 | + |
| 349 | + const N = 10000000; |
| 350 | + |
| 351 | + for (int i=0; i<N; ++i) { |
| 352 | + |
| 353 | + var x = random.nextInt(max); |
| 354 | + if (x<a) { |
| 355 | + countA++; |
| 356 | + } else if (x<b) { |
| 357 | + countB++; |
| 358 | + } else { |
| 359 | + countC++; |
| 360 | + } |
| 361 | + } |
| 362 | + |
| 363 | + final int expected = (N/3).round(); |
| 364 | + final int delta = (expected*0.1).round(); |
| 365 | + |
| 366 | + expect(countA, greaterThan(expected - delta)); |
| 367 | + expect(countA, lessThan(expected + delta)); |
| 368 | + |
| 369 | + expect(countB, greaterThan(expected - delta)); |
| 370 | + expect(countB, lessThan(expected + delta)); |
| 371 | + |
| 372 | + expect(countC, greaterThan(expected - delta)); |
| 373 | + expect(countC, lessThan(expected + delta)); |
| 374 | +} |
| 375 | + |
321 | 376 |
|
322 |
| -void checkIntegers(Random r) { |
| 377 | +/// Check that `nextInt(max)` returns only values from the `0 < x < max`, including `0` and `max-1`. |
| 378 | +void check_nextInt_bounds(Random r) { |
323 | 379 | int countMin = 0;
|
324 | 380 | int countMax = 0;
|
325 | 381 |
|
|
0 commit comments