Skip to content

Add BigMul across the integer types - #594

Merged
SimonCropp merged 1 commit into
mainfrom
bigmul
Sep 10, 2026
Merged

Add BigMul across the integer types#594
SimonCropp merged 1 commit into
mainfrom
bigmul

Conversation

@SimonCropp

Copy link
Copy Markdown
Owner

Ten members. The audit item said "net9, 8 members"; the real surface is wider and one part of it turned out not to be polyfillable at all.

Member BCL since
Math.BigMul(long, long, out long), Math.BigMul(ulong, ulong, out ulong) net5
int.BigMul, uint.BigMul, long.BigMul, ulong.BigMul net9
Int128.BigMul, UInt128.BigMul, nint.BigMul, nuint.BigMul net11

The two Math out-overloads are net5, not netcoreapp3.0

The docs list these as .NET Core 3.0. The 3.0.0 and 3.1.0 reference assemblies expose only Math.BigMul(int, int) — the 64-bit out-overloads first appear in 5.0.0. I had guarded on NETCOREAPP3_0_OR_GREATER and the netcoreapp3.0/3.1 Split builds failed, which is how it surfaced. So these were missing on every target below net5.0, not just below netcoreapp3.0, and are now polyfilled there with the limb-splitting fallback the runtime itself uses when there is no widening-multiply intrinsic.

Three Math overloads cannot be polyfilled

Math.BigMul(uint, uint), Math.BigMul(long, long) and Math.BigMul(ulong, ulong) have signatures identical to uint.BigMul, long.BigMul and ulong.BigMul. C# emits static extension members onto the enclosing class with no receiver in the signature, so extension(Math) and extension(uint) declaring the same (uint, uint) → ulong is CS0111 — "Type 'Polyfill' already defines a member called 'BigMul' with the same parameter types".

Only one of each pair can ship. I kept the numeric-type members: that yields a complete, consistent set of four (int/uint/long/ulong), where the Math side would have been an incoherent mix — int.BigMul has no Math twin to collide with, so it would have shipped either way. Each of the three affected members carries a //Note: pointing at the omission.

net7.0 floor on the 128-bit results

long.BigMul and ulong.BigMul return Int128/UInt128, which do not exist below net7.0 and which Polyfill does not recreate. Those two, and Int128.BigMul/UInt128.BigMul, are therefore net7.0+. That is a clean floor rather than a hole in the middle of the range, but it is not visible from the #### Int64 section header, so it is noted.

Verified rather than assumed

Every case is reconstructed from the returned halves and compared against a BigInteger product:

  • Math.BigMul(long, long, out long) returns a signed upper half over an unsigned lower half — mixing those up is silently wrong only for negative operands.
  • nint.BigMul splits at 32 or 64 bits depending on IntPtr.Size, so the upper half is not simply a sign extension on a 32-bit process.
  • Int128.BigMul applies the signed correction upper - ((left >> 127) & right) - ((right >> 127) & left) to the unsigned 256-bit product.

The tests use the same BigInteger oracle over an edge-case matrix (0, ±1, ±2, MinValue, MaxValue, alternating bit patterns) and run on every target framework, so on net9.0+ they validate the BCL and below it the polyfill.

Result

API count 1095 → 1105.

Solution clean in Release, Consume clean across all 22 TFMs, tests green on net11.0 (1690), net10.0 (1690), net9.0 (1690), net8.0 (1687), net462 (1652), plus PublicTests, EmbeddedTests, UnsafeTests, NoRefsTests and NoExtrasTests. net7.0 and net5.0 compile but could not be run here — those runtimes are not installed on this machine — though net8.0 exercises the same NET7_0_OR_GREATER && !NET9_0_OR_GREATER branch that carries the Int128/UInt128 polyfills, and net462 exercises the software Math.BigMul path.

Ten members: int.BigMul, uint.BigMul, long.BigMul and ulong.BigMul (net9),
Int128.BigMul, UInt128.BigMul, nint.BigMul and nuint.BigMul (net11), and the two
Math.BigMul out-overloads, which are net5 rather than netcoreapp3.0 as the docs
imply, and were missing on every target below that.

The three net9 Math.BigMul overloads cannot be added. Math.BigMul(uint, uint),
Math.BigMul(long, long) and Math.BigMul(ulong, ulong) have signatures identical
to the numeric-type members above, and C# emits both static extension members
onto the same class, so declaring both is CS0111. The numeric-type set is the
coherent one to keep, and the omission is noted on each of the three.

The four overloads returning Int128 or UInt128 are net7.0 and later, since those
types do not exist below that. Noted, since the api_list section header does not
show it.

Verified against net11 by reconstructing every product from the returned halves
and comparing to BigInteger: Math.BigMul(long, long, out long) returns a signed
upper half over an unsigned lower half, and nint.BigMul splits at 32 or 64 bits
depending on IntPtr.Size. The tests use the same oracle and run on every target,
so they validate the BCL on net9.0 and later and the polyfill below.

API count 1095 -> 1105.
@SimonCropp SimonCropp added this to the 11.3.0 milestone Sep 10, 2026
@SimonCropp
SimonCropp merged commit d8df831 into main Sep 10, 2026
4 of 6 checks passed
@SimonCropp
SimonCropp deleted the bigmul branch September 10, 2026 04:16
This was referenced Sep 10, 2026
This was referenced Sep 11, 2026
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