Skip to content

RewriteRules.Power rewrites ln(1/x) to -ln(x), which is false on the negative reals #1062

Description

@Rafael-SOWNet

PowerRules has three arms that move a reciprocal through a logarithm:

Logf(Divf(Integer(1), var any1), Divf(Integer(1), var any2)) => MathS.Log(any1, any2),
Logf(var any1, Divf(Integer(1), var any2)) => -MathS.Log(any1, any2),
Logf(Divf(Integer(1), var any1), var any2) => -MathS.Log(any1, any2),

All three are unconditional, and ln(1/b) = -ln(b) is false on the negative reals. The principal argument does not negate with its logarithm:

at x = -0.63
ln(1 / x) 0.462 + 3.1416i
-ln(x) 0.462 - 3.1416i

Measured, not derived: RewriteRules.Power.ApplyOnce("ln(1 / x)") is -ln(x), and the arm was attributed by asking every rule in RewriteRules.Power.Rules which one reproduces it — PowerRules:198.

The guard already exists, ten lines below

// ln(a) + ln(b) = ln(a*b), and the difference likewise, are false off the positive
// reals: at x = -3 the sum of ln(x) and ln(x+1) exceeds ln(x*(1+x)) by 2*pi*i, the
// turn of the argument the principal branch discards. Both were applied
// unconditionally, and that was the last disagreement `boundcheck` reported.
Sumf(Logf(var any3, var any1), Logf(var any3a, var any2)) when any3 == any3a
    && MayGatherLogarithms(any1, any2, isDifference: false) => ...

Same defect, same file, fixed for one pair and not for its three neighbours. ln(1/b) is ln(1) - ln(b), so a reciprocal is the difference case with a numerator of 1 and the existing helper answers it directly — including its second way in, where the limit machinery has established the sign on the way to a destination.

What it does and does not break

Simplify does not produce it — "ln(1 / x)".Simplify() is ln(1 / x), so no caller sees a wrong answer today; the candidate search does not pick that branch.

A caller applying RewriteRules.Power on its own does, and that is what #746 tier 2 makes possible: rule sets as first-class data that a caller may reach for one at a time.

Why it was not found before

work/rulecheck samples nothing that builds log(_, 1/_): its level-3 corpus was a stride-13 sample of level 2. Widened to every level-2 shape, it reports all three arms. boundcheck did not find it either, because it measures Simplify — which, as above, declines the rewrite.

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