← index #17730PR #9531
Related · medium · value 0.219
QUERY · ISSUE

code paths that return large ints where small ints would suffice

openby jepleropened 2025-07-21updated 2025-07-21
bugpy-core

Port, board and/or hardware

unix, standard variant, 32 bit

MicroPython version

MicroPython v1.26.0-preview.387.g67acac257f.dirty on 2025-07-21; linux [GCC 12.2.0] version

Reproduction

3-arg pow always returns long ints

>>> i = pow(3,3,3)
>>> i
0
>>> i is 0
False
>>> id(i)
4155902608

anything using the code paths for 'Q' type codes:

>>> i = array.array('Q', [0])[0]
>>> i
0
>>> id(i)
4155903568

Interestingly, the latter doesn't even behave as a zero in some ways (it has 4 digits):

$5 = {neg = 0, fixed_dig = 0, alloc = 4, len = 0, dig = 0xf7b60b80}
(gdb) p mod->dig[0]
$6 = 0
(gdb) p mod->dig[1]
$7 = 0
(gdb) p mod->dig[2]
$8 = 0
(gdb) p mod->dig[3]
$9 = 0

so this will hang indefinitely:

pow(3,3,i)

Expected behaviour

As I understood it, the invariant is supposed to be that if an integer value fits in a small int, it MUST be returned to Python as a small int.

Observed behaviour

In these code paths (and possibly others) there's the possibility for these values to be returned as long ints instead

Additional Information

I discovered this while implementing review comments for #17716, because I noticed a comment that said

        mp_obj_t result = mp_obj_new_int_from_ull(0); // Use the _from_ull version as this forces an mpz int

Code of Conduct

Yes, I agree

CANDIDATE · PULL REQUEST

py/objint_*: Convert back to small int after binary op.

mergedby jimmoopened 2022-10-06updated 2024-07-01
py-core

Inspired by https://github.com/micropython/micropython/pull/9516#discussion_r988475628 -- this behaviour was a surprise to me.

Before this change, long/mpz ints propagated into all future calculations.

For example, a relatively common operation like x = a * b // c where a,b,c all small ints would always result in a long/mpz int, even if it didn't need to, and then this would impact all future calculations with x.

This adds +24 bytes on PYBV11 but avoids heap allocations and potential surprises (e.g. big-big is now a small 0, and can safely be accessed with MP_OBJ_SMALL_INT_VALUE).

There are several places in the int_big* tests that result in small integers (especially zero). I don't think this change makes those tests less valid -- it still correctly checks that they evaluate to the right thing. What we're missing is a way to check that you can print a small-valued mpz (as all these tests will now be going via small int printing).

The longlong implementation doesn't have tests, but I adapted the new int_big_to_small.py to run on a 32-bit Linux build.

This work was funded through GitHub Sponsors.

Keyboard

j / / n
next pair
k / / p
previous pair
1 / / h
show query pane
2 / / l
show candidate pane
c
copy suggested comment
r
toggle reasoning
g i
go to index
?
show this help
esc
close overlays

press ? or esc to close

copied