Skip to content

Truncate Python integer division in integer arithmetic so long quotients stay exact [patch] - #206

Merged
matt-edmondson merged 1 commit into
mainfrom
fix/192-python-long-division
Oct 12, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
fix/192-python-long-division

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #192

What changed

PythonGenerator.GenerateBinary lowered integer / to int(a / b) and integer % to a - b * int(a / b). Python's / always returns a float. For a long above 2^53 the quotient was rounded before it was truncated, so the quotient came out wrong and the remainder could come out negative. For example, n % 7 with n = 1700000000123456789 returned -75 instead of 2.

The quotient is now truncated using integer arithmetic only:

(a // b if (a < 0) == (b < 0) else -(-a // b))

When the operands share a sign, flooring and truncating agree. When they don't, negating the dividend turns floor division into truncation. The remainder is still a - b * <quotient>. As before, an operand that is a variable or a literal is written inline, and anything else (such as a call) is bound once through a lambda. The IntegerDivisionTests call-count assertion still checks that.

int operands get the same lowering. It is exact for them too, and one form for both keeps the generator simple.

Tests (IntegerDivisionTests)

Python_LongOperandsAbove2To53_MatchTheCFamily runs the generated Python and compares it with C#'s / and %. It covers:

  • long operands passed as parameters, through a call operand (the lambda path), and as the issue's repro (long n = 1700000000123456789; return n % 7)
  • all four sign combinations, 9007199254740993 / 1, long.MaxValue, and long.MinValue + 1

The test module starts with long = int, because the generator still writes long as an undefined annotation (#109, open PR #124). That keeps this test about the arithmetic.

With the generator change reverted, the test fails on its first check: div_long_literal() = 242857142874779552, expected 242857142874779541. With the change, the existing Python_MatchesTheCFamily cases still pass, and the full suite passes (1116/1116).

The JavaScript side (long values outside ±(2^53−1)) is a separate issue, #168, and is not touched here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CkCrYLchcFZxtGF9DyXZHi


Generated by Claude Code

…nts stay exact [patch]

Integer `/` and `%` were lowered to `int(a / b)` and `a - b * int(a / b)`.
Python's `/` is true division and returns a float, so for a long above
2^53 the quotient was rounded before it was truncated: `n % 7` on
1700000000123456789 returned -75 instead of 2.

The quotient is now `a // b` when the operands share a sign and
`-(-a // b)` when they don't, which truncates toward zero without leaving
integer arithmetic. The remainder is `a - b * quotient` as before. Operands
that are not safe to read twice are still bound once through a lambda.

Fixes #192

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CkCrYLchcFZxtGF9DyXZHi
@sonarqubecloud

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit 3020d1b into main Oct 12, 2026
15 checks passed
@matt-edmondson
matt-edmondson deleted the fix/192-python-long-division branch October 12, 2026 23: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.

Python integer division and remainder are wrong for long values above 2^53: n % 7 returns -75

2 participants