code paths that return large ints where small ints would suffice
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
py/objint_*: Convert back to small int after binary op.
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.