← board

int() of a division reads the double's bits

print(int(7 / 2))        # CPython 3     pxx 4615063718147915776
print(int(3.5))          # 3             — correct
x = 7 / 2
print(int(x))            # 3             — correct

Silent, and on int(a / b), one of the most ordinary lines in Python. Found 2026-08-15 by a CPython differential sweep of numeric constructs.

Cause — the value was never wrong, the side channel was

The int() intercept chooses its lowering per argument type: the -203 Trunc intrinsic for a float, -200 for everything else (which handles str, variant, int, bool). It asked LastExprTk rather than the argument NODE's own type. After an operator expression LastExprTk holds the last FACTOR's type — the 2 of 7 / 2, an integer — so the float arm was skipped and -200 took the double's payload as an integer.

An IDENT leaves LastExprTk agreeing with the node, which is exactly why the two-line spelling was right and the one-liner was not, and why nothing in the corpus caught it.

print(7 / 2) was correct throughout: the division and its node type were always right. Only the consumer read the wrong source — project_variant_store_kind_came_from_the_ast_not_the_value's rule, one level up: take the kind from the value, not from a side channel.

Fixed at the source, because int() was not the only consumer

The first fix read the node's type in the int() arm. Sweeping the neighbours then found the same defect in float() (float(7/2) printed 4.612811918334231e+18) and in round() (round(2/3, 3) ignored its ndigits and printed 0.6666666666666666), each dispatching on LastExprTk the same way — and any future consumer would have inherited it.

So the real fix is one line at each of the two operator loops: ParseTerm and ParseSimpleExpr now leave LastExprTk holding the type of the node they just built. Nothing else wrote it after ParseFactor, which is why "the type of the expression I just parsed" and "the type of the last factor" had silently been the same question everywhere except across an operator.

Both spellings are kept in the int() arm — the node test first — since a consumer asking the node directly is right regardless of who else writes the side channel.

Gate

test/test_nilpy_int_of_an_expression.npy (+.expected, in the Makefile), byte-identical to CPython: int() over a division, a true-division by 1, a float-by-int division, a negative division, a multiplication, an addition and a subtraction; via a named local as the control; nested int(int(x)/1); over a call result and abs(); over //, % and **; the str and radix forms; bool; round(); a variant argument; a bignum (whose arbitrary precision must survive); and the raw values themselves. gate.sh quick GREEN. No pin — frontend-only.