Let a connective settle what its truth table settles (#880) - #907
Merged
Conversation
Simplify and evaluation disagreed about three-valued logic. Simplify gave the Kleene answer and evaluation absorbed everything into NaN, so "True or (True and (x < 0))" simplified to True and evaluated at x := i to NaN -- the same expression, two answers, and one of them claiming the thing does not exist. The tables were already three-valued. Andf reads (_, Boolean(false)) as False and (Boolean(true), _) as its right operand, which is Kleene as written. What overrode them was a single line in the shared ExpandOnTwoArguments -- if (left.IsNaN || right.IsNaN) return MathS.NaN -- running before the table was consulted. The four connectives now get first refusal on an undefined operand, through a settlesNaN flag, and hand back null where they cannot settle it, which is what still reaches NaN. Checked row by row against Kleene's tables first: and, or, implies and xor each already give the right answer for an unknown operand, including leaving it unknown where it decides the result. Opted in per node rather than changed in the helper for everything, because arithmetic must stay strict: a rule for a zero factor exists and NaN * 0 must not reach it. Measured, not assumed -- (0/0) * 0, (0/0) + 1 and (0/0) - (0/0) are all still NaN. #880 set this out as a fork between Kleene and strict and left it open for want of one measurement: how much of the suite pins strict propagation. The answer is one assertion of 6385, and it is that issue's own guard clause -- the row asserting `x < 0 and x = 0` is NaN at x = i, written to keep the test from going vacuous if a comparison ever gained a truth value. It has gained one indirectly: `i = 0` is decidably False, and False and u is False. That row moves to a test of its own, which also records the consequence: Simplify answers `False provided x in RR` there, and the condition is over-strong for that reduction, which needs one conjunct false rather than both operands real. So Simplify is now weaker than evaluation on it rather than stronger. The rules #876 conditioned want going through one at a time to see which still need it; that is not this change. Suite 6389 passed, F# wrapper 130 passed; casbench 117/119 with 0 wrong; rootcheck 596/596; simpsweep 10463/10463; propcheck 1340 checks 0 failures; crashcheck 1652 cases 0 crashes and 0 unexpected throws; boundcheck unchanged at 2 disagreements. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #880 — which set this out as a fork and left it open for want of one measurement. This PR is
that measurement plus the branch it favours; say the word and I will close it instead.
The contradiction
Simplifygave the Kleene answer and evaluation absorbed everything intoNaN, so the same expressionhad two:
i < 0has no truth value: the default codomain isDomain.Complex, and the complex numbers are notordered. What changes here is only what a connective does with such an operand.
(i < 0) and FalseNaNFalse(i < 0) or TrueNaNTrueFalse implies (i < 0)NaNTrue(i < 0) implies TrueNaNTrue(i < 0) and TrueNaNNaN, unchanged(i < 0) or FalseNaNNaN, unchangednot (i < 0)NaNNaN, unchanged(i < 0) xor (i < 0)NaNNaN, unchanged(0/0) * 0,(0/0) + 1,(0/0) - (0/0)NaNNaN, unchangedIt is one line, and the tables were already right
Andfreads(_, Boolean(false))asFalseand(Boolean(true), _)as its right operand — Kleene aswritten. What overrode them is a single line in the shared
ExpandOnTwoArguments:running before the table is consulted. The four connectives now get first refusal on an undefined
operand through a
settlesNaNflag, and hand backnullwhere they cannot settle it — which is whatstill reaches
NaN. So #880's prediction holds exactly: under Kleene, no rule needs touching.I checked all four tables row by row against Kleene's before enabling them, rather than assuming:
and,or,impliesandxoreach already give the right answer for an unknown operand, including leaving itunknown where it genuinely decides the result (
u and True,u xor u).Opted in per node rather than changed in the helper for everything, because arithmetic must stay
strict. A rule for a zero factor exists, and
NaN * 0must not reach it. Measured with a computed NaNrather than argued — the literal
NaNtoken turns out to parse as a variable, which is its own defectand is now #906.
The measurement #880 asked for
One assertion of 6385, and it is that issue's own guard clause. The row asserting
x < 0 and x = 0is
NaNatx = iwas written for #876 to keep the test from going vacuous if a comparison ever gaineda truth value. It has gained one indirectly:
i = 0is decidablyFalse, andFalse and uisFalse.Everything else is unchanged: suite 6389 passed / 0 failed, F# wrapper 130 passed, casbench 117/119 with
0 wrong, rootcheck 596/596, simpsweep 10463/10463, propcheck 1340 checks with 0 failures, crashcheck
1652 cases with 0 crashes, boundcheck unchanged at 2 disagreements.
One consequence, recorded rather than fixed
That row now moves the disagreement to the other side. The evaluator settles
Falsefor everyx,while
SimplifyanswersFalse provided x in RR— and for this reduction the condition is over-strong:it needs one conjunct false, not both operands real. So
Simplifyis now weaker than evaluation thererather than stronger, which is the opposite of #876's original defect and not a regression in either.
It is pinned by a test that says so, because the rules #876 conditioned want going through one at a time
to see which of them still need the condition under Kleene. That is a separate change and I have not
made it here.
What this does not touch
Excluded middle, which #880 flags as surviving either fork:
p or not pneedspto have a truthvalue, and Kleene does not supply one, so
NaN or not NaNis stillNaNand #876's conditioning ofthose is still the right shape. Its tests are unchanged and passing.
Cut from
masterat8c56b59f.