Repository navigation
libgis: Add integer functions for a random number state - #8079
Merged
Merged
Conversation
G_random_double() was the only way to draw from a program-owned state, so code which needed an integer, as from G_lrand48() or G_mrand48(), scaled the double by 2^32 and cast it, repeating the constant and the cast at every site. The three functions are one draw read three ways: G_random_uint32() gives the high 32 bits of the state, which is what G_mrand48() returns read as unsigned and the floor of 2^32 times the double; G_random_int32() reads the same bits signed, as G_mrand48(); G_random_int() keeps their top 31 bits as a non-negative int, as G_lrand48(). A test checks all four relations for 100 draws of seven seeds. Written with the help of Claude Code.
The old functions each one reproduces are named in the migration table of the random numbers page instead. Written with the help of Claude Code.
The rows for G_lrand48() and G_mrand48() name G_random_int(), G_random_int32() and G_random_uint32() instead of scaling and casting the double by hand. Written with the help of Claude Code.
The relation to G_random_double() and the fact that the three are one draw read three ways are for the commit message and the migration table, not the function comments.
petrasovaa
approved these changes
Oct 20, 2026
petrasovaa
left a comment
Contributor
There was a problem hiding this comment.
Some nitpicks if you want to look at it, but I think it's good to go.
Details
1. Signed cast is implementation-defined (lib/gis/lrand48.c:834). The (int32_t) cast of a value above INT32_MAX is implementation-defined before C23, but the doc comment promises the bits "read as a signed value" and the test pins it to G_mrand48(). All GRASS targets are two's complement so it matches today, and G_mrand48() already has the same pattern. The contract could be stated as modular conversion, or spelled with an explicit subtraction of 2^32. 2. Narrative doc never mentions the integer functions (lib/gis/random_streams.dox.md:30). The Terms section still says a State is only drawn from by G_random_double(). The three new functions appear only in the migration table. Terms and Usage should mention the integer variants once. 3. Stale G_random_advance() comment (lib/gis/lrand48.c:751). It still says the next G_random_double() returns the skipped draw. Should say "the next draw" now that other functions step the state too. 4. Migration row for G_mrand48() overstates equivalence (random_streams.dox.md:422). It says values are "the same" for both the signed and unsigned replacements, but the unsigned one gives the same bits, not the same value, for half the range. The old row's "same bits" distinction should be kept, or the row split. 5. Naming trap between G_random_int() and G_random_int32() (include/grass/defs/gis.h:535). One returns non-negative [0, 2^31), the other signed [-2^31, 2^31). Near-identical names with different sign contracts. A one-line note in the header, or a name like G_random_int31(), would help. Low severity, API design. 6. Inconsistent doc comment detail (lrand48.c:805). The new draw functions drop the context the G_random_double() comment gives about seeding and obtaining a state. Cosmetic. 7. Test checks only against the live generator (lib/gis/tests/lib_gis_lrand48_test.py:507). The new test compares state-based integer draws to the shared generator but not to the pinned REFERENCE table, unlike the existing drand48 cross-check. Low severity, since the separate G_lrand48/G_mrand48 reference tests cover it indirectly.
Member
Author
|
Thanks. I will circle back to some of the nitpics later. 2-4, 6 and 7 are all basically about improving documentation and bringing the integer functions on par with the double ones (in terms of tests and doc). (1 is the current state, so no action. 5 is either doc or name choice, I considered different combinations and landed on the one in the PR because types are real types unlike the int31 which is a range, but not a type unlike int32.) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Since I focused the original implementation of the new RNG on r.sim.water, the caller-owned
struct G_random_statecould be used withG_random_double(). This adds two integer functions, replacingG_lrand48()andG_mrand48()from the global generator (theintandint32functions). It also add a newuint32function which complements theint32variant.As with the double function, this gives the same results as the global functions at this point (and that's tested).
Details (AI generated)
The functions
One draw of the state read three ways, each with its range in the doc comment and the old function it reproduces:
int G_random_int(struct G_random_state *)G_lrand48()int32_t G_random_int32(struct G_random_state *)G_mrand48()uint32_t G_random_uint32(struct G_random_state *)(unsigned int)G_mrand48(), the floor of 2^32 timesG_random_double()G_random_int()returns a plainint, asG_random_double()returns a plaindouble: a value to compute with, whose range the documentation gives; the two fixed-width functions are the bit-level pair. The old functions returnedlongfrom the days whenintcould be 16 bits;intholds 2^31 - 1 on every platform GRASS builds on.Migration
G_lrand48()G_random_int(&rng)G_mrand48()G_random_int32(&rng)(unsigned int)G_mrand48()G_random_uint32(&rng)Tests
One test with 100 draws for each of seven seeds checks all four relations against the shared generator and the double.
Suggested squash commit message
The code and tests were written with Claude Code (Claude Fable 5.1) and reviewed by the author of the PR.