Make a logarithm behave like the division it is defined as (#890) - #893
Merged
Conversation
log_b(z) is ln(z)/ln(b), and three answers did not follow from that. log(1, 1) was 0. It is 0/0, and every division by zero in this library is NaN -- 0/0, 2/0 and -2/0 all are. The cause was the real shortcut in Number.Log: LogN answers 0 for log_1(1) and +oo for log_1(2), where falling through to Ln(x)/Ln(base) gives NaN for both. A base of 1 is now excluded from the shortcut, so the two paths agree. log(b, 1) was 0 for any base at all, including 1. It is 0/ln(b), which is 0 for every base but 1, so the answer carries `not b = 1`. A condition is right here, unlike the interval cases in #884: at b = 1 the expression genuinely is undefined rather than merely something else. log(b, 0) was -oo for any base. It is -oo/ln(b), so the sign of the answer is the sign of ln(b): -oo above 1 and +oo between 0 and 1, which made log(1/2, 0) wrong in exact form. It now answers where the base can be placed on one side of 1 and leaves the node alone otherwise, there being no signed answer to give for a base it cannot place. boundcheck gains the six degenerate logarithm shapes, and -- more usefully -- the two points that make a rule's own arithmetic rather than its branch degenerate, x = 1 and x = 0. All 366 existing shapes had passed at 23 points chosen for branch cuts and principal intervals; the two new points immediately turned up abs(sgn(x)) and sgn(abs(x)) both simplifying to 1 where they are 0, filed as #892. Measured: 6280 C# tests pass, 130 F# tests pass, casbench 117/119 with 0 wrong, propcheck 1340 checks with 0 failures, rootcheck 596/596, boundcheck unchanged at the five master already had.
Two conflicts, both additive on each side, so both sides are kept. BREAKING-CHANGES.md: #888's arccotan row and section went in at the same anchors as the logarithm ones. The arccotan entries come first, continuing the inverse-trigonometric entry above them. SimplificationRegressionTest.cs: #888's arccotan cases and this branch's logarithm cases were appended at the same point.
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.
log_b(z)isln(z) / ln(b). Three answers did not follow from that, and one of them was0whereevery division by zero in this library is
NaN.Closes the second half of #890 — the wrong answers. The first half, the disagreement between
DomainConditionand evaluation, needs a decision and is left open.What changed
log(1, 1)0NaN— it is0/0log(1, 2)+ooNaN— dividing byln 1 = 0has no signed answerlog(b, 1)0for any base, including 10 provided not b = 1log(1/2, 0), any base below 1-oo+oolog(2, 0), any base above 1-oo, unchangedlog(x, 0)symbolic-oo0/0,2/0and-2/0are allNaNhere, so the library already has a settled convention fordividing by zero and the logarithm simply was not following it.
Causes, which are two different ones
Number.Loghad a shortcut that disagreed with its own fallback. For a positive real base it usesEDecimal.LogN, andLogNanswers0forlog_1(1)and+ooforlog_1(2)— where falling throughto
Ln(x)/Ln(base), three lines below, givesNaNfor both. A base of1is now excluded from theshortcut, so the two paths agree. That is the whole fix for the first two rows: no new arithmetic,
just stop taking a shortcut that is wrong at one point.
Two arms of
Logf.InnerSimplifystated no assumption.log(b, 1) -> 0is0/ln(b), which is0for every base but
1;log(b, 0) -> -oois-oo/ln(b), whose sign follows the sign ofln(b),so it is
+oofor a base between 0 and 1.A condition is right for
log(b, 1), and this is worth being explicit about because the twoprevious fixes in this family went the other way. In #884 and #887 attaching a condition would have
been wrong, because
arcsin(sin(3))is defined and merely has a different value. Here, atb = 1, theexpression is genuinely undefined — so narrowing the domain is what the mathematics says, and
0 provided not b = 1is the honest answer. The contract's §3 is the distinction being applied.For
log(b, 0)with a base whose side of 1 cannot be read, there is no signed answer to give, so thenode is left as written.
boundcheck gains the points that would have caught this
The harness had 366 shapes passing at 23 points chosen for branch cuts and principal intervals —
and none of them was
x = 1, solog(x, 1) -> 0looked sound at every point tried. That is a real gapin the harness, not just in the rule: "boundary" has to include the points where an identity's own
arithmetic degenerates, not only where a branch is crossed.
Added
x = 1andx = 0, plus the six degenerate logarithm shapes. The two new points immediatelyturned up two more wrong answers, filed as #892:
That is the third instance of one shape after #884 and #887 — an identity that holds off a thin set,
written as though it held everywhere. Not fixed here; #892 has the analysis, including that the
neighbouring
sgn(z)*|z| -> zin the same file is sound at zero, so that file has the samesound-half/unsound-half split as the other two.
Measured on this branch, .NET 10
casbenchpropcheckrootchecksimpsweepboundcheckTen new regression cases:
log(1, 1)isNaNthrough bothSimplifyandEvalNumerical, theordinary bases still collapse to
0, the symbolic base carries its condition and evaluates toNaNat
x = 1,log(b, 0)follows the sign of its base, and the symbolic case is left alone.