Skip to content

DomainCondition says log(-3, -3) is defined nowhere and it evaluates to 1; log(1, 1) is 0 and should be NaN #890

Description

@Rafael-SOWNet

Entity.DomainCondition and evaluation disagree about where log is defined, in both directions, and one of the disagreements is a wrong answer.

Measured on master (f2594259), .NET 10, default MathS.Settings.Codomain (Domain.Complex):

expression DomainCondition Evaled what it should be
log(b, x) b > 0 and not b = 1 and x > 0
log(-3, -3) False 1 1 — the condition is wrong
log(-1, -1) False 1 1 — likewise
log(1, 1) False 0 NaN — the value is wrong
log(1, 2) False +oo unsigned/complex infinity
log(0, 2) False 0
log(2, 0) False -oo

Two separate defects, and they point opposite ways.

1. The declared domain is too small

log_b(z) is ln z / ln b, and over the complex plane that is defined and single-valued whenever
z != 0 and ln b != 0 — so for a negative base and argument too: ln(-3)/ln(-3) is exactly
1. DomainCondition says False, i.e. "this expression has a value nowhere", while Evaled
returns 1 and Simplify returns 1.

Logf's IntrinsicCondition is written for the real reading (b > 0, x > 0) while the default
codomain is complex, and nothing reconciles the two. sqrt goes the other way — DomainCondition
of sqrt(x) is True, the complex reading — so the two functions do not even agree with each
other.

Why it matters beyond tidiness. DomainCondition is what a rewrite is supposed to consult to
decide whether it may fire and what condition to attach; that is obligation O5 in
SimplificationContract.md. A rule that
trusts it inherits the over-strong condition, which is exactly what has happened to
log_b(b) -> 1:

"log(x, x)".Simplify()   =>   1 provided x > 0

At x = -3 the expression is 1 and the simplification says it is undefined. That is the mirror of
the usual defect — it turns a value into undefined rather than the other way round — and it was
found by work/boundcheck, which is why this issue exists.

2. log(1, 1) is 0, and should be NaN

ln 1 is 0 exactly, so log_1(1) is 0/0. SymPy gives nan and Mathematica Indeterminate.
Returning 0 is a wrong answer in the sense AGENTS.md
means it: a definite value where none exists. It is not shielded by the domain condition, which
already excludes b = 1 — evaluation simply does not consult it.

log(1, 2) giving +oo is a milder version of the same thing: dividing by ln 1 = 0 has no signed
answer, and both SymPy and Mathematica report an unsigned complex infinity.

What needs deciding, and what does not

The wrong answer at log(1, 1) needs no decision — 0/0 is not 0.

The domain is a decision, and it is the one #721
is about: DomainCondition is fixed at construction and cannot read
MathS.Settings.Codomain, so it cannot say "defined here, given that we are doing real analysis".
Either it becomes codomain-aware, or it commits to the complex reading and the real-analysis
restriction moves to where the codomain is known. Picking one is a maintainer's call; what is not in
question is that a single expression should not be declared undefined everywhere and simultaneously
evaluate to 1.

Related: #887 records the log(x, x)
symptom, and #884 is the other logarithm
rule whose assumption is not stated.

