Skip to content

Perform 32-bit random if the bound fits into 32 bits at runtime.

Patch: random32.patch.

Motivating example:

{$mode objfpc}
uses
	Math;
var
	shuffleBag: array of int32;
	i: SizeInt;
begin
	write('Shuffle: ');
	shuffleBag := [1, 2, 3, 4, 5, 6, 7];
	repeat
		i := random(length(shuffleBag));
		write(shuffleBag[i], ' ');
		delete(shuffleBag, i, 1);
	until length(shuffleBag) = 0;
	writeln(LineEnding, 'Nefia boss hex resistance = ', RandomRange(0, 100), '%');
end.

Output on x86-32 before the patch (and everywhere after the patch):

Shuffle: 7 4 5 6 2 1 3
Nefia boss hex resistance = 64%

Output on x86-64 before the patch:

Shuffle: 7 5 2 6 3 4 1
Nefia boss hex resistance = 90%

“Shuffle” part might be understandable: length() is a SizeInt, which is an int32 on 32 bits and int64 on 64 bits, so they call different random overloads which work differently in general and draw different amounts of uint32 from the underlying generator, so what’d you expect. However, this is very inconvenient if you seek reproducibility across bitnesses, and random(int32(length())) is an extremely annoying workaround: in addition to being cumbersome, it, well, truncates length() to 32 bits which means all of its instances have the potential to become hard-to-find bugs.

Moreover, int64 is a native type on 64 bits, so calculating the random bound in place (a, b: int32; random(a + b)) will often upcast it to 64 bits according to FPC rules, leading to more unobvious results, like bitness-dependent Math.RandomRange which does exactly that under the hood.

I personally did this change with my own RNG a thousand years ago, and remembered about the same problem with standard System.Random because I found this issue while searching for something unrelated. It had a completely different complaint which is not fixed by my patch, but I still think it can be closed because it’s long out of date (and the complaint would remain DUBIOUS regardless of that, like, the only reason to do that is validating the underlying generator, which is not going to lose much of the reliability from checking every other value).

As you might suspect from having a glimpse at random(int32) and random(int64) implementations, this is also an optimization to random(int64): on my computer, it becomes 50–80% faster (not inlining random(int32) reduces this to 30–50%) when its argument fits into int32, at the cost of 5–10% slowdown when it doesn’t.

Edited by Rika
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information