Skip to content

Re-work PR 24105 to support threads and otherwise clean up - #24294

Closed
tonycoz wants to merge 8 commits into
Perl:bleadfrom
tonycoz:24105-modern_rand
Closed

Re-work PR 24105 to support threads and otherwise clean up#24294
tonycoz wants to merge 8 commits into
Perl:bleadfrom
tonycoz:24105-modern_rand

Conversation

@tonycoz

@tonycoz tonycoz commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

This is a expansion of #24105 which:

  • adds threading support
  • also updates the "internal" RNG to use the same RNG
  • split out the random number generator code into separate headers/implementation files
  • produce an NV instead of a double, since they have the bits of randomness to support larger floats
  • config cleanup

I've pointed the author of #24105 at this branch in the past.


  • This set of changes requires a perldelta entry, and I need help writing it.

scottchiefbaker and others added 8 commits March 17, 2026 09:17
since we may not be using drand48, just something that returns
double (at least for now)
Some of this will be further changed:

- moved the pcg64_random_t type to util.h so it's visible to intrpvar.h
- changed the PL_RANDOM_STATE_TYPE to pcg64_random_t
- changed the internal RNG to reference the new RNG and changed the
  macro names to something more generic (without "drand48")
- added pcg64_random_t * parameters to the seed and RNG generation
  functions (and rename with _r) so we can make them thread safe and
  eliminate the global non-thread-safe random state
- Needed to move the functions out of prng.h so porting/args_assert
  could find the function definitions and eliminate prng.h
- re-define the original non-thread-safe functions as macro wrappers
  that pass in the state to the _r versions of the functions

I plan to move all of the random stuff out of util.h/util.c into
perlrand.h and random.c.
Perl supports larger floats, the existing code would drop bits of
randomness on the floor, avoid that.
Make it easier to find the bits of perl that deal with random number
generation and seeding.
@scottchiefbaker

scottchiefbaker commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

produce an NV instead of a double, since have have the bits of randomness to support larger floats

I'm trying to figure out what this could mean. Can you clarify the double "have" in this sentence?

Also is this PR merge-able? What else needs to be done?

@tonycoz

tonycoz commented Mar 31, 2026

Copy link
Copy Markdown
Contributor Author

produce an NV instead of a double, since have have the bits of randomness to support larger floats

I'm trying to figure out what this could mean. Can you clarify the double "have" in this sentence?

My typo, should be: "since they have the bits ..."

In particular, you can build perl with it's internal floating point as long double (-Duselongdouble) or __float128 (-Dusequadmath) which have more bits of precision than a double.

Also is this PR merge-able? What else needs to be done?

Probably in 5.45.0, but I'd like some review.

@Leont

Leont commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Probably in 5.45.0, but I'd like some review.

Yeah I'd lean towards that too, we're quite late in the cycle for 5.44 now.

@scottchiefbaker

Copy link
Copy Markdown
Contributor

I agree 100% this should target Perl v5.45, because we're too late into the release cycle. I will poke around with this code, see if I can get it to build and report back. Thanks @tonycoz

@scottchiefbaker

scottchiefbaker commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

I looked through this and it compiles and spits out appropriate PCG64 numbers. After some investigation it appears that most Perl installations use 8 byte NVs, which is essentially a double. Due to this, in order to produce less biased random floats, I think uint64_to_NV() should be changed to -53.

I have some minor white-space, and DEBUG clean-ups I'd like to land as part of this. Is there a way to submit a PR for this PR? Or how do I suggest changes?

Otherwise I think this looks really solid. Thanks for the hard work @tonycoz

@tonycoz

tonycoz commented Apr 1, 2026

Copy link
Copy Markdown
Contributor Author

I think uint64_to_NV() should be changed to -53.

This would lead to the bottom 11 bits of randomness being lost on builds with more precise NVs (I assume you want the shift back in or we won't get [0 .. 1) ).

What does changing to -53 fix? I don't see how it change the bias of the result - in both cases the high-bits of the result will be the same. double will have 53-bits of precision whether you remove the bottom 11 bits with a shift or the conversion from U64 to double discards them.

The only problem I can see with preserving those low bits for more precise NVs would be the slight difference in results, but I could see that preserving the bits reducing bias, ie. for floor(rand()*N) I'd expect the results to be (very very) slightly more evenly distributed (assuming PCG64 is evenly distributed)

I have some minor white-space, and DEBUG clean-ups I'd like to land as part of this. Is there a way to submit a PR for this PR? Or how do I suggest changes?

As mentioned in IRC, you could open a PR against my fork, or update your original PR.

@scottchiefbaker

Copy link
Copy Markdown
Contributor

@tonycoz you're smart so I assume you know most of this, but I'm writing it all down for posterity for other people that have questions about generating random floats.


PRNGs only generate integers. Thus if you have a 64bit PRNG it generates integers between 0 and 2^64 -1. Perl wants to give out floats instead of giving out integers. To convert a random number to a float the math appears simple:

my $num   = rand64();
my $float = $num / (2 ** 64 - 1);

Due to the nature of doubles only being able to hold 53bits of mantissa (decimal values) this causes problems. The input has 2^64 potential values, but the output only has 2^53 potential values. This means that there are two input numbers that will generate the same output value. There is a small amount of bias because in those cases where there are duplicates there is a much higher chance of a particular random float coming out.

The traditional method ("Generating uniform doubles") of generating random floats is to only use 53bits so that the input to output is 1:1.

my $num   = rand64();
$num    >>= 11; # Use the most significant 53bits
my $float = $num / (2 ** 53 - 1);

@tonycoz

tonycoz commented Apr 2, 2026

Copy link
Copy Markdown
Contributor Author
my $num   = rand64();
$num    >>= 11; # Use the most significant 53bits
my $float = $num / (2 ** 53 - 1);

None of the examples on that page use / (2 ** N - 1) which is a little confusing, they all end up dividing by a power of two, not a (power of two) minus one. (dividing by $2^N-1$ would give us the range [0, 1] not the desired [0, 1))

The C example is:

 (x >> 11) * 0x1.0p-53

which has a different meaning to your expression, dividing by $2^{53}$

This means that there are two input numbers that will generate the same output value. There is a small amount of bias because in those cases where there are duplicates there is a much higher chance of a particular random float coming out.

With $num >> 11 / (2 ** 53) there's $2^{11}$ input values for each possible output value.

I suspect the issue is that for smaller input integers we get more possible output numbers for a given range of input, even if the [0, 1) range is still evenly distributed. And rounding might rear its ugly head too.

Unfortunately we don't have a constant like DBL_MANT_DIG (NVMANTBITS isn't quite right) which would let us make a smarter conversion, but maybe that can change.

@scottchiefbaker

Copy link
Copy Markdown
Contributor

You are correct, I was off by one in my examples above. I used the correct algorithm in my updated PR.

@scottchiefbaker

Copy link
Copy Markdown
Contributor

This would lead to the bottom 11 bits of randomness being lost on builds with more precise NVs (I assume you want the shift back in or we won't get [0 .. 1) ).

If the goal is to get every available random bit to the user (an admirable goal) then we shouldn't be using floats. 53bits of randomness is probably suitable for 99% of use cases. In the situation where the user needs more, we should expose the raw underlying PRNG to return uint64_t.

For the purposes of this PR I think that's beyond the scope.

@tonycoz

tonycoz commented Apr 23, 2026

Copy link
Copy Markdown
Contributor Author

#24105 now includes the relevant changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants