Skip to content

An expression can be written as a single fraction: AsSingleFraction - #1622

Merged
Rafael-SOWNet merged 5 commits into
masterfrom
as-single-fraction
Oct 4, 2026
Merged

Rafael-SOWNet merged 5 commits into
masterfrom
as-single-fraction

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

Entity.AsSingleFraction(), Transformation.AsSingleFraction and "…".AsSingleFraction() write an expression as one numerator over one denominator, with nothing divided inside either. That is what a textbook calls writing it as a single fraction. The name is the one Happypig375 chose on #1239.

  • Any expression. Functions are carried whole, and their arguments are not gathered: sin(x) + 1/cos(x) is (1 + cos(x) * sin(x)) / cos(x).
  • Over the least common denominator as written. 1/x + 1/x^2 is (x + 1) / x ^ 2, not over x^3, and 2/3 + x/2 is over 6. Nothing is factorised to find it, so x^2 - 1 and x + 1 share no factor.
  • Nothing cancelled or multiplied out. x/x stays x / x, and 1/(t^2+1) + 1/(t+1) keeps its denominator as (t ^ 2 + 1) * (t + 1). A rational number counts as the fraction it is: 2/3 + x/2 is (4 + 3 * x) / 6. Each half is tidied the way Simplify tidies, without its search: like terms are collected and operands put in order, so 1/(x^2 - 1) + 1/(x + 1) is (x + x ^ 2) / ((x ^ 2 - 1) * (x + 1)).
  • Defined exactly where the expression is. Dividing by a fraction moves its denominator into the numerator, where it no longer stops the answer having a value, so the answer says the denominator is nonzero: 1/(1/x) is x provided not x = 0, and (a/b)/(c/d) is a * d / (b * c) provided not d = 0. This is O4 of the simplification contract. Where the new denominator still contains it, nothing is added: (1/x)/(1/x) is x / x.

CanonicalizeAsRationalFunction remains the canonical form for a rational function (lowest terms, denominator multiplied out and monic), and the docs point there. On #1239 I proposed modelling the two as one pipeline: single fraction, then lowest terms, then normalise, rather than one operation with a cost setting. The canonical form has the same domain gap on division by a fraction, filed as #1618, and the fix proposed there is to gather through this first step.

SingleQuotient.Of(expr) keeps the product of the denominators, so its callers see no change: the integrator, partial fractions, the inequality solver and the quantifiers. SingleQuotient.OverLeastCommonDenominator(expr, carried) is the new entry this uses.

The API is additive, so there is no BREAKING entry (as for #934).

Suite, net10.0, at 9e23f639 on master 8cb650e2: 14106 passed, 0 failed.
Gate: allocation is what the baseline says on all 19 gated benchmarks.

Closes #1239.

🤖 Generated with Claude Code

https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

Rafael-SOWNet and others added 4 commits September 30, 2026 10:52
Entity.AsSingleFraction(), with the string extension and Transformation.AsSingleFraction,
writes an expression as one numerator over one denominator, nothing divided inside either:
1 + 2/(1+t^2) is (t^2 + 3)/(t^2 + 1). Any expression, functions included, and nothing
cancelled or multiplied out, so the denominator's factors stay and x/x stays x/x; a rational
number counts as the fraction it is written as. Simplify never does it, and for a rational
function in lowest terms Transformation.RationalCanonicalization is the canonical form. The
name is the textbook's, "as a single fraction", as asked on #1239.

Closes #1239.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
Dividing by a fraction moves its denominator into the numerator, where it
no longer stops the answer having a value: 1/(1/x) gathers to x, which is 0
at x = 0 where the expression has no value, and (a/b)/(c/d) to a d/(b c),
which is 0 at d = 0. That is the one thing the simplification contract
forbids (O4), so the answer now says so: x provided not x = 0.

SingleQuotient.Of gains an overload that records each denominator it
carries across; the one-argument form passes nothing and is unchanged for
its callers. A denominator the new one still contains is not repeated, so
(1/x)/(1/x) stays x / x.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
As it is by hand: 1/x + 1/x^2 is (x + 1)/x^2, where the product of the
denominators put it over x^3 with a factor x in both halves. The common
denominator is taken from the denominators as they are written -- each
factor to the highest power any of them has it, and the whole numbers by
their least common multiple -- so nothing is factorised to find it, and
x^2 - 1 and x + 1 share nothing.

SingleQuotient.OverLeastCommonDenominator is the entry AsSingleFraction
uses; SingleQuotient.Of keeps the product for its callers, which divide
out afterwards, and is unchanged for them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
The tidying pass writes x^2 - 1 as x ^ 2 + -1, and Simplify's last step,
NumericNeat, writes it back. So 1/(x^2 - 1) + 1/(x + 1) is
(x + x ^ 2) / ((x ^ 2 - 1) * (x + 1)), and every other written form here
is as it was.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
@Rafael-SOWNet
Rafael-SOWNet merged commit 6bbed69 into master Oct 4, 2026
34 checks passed
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.

Nothing writes an expression as a single quotient: no together / ratsimp

1 participant