Activity

  1. Happypig375 commented on Aug 11, 2026

    @Happypig375
    Member

    Yes, it should become codomain-aware.

  2. Rafael-SOWNet commented on Aug 12, 2026

    @Rafael-SOWNet
    MemberAuthor

    The domain half of this is answered on #721 with what SymPy and Mathematica actually do, measured rather than recalled: neither carries a real-analysis domain on the expression. SymPy evaluates log(-3, -3) to 1 exactly as this library does, and gets the real domain from continuous_domain(f, x, S.Reals) — a query with the reading as an argument. Mathematica's FunctionDomain[f, x, dom] is the same shape, defaulting to Reals.

    So the fork's second option — parameterise the query by the reading — is the ordinary design elsewhere, and it also makes this library's log(x, x) -> 1 provided x > 0 stronger: over ℂ the condition is not x = 1 and not x = 0, which removes one of boundcheck's two remaining disagreements. The comment on #721 lists what else it wins back, including the two limits #902 lost.

  3. Rafael-SOWNet commented on Aug 16, 2026

    @Rafael-SOWNet
    MemberAuthor

    Re-measured on master at 2d128f3c. Both defects are fixed, by #916 (51194ce8, "Let the logarithm's domain follow the reading (#721, #890)"), and this looks closeable.

    expression DomainCondition then now Evaled then now
    log(b, x) b > 0 and not b = 1 and x > 0 not b = 0 and not b = 1 and not x = 0
    log(-3, -3) False True 1 1
    log(-1, -1) False True 1 1
    log(1, 1) False False 0 NaN
    log(1, 2) False False +oo NaN
    log(0, 2) False False 0 0
    log(2, 0) False False -oo -oo

    The condition now reads

    Codomain < Domain.Complex
      ? Base > 0 & !Base.EqualTo(1) & Antilogarithm > 0
      : !Base.EqualTo(0) & !Base.EqualTo(1) & !Antilogarithm.EqualTo(0)

    which is @Happypig375's "it should become codomain-aware" done literally — the real reading under a real codomain, the complex one otherwise. So log(-3, -3) is now declared defined and still evaluates to 1, and log(1, 1) is NaN rather than the wrong 0.

    On the two rows that still show a disagreement. log(0, 2) evaluates to 0 and log(2, 0) to -oo while the condition says False for both. That is deliberate, and the reason is now written where the decision is:

    The zero cases are stated rather than read off evaluation, which returns the extended-real -oo for ln(0) and so would call it defined. -oo is not a complex number, and taking it for one loses conditions that are needed downstream: ln(x) * 0 is 0 only away from x = 0, since -oo * 0 is NaN.

    So evaluation returning a value is not the definedness oracle, and those two rows are the condition being right rather than the two disagreeing.

    One row does not match what the issue asked for, and it is minor: log(1, 2) is NaN, where the issue said it should be unsigned/complex infinity. ln 2 / ln 1 is ln 2 / 0, so an unsigned infinity is defensible and NaN is the library's usual answer for a division by zero that is not a limit. If that is worth changing it is a smaller and separate question than this issue was — happy to file it if you want it tracked.

    Closing is yours; I have not changed anything here.

  4. Rafael-SOWNet commented on Aug 23, 2026

    @Rafael-SOWNet
    MemberAuthor

    Both defects are fixed. Re-measured on master at 3ac24bc2, same settings — default MathS.Settings.Codomain (Domain.Complex):

    expression DomainCondition then now Evaled then now
    log(b, x) b > 0 and not b = 1 and x > 0 not b = 0 and not b = 1 and not x = 0
    log(-3, -3) False True 1 1
    log(-1, -1) False True 1 1
    log(1, 1) False False 0 NaN
    log(1, 2) False False +oo NaN
    log(0, 2) False False 0 0
    log(2, 0) False False -oo -oo

    §1 — the declared domain was too small. It is now the complex reading this issue asked for: not b = 0 and not b = 1 and not x = 0, which admits a negative base and a negative argument. And the rule that inherited the over-strong condition inherits the right one:

    "log(x, x)".Simplify()
      was:  1 provided x > 0                       -- undefined at x = -3, where it is 1
      now:  1 provided not x = 0 and not x = 1
    

    §2 — log(1, 1) was 0. It is NaN.

    On log(1, 2)

    I suggested unsigned/complex infinity. It is NaN, and I now think that is the better answer here rather than a shortfall, because it is the one this library already gives for the same arithmetic:

    1/0   =>  NaN        -1/0  =>  NaN        0/0  =>  NaN
    ln(1) =>  0          ln(0) =>  -oo
    

    log_1(2) is ln 2 / ln 1 = ln 2 / 0, so it lands wherever 1/0 lands. Adopting zoo for this one function would have made it disagree with division. The remaining False domain conditions against a returned 0 or -oo are the separate and deliberate distinction that -oo is a value but not a complex number -- evaluation is not the definedness oracle.

    Closing as fixed unless you read those last two rows differently. Found while sweeping the open issues before proposing a release, so this was measured rather than assumed.

  5. Rafael-SOWNet commented on Aug 23, 2026

    @Rafael-SOWNet
    MemberAuthor

    Re-measured on master at 6b93b401 (2.3.0), .NET 10, default Domain.Complex. Both defects named in the title are fixed — by #893 (log behaving like the division it is defined as) and #916 (the logarithm's domain following the reading):

    expression DomainCondition then now Evaled then now
    log(-3, -3) False True 1 1
    log(-1, -1) False True 1 1
    log(1, 1) False False 0 NaN

    So §1 (the declared domain too small for a negative base and argument) and §2 (log(1, 1) answering 0 where nothing is there) are both answered, in the directions the issue argued for.

    What is left is narrower than the title and is still a disagreement. Two rows where DomainCondition says the expression has a value nowhere and evaluation returns one:

    log(0, 2)     DomainCondition = False     Evaled = 0
    log(2, 0)     DomainCondition = False     Evaled = -oo
    

    Both come from reading ln(0) as -oo and then dividing, which is a limit rather than a value — so here it is arguably evaluation that is over-eager rather than the domain that is over-strict, which is the opposite of §1's diagnosis. And one row moved without being asked to: log(1, 2) was +oo and is now NaN. ln(2)/ln(1) is complex infinity, which this library has no way to say (#217), so NaN and +oo are both wrong and NaN is the less wrong of the two — worth recording rather than leaving as a silent change.

    Keeping this open for that residue. Closing it would claim the domain and evaluation now agree about log, and on those three rows they do not.

  6. Rafael-SOWNet commented on Aug 27, 2026

    @Rafael-SOWNet
    MemberAuthor

    Re-measured on master at a2edfc33 during a pre-release sweep. Both defects in the title are
    gone.

    expression DomainCondition Evaled this issue said
    log(-3, -3) True 1 condition was False — wrong
    log(1, 1) False NaN value was 0 — wrong
    log(b, x) not b = 0 and not b = 1 and not x = 0 was b > 0 and not b = 1 and x > 0

    Logf.IntrinsicCondition states both readings now and selects on the node's own Codomain:
    the complex one asks only that both logarithms exist and that the denominator is not zero, which
    is what makes a negative base and a negative antilogarithm fine.

    And the consequence this issue called out — "log(x, x)".Simplify() giving 1 provided x > 0,
    undefined at x = -3 where the expression is 1 — is gone with it. The rule reads the node's
    own DomainCondition rather than stating a reading inside itself, so it follows the reading
    instead of fixing one.

    One thing left, and it is not what the title is about

    domain(log(x, x)) is not x = 0 and not x = 1 and not x = 0 — the same conjunct twice,
    because base and antilogarithm are the same expression. It is a tidiness matter rather than a
    wrong answer, and it is not free to fix: Andf.InnerSimplify already does idempotence for the
    immediate pair (left == right ? left), but a and b and a is Andf(Andf(a, b), a) and
    those two are not equal. Collapsing it wants a dedup over Andf.LinearChildren, which is on
    the InnerSimplified path that DomainCondition itself calls — so it needs a measurement
    attached, not just a patch.

    Closing is yours; flagging it because a release is being prepared and this reads as an open
    wrong-answer report while it is not one any more.

  7. Rafael-SOWNet commented on Aug 31, 2026

    @Rafael-SOWNet
    MemberAuthor

    Measured on master at 9d9b21e4, .NET 10, default settings. Both halves of the title now answer correctly.

    log(1, 1).Evaled            = NaN
    log(1, 1).Simplify()        = NaN
    log(1, 2).Evaled            = NaN
    log(-3, -3).Evaled          = 1
    log(-3, -3).DomainCondition = True
    log(x, x).Simplify()        = 1 provided not x = 0 and not x = 1
    log(b, x).DomainCondition   = not b = 0 and not b = 1 and not x = 0
    

    log(1, 1) is NaN rather than 0, and log(-3, -3)'s domain condition is True rather than a condition that holds nowhere — so the value 1 and the domain agree.

    The general condition is the one the issue argued for: a base of 0 or 1 and an argument of 0 are excluded, and nothing else is. log(x, x) carries exactly those two exclusions rather than being answered 1 unconditionally.

    Closing as fixed. If any of the above is not what you read as the correct answer, reopening is cheap — the measurement is one probe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions