Skip to content

Subtracting an interval turns it round - #1117

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
interval-subtraction-order
Aug 31, 2026
Merged

Rafael-SOWNet merged 1 commit into
masterfrom
interval-subtraction-order

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

Minusf slid an interval's ends without swapping them, so subtracting one produced an interval whose left end was above its right — which is empty:

"5 - (0; 1)".ToEntity().Simplify()          // was (5; 4), is (4; 5)
"4.5 in (5 - (0; 1))".ToEntity().Simplify() // was False, is True

The second line is the one that matters: a wrong answer reached through the operation an interval exists for.

The openness is the half that is easy to miss

5 - (0; 1] is [4; 5) — the excluded 1 becomes the excluded lower end 4, and the included 0 becomes the included upper end 5.

Was Is
5 - (0; 1) (5; 4) (4; 5)
5 - [0; 1] [5; 4] [4; 5]
5 - (0; 1] (5; 4] [4; 5)
5 - [0; 1) [5; 4) (4; 5]

Swapping the ends and leaving the flags where they were would give (4; 5] for the third row: right about the width and wrong at both ends, which no test on the printed form alone would catch. IntervalSubtractionTest asserts membership as well for that reason.

An interval minus a number was always right and stays right — (0; 1) - 5 is (-5; -4), nothing being reflected. Asserted, so that fixing the other direction cannot break it.

What this does not fix, asserted rather than left implicit

0 - x is negated by an earlier arm, so 0 - [0; 1) is -[0; 1) and stops there. Negating an interval is multiplying one, and Mulf has no interval case at all — (0; 1) * 2 is left alone too. That is #322's remaining half, and it needs a sign analysis this does not: a negative multiplier turns the interval round exactly as subtraction does, and a zero one collapses it to a point.

How it was found

Looking at #322 — "applying a transformation to all elements of a set" — whose body says it will be possible "once we implement quantifiers". It does not need quantifiers. (0; 1) + 1 already answers (1; 2), so Sumf and Minusf have interval cases and Mulf does not. Reading the two that exist, to see how far the capability actually went, is what showed one of them reversing its output.

Recorded in BREAKING-CHANGES.md, both values measured on a build.

Full suite: 9028 passed, 14 skipped, 0 failed.

`Minusf` slid an interval's ends without swapping them, so subtracting one produced an
interval whose left end was above its right -- which is empty:

    "5 - (0; 1)".ToEntity().Simplify()          // was (5; 4), is (4; 5)
    "4.5 in (5 - (0; 1))".ToEntity().Simplify() // was False, is True

The second line is the one that matters: a wrong answer reached through the operation an
interval exists for.

**The openness is the half that is easy to miss.** `5 - (0; 1]` is `[4; 5)` -- the *excluded*
1 becomes the excluded lower end 4, and the *included* 0 becomes the included upper end 5.
Swapping the ends and leaving the flags where they were would give `(4; 5]`: right about the
width and wrong at both ends, which no test on the printed form alone would catch.
`IntervalSubtractionTest` asserts membership as well for that reason.

An interval *minus* a number was always right and stays right, and is asserted so that fixing
the other direction cannot break it.

**What this does not fix**, asserted rather than left implicit: `0 - x` is negated by an
earlier arm, so `0 - [0; 1)` is `-[0; 1)` and stops there. Negating an interval is
multiplying one, and `Mulf` has no interval case at all -- `(0; 1) * 2` is left alone too.

**How it was found.** Looking at #322 ("applying a transformation to all elements of a set"),
whose body says it needs quantifiers. It does not: `(0; 1) + 1` already answers `(1; 2)`, so
`Sumf` and `Minusf` have interval cases and `Mulf` does not. Reading the two that exist to
see how far the capability went is what showed one of them reversing its output.

Recorded in BREAKING-CHANGES.md, both values measured on a build.

Full suite: 9028 passed, 14 skipped, 0 failed.
@Rafael-SOWNet
Rafael-SOWNet merged commit 2daedfe into master Aug 31, 2026
31 checks passed
Rafael-SOWNet added a commit that referenced this pull request Aug 31, 2026
#322's arithmetic half. `Sumf` and `Minusf` had interval cases and `Mulf` and `Divf` had
none, so `(0; 1) + 1` answered `(1; 2)` while `(0; 1) * 2` was handed back. Three rows of
`Core/Sets/Arithmetics` recorded that asymmetry as the expected behaviour, directly beneath
the two addition rows that answer.

    "3 * [2; 3]".ToEntity().Simplify()   // was 3 * [2; 3], is [6; 9]
    "[2; 3] / 2".ToEntity().Simplify()   // was [2; 3] / 2, is [1; 3/2]

**A negative factor reflects the interval**, so its ends swap and their openness swaps with
them, exactly as subtracting one does: `[2; 3) * (-1)` is `(-3; -2]`. That is the half a test
on the printed form alone would not catch -- swapping the ends and leaving the flags where
they were gives the right width and the wrong bracket at both ends -- so membership is
asserted too.

`0 - [0; 1)` is now `(-1; 0]`, and it is not a subtraction at all: `0 - x` is negated by an
earlier arm, so it reaches `Mulf` as `-1 * [0; 1)`. It is the case #1117 had to leave out, and
it comes back for free.

**An unknown sign is answered by not answering.** `(0; 1) * k` for a symbolic `k` is one
interval when `k` is positive and the reflected one when it is negative, so picking either
would be choosing which.

Two boundaries, asserted rather than left to be discovered. `(0; 1) * 0` still answers the
number `0` rather than the set `{ 0 }` -- that arm is over every `Entity` and not only
intervals, and moving it would change matrices and finite sets with it. And `2 / (0; 1)` is
left alone: a constant over an interval straddling zero is two unbounded pieces rather than
one interval, so there is no `Interval` to answer with.

#322 says this "will be possible once we implement quantifiers". It needs none -- scaling is
monotone in the factor's sign and in nothing else. Applying a *non-monotonic* function to an
interval is the issue's other half and is still open.

Recorded in BREAKING-CHANGES.md, both values measured on a build.

Full suite: 9059 passed, 14 skipped, 0 failed.

Part of #322.
Rafael-SOWNet added a commit that referenced this pull request Sep 18, 2026
…he images of its ends, and a product or quotient of two intervals is an interval (#1423)

What #322 has left after #1117 and #1118: a function applied to an interval, and an interval
times an interval. A function monotone on an interval maps it to the interval between the
images of its ends -- in the same order when increasing, reversed when decreasing -- each end
attained exactly when the end it comes from is, and an infinite image never attained:
ln((0; 1)) is (-oo; 0), and so is ln([0; 1)), since ln(0) is not a value. Answered for the
functions the library has nodes for on the intervals where they are monotone: ln and log for
any positive base but 1 on the positives, b^I for such a base everywhere, I^n for a whole n
(odd everywhere; even on either side of zero and folded across it; negative through the
reciprocal where zero is outside), I^(1/n) from zero on, abs, arctan everywhere, arcsin and
arccos on [-1; 1]. A product of two intervals with finite numeric ends is the interval between
the least and the greatest of the four products of ends, an end attained where both its
factors are; a quotient is the product by the reciprocal, which is an interval exactly when
the divisor does not contain zero, and that also answers a constant over such an interval,
which #1118 left. Symbolic ends and factors, a sine over an interval, a root of a negative
interval and a divisor around zero are left as written.

Recorded in BREAKING-CHANGES.md against a v2.5.0 build, where every one of these was left as
written. Full suite green; benchmark gate PASSED.

Closes #322.


Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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