Skip to content

Compiled combinatorial functions at whole arguments are exact - #2218

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
whole-arguments-compile-exactly
Oct 30, 2026
Merged

Rafael-SOWNet merged 1 commit into
masterfrom
whole-arguments-compile-exactly

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

Part of #2195, item 3: a follow-up to #2213 and #2216.

At whole arguments, the compiled binomial, falling and rising factorials, double factorial and Catalan numbers are now evaluation's exact value, rounded once to a double.

The binomial took a whole lower argument as a product of ratios in doubles, (n - i)/(i + 1), and each ratio was rounded. So binomial(54, 45) came out 5317936259.999995 where evaluation gives 5317936260. Of the 2485 binomials with 0 <= k <= n < 70, 1173 were not the integer. Now it uses binomial(m, i + 1) = binomial(m, i) (m - i)/(i + 1), whose division leaves no remainder, over the shorter of k and m - k. A negative n goes through (-1)^k binomial(k - n - 1, k).

The factorial powers and the double factorial are products of whole factors, and were rounded at each step once a partial product passed 2^53. Each is now computed in doubles while every partial product is below 2^53, where every step is exact, and in EInteger past it. The result is rounded by EFloat, or by ERational for a negative order's reciprocal. A factor of 0 is checked for first, so a partial product past the range of the doubles stays past it, and the loop stops there.

The Catalan numbers (#2216) were computed exactly but rounded by (double)BigInteger, which truncates. 246 of the first 1000 came out a unit low in the last place: C_32 = 55534064877048200 compiled to 55534064877048190. They are rounded by EFloat now. Nothing else in the library rounds a BigInteger to a double for a result.

master now
"binomial(x, 45)".ToEntity().Compile<double, double>("x")(54) 5317936259.999995 5317936260
"binomial(x, 30)".ToEntity().Compile<double, double>("x")(60) 1.1826458156486146E+17 1.1826458156486142E+17
"catalannumber(x)".ToEntity().Compile("x").Call(32) (55534064877048190, 0) (55534064877048200, 0)
"x!!".ToEntity().Compile("x").Call(-301) (2.6643404405685313E-307, 0) (2.6643404405685293E-307, 0)

Each test compares FastExpression and the LINQ compiler with evaluation, as doubles and exactly:

  • every two-argument function on a grid of 90 whole first arguments, up to 10^12, and 58 whole second arguments from -12 to 45;
  • the binomial past 2^53 and past the doubles, among others at (60, 30), (1100, 550), (3000, 1500) and (-1, 4001);
  • the double factorial from -301 to 320;
  • the Catalan numbers from 0 to 1000.

The expected value is the exact rational, rounded once by ERational. ToNumerics goes through a decimal whose exponent has a floor, and reads 1/299!! as 0. On master's kernel, 9 of these tests fail.

The combinatorial entry in BREAKING-CHANGES.md, unreleased, now says so and gains the binomial(54, 45) row.

🤖 Generated with Claude Code

The compiled binomial took a whole lower argument as a product of ratios
in doubles, (n - i)/(i + 1), each rounded, so binomial(54, 45) came out
5317936259.999995 where evaluation gives 5317936260. Of the 2485
binomials with 0 <= k <= n < 70, 1173 were not the integer. The factorial
powers and the double factorial, products of whole factors, rounded at
each step once a partial product passed 2^53, and so were off in the last
places from there.

At whole arguments each is now computed exactly and rounded once:
- the binomial by binomial(m, i + 1) = binomial(m, i) (m - i)/(i + 1),
  whose division leaves no remainder, over the shorter of k and m - k,
  and for a negative n by (-1)^k binomial(k - n - 1, k);
- the products in doubles while each partial product is below 2^53,
  where every step is exact, and past it in EInteger, rounded by EFloat
  or, for a negative order's reciprocal, by ERational.

The Catalan numbers were computed exactly, in #2216, and rounded by
(double)BigInteger, which truncates: 246 of the first 1000 were a unit
low in the last place, C_32 = 55534064877048200 coming out
55534064877048190. They are rounded by EFloat now. Nothing else in the
library rounds a BigInteger to a double for a result.

A factor of 0 is checked for before the product, so a partial product
past the doubles' range is known to stay past it, and the loop stops
there.

Checked against evaluation exactly, as doubles, in both compilers: every
two-argument function on a grid of 90 whole first arguments, up to 10^12,
and 58 whole second arguments from -12 to 45; the binomial past 2^53 and
past the doubles, among others at (60, 30), (1100, 550), (3000, 1500) and (-1, 4001);
the double factorial from -301 to 320; and the Catalan numbers from 0 to
1000. The expected value is the exact rational rounded once by ERational,
since ToNumerics goes through a decimal whose exponent has a floor, and
reads 1/299!! as 0. On master 9 of these tests fail.

Part of #2195.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Rafael-SOWNet Rafael-SOWNet moved this from Todo to In review in AngouriMath Oct 30, 2026
@codecov

codecov Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.22642% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.65%. Comparing base (b21f383) to head (552d23a).

Files with missing lines Patch % Lines
...ces/AngouriMath/Numerics/CombinatorialFunctions.cs 96.22% 0 Missing and 2 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #2218   +/-   ##
=======================================
  Coverage   86.64%   86.65%           
=======================================
  Files         306      306           
  Lines       67494    67536   +42     
  Branches    19566    19587   +21     
=======================================
+ Hits        58482    58522   +40     
+ Misses       4898     4897    -1     
- Partials     4114     4117    +3     
Flag Coverage Δ
cpp 37.77% <ø> (ø)
csharp 87.20% <96.22%> (+<0.01%) ⬆️
fsharp 32.97% <ø> (ø)
interactive 40.72% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...ces/AngouriMath/Numerics/CombinatorialFunctions.cs 94.78% <96.22%> (-1.11%) ⬇️

... and 6 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Rafael-SOWNet
Rafael-SOWNet merged commit 8707aae into master Oct 30, 2026
35 checks passed
@github-project-automation github-project-automation Bot moved this from In review to Done in AngouriMath Oct 30, 2026
@Rafael-SOWNet
Rafael-SOWNet deleted the whole-arguments-compile-exactly branch October 30, 2026 03:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant