Skip to content

random: randint() no longer rejects the largest upper bound - #11443

Merged
tannewt merged 1 commit into
adafruit:mainfrom
dhalbert:fix-randint-upper-bound
Sep 22, 2026
Merged

tannewt merged 1 commit into
adafruit:mainfrom
dhalbert:fix-randint-upper-bound

Conversation

@dhalbert

@dhalbert dhalbert commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Code written by Claude Code, guided and corrected by @dhalbert.

The problem

#11378 (FYI @peterbay) made randint(a, b) raise ValueError when b is the largest mp_int_t, to avoid the b + 1 overflow in the randrange(a, b + 1, 1) it called. On 32-bit ports that value is 0x7FFFFFFF, which is a perfectly ordinary upper bound. And randint(1, 0x7FFFFFFF) is what the WIZnet DHCP library uses for its transaction id.

The changes

  • Add shared_modules_random_randint() in shared-module/random/__init__.c. It computes the span b - a + 1 in mp_uint_t, so it cannot overflow for any a <= b, and draws with yasmarang_randbelow() directly. A span of zero (the full mp_int_t range) returns a raw 32-bit draw instead of looping forever in randbelow(). In practice mp_obj_get_int() already rejects the most negative mp_int_t, so that branch is a guard rather than a reachable path.
  • random.randint() in shared-bindings/random/__init__.c calls it and drops both the b + 1 and the b == INT_MAX check. The a > b check is unchanged.
  • Add a new CIRCUITPY-CHANGE block in tests/extmod/random_extra.py exercising randint(0, 0x7FFFFFFF) and randint(1, 0x7FFFFFFF). The unix test build is 64-bit, so this only catches the regression on 32-bit targets, but it documents the requirement.

Testing

Feather nRF52840 Express, before and after.

10.4.0-alpha.2 this branch
randint(1, 0x7FFFFFFF) ValueError in range, 200 draws
randint(0, 0x7FFFFFFF) ValueError in range, 200 draws
randint(-1, 0x7FFFFFFF) ValueError in range, 200 draws
randint(-0x7FFFFFFF, 0x7FFFFFFF) ValueError in range, 200 draws
seed(7) twice, then randint(0, 0x7FFFFFFF) ValueError reproducible
randint(5, 5), randint(-3, -3) 5, -3 5, -3
randint(2, 1) ValueError ValueError

tests/extmod/random_*.py pass on the unix coverage build.

adafruit#11378 made `randint(a, b)` raise `ValueError` when `b` is the largest
`mp_int_t`, to avoid the `b + 1` overflow in `randrange(a, b + 1, 1)`.
On 32-bit ports that is `0x7FFFFFFF`, which the WIZnet DHCP library
passes, so `randint(1, 0x7FFFFFFF)` stopped working (adafruit#11441).

Add `shared_modules_random_randint()`, which computes the span
`b - a + 1` in unsigned arithmetic so it cannot overflow for any
`a <= b`, and use it from the binding instead of forming `b + 1`.

Fixes adafruit#11441

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@tannewt

tannewt commented Sep 22, 2026

Copy link
Copy Markdown
Member

Want this on 10.3.x instead?

@dhalbert

Copy link
Copy Markdown
Collaborator Author

Want this on 10.3.x instead?

The issue says this works in 10.3.1, and the code that caused the regression was only on main, not on the 10.3.x branch (I checked the branch). So there's nothing to do on 10.3.x.

@tannewt tannewt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, great!

@tannewt
tannewt merged commit c81412b into adafruit:main Sep 22, 2026
691 checks passed
@dhalbert
dhalbert deleted the fix-randint-upper-bound branch September 22, 2026 18:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

random.randint() exception in 10.4.0-alpha.2 (was OK in 10.3.1)

2 participants