Skip to content

% of two integers of any width is an integer remainder - #852

Merged
ASDAlexander77 merged 1 commit into
mainfrom
integer-remainder
Nov 14, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
integer-remainder

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

What

% of two integers of any width is now an integer remainder with an integer result. Since #571 that was true only when one operand was 64-bit; two narrower integers were cast to number.

Why

Found in the default library: Map and Set compute <uint>hashCode % <uint>buckets.length on every lookup. That compiled to two uitofp, an frem (an fmod call) and an fptosi. Modulo a length of 0 it was NaN converted to an index, which is undefined (ASDAlexander77/TypeScriptCompilerDefaultLib#100 removed the zero length). It is now:

%8 = urem i32 %1, %7

The type of the result

  • An integer literal that fits takes the other operand's type, on either side (u % 7 is a u32).
  • Otherwise the wider operand's type; of two of the same width and signedness, the left one.
  • A signed and an unsigned operand that an s64 holds both of are done in s64, so neither changes its value (s32 % u32, s16 % u16).
  • 64-bit operands are as they were after TSLANG_ISSUES 16: % of 64-bit integers is an integer remainder #571.
  • A number on either side is still a number's remainder.

Breaking

  • Result type: int % int is an integer, not a number. own/own_interface_view.ts put i % 10 into an object literal's field seen through an interface declaring number, which no longer compiled; it now declares the constant : number. That was the only test affected.
  • Zero divisor: undefined, as integer division by zero is in C (a fault on x86), where it was NaN. The same holds for the least value of a signed type % -1. This is what TSLANG_ISSUES 16: % of 64-bit integers is an integer remainder #571 already did for 64-bit operands.

Change

  • tslang/lib/TypeScript/MLIRGenImpl.h: the % case of the cast step in adjustTypesForBinaryOp. The lowering is unchanged: it already picks remsi/remui by type.
  • tslang/test/tester/tests/00int_remainder.ts (new, registered for compile and JIT): s8 to u32, literals on either side, mixed signedness, mixed widths, %=, what >>> gives, and the number path.
  • 00int64_remainder.ts: one comment that said narrower integers keep the number path.

Testing

  • Windows x64, release: the whole ctest suite passes, 7244 of 7244.
  • The default library rebuilt with this compiler for gc, rc, none and own, and its suite passes on all four, compiled and under the JIT. It needs no source change.
  • Speed: a loop of Map.get over 200,000 keys went from about 18 ms to about 17 ms. That is small and close to noise.
  • Not run locally: debug, Linux, x86, wasm.

🤖 Generated with Claude Code

Only a 64-bit operand made `%` an integer remainder (#571); two narrower
integers were cast to number. A hash modulo the length of a table - every
lookup of a Map or a Set - was an fmod call and two conversions, and modulo a
length of 0 it was NaN made into an index. It is one urem/srem now.

The result has the integer type, where it was a number. The type: an integer
literal that fits takes the other operand's; otherwise the wider one, and of
two of a width the left one. A signed and an unsigned one that an s64 holds
both of are done in s64, where neither changes its value. 64-bit operands are
as they were.

Breaking:
- the result of `int % int` is an integer, not a number. own_interface_view.ts
  put it in an object literal's field seen through an interface that declares
  number, and now declares it `: number`.
- a zero divisor is undefined, as integer division by zero is in C (a fault on
  x86), where it was NaN; so is the least value of a signed type `% -1`.

A number on either side is still a number's remainder.

00int_remainder.ts covers s8 to u32, literals on either side, mixed
signedness, mixed widths, `%=`, what `>>>` gives, and the number path.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 5aa1cdb into main Nov 14, 2026
8 checks passed
@ASDAlexander77
ASDAlexander77 deleted the integer-remainder branch November 14, 2026 16:33
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.

1 participant