Repository navigation
Conversation
(x^a)^b = x^(a*b) holds for every real x only when a and b are integers. With a visibly fractional exponent the fold changed the value — (x^2)^(1/2) folded to x instead of |x|, so it evaluated -4 where sqrt(x^2) gave 4 — or invented one where the expression is undefined, as (x^(1/2))^2 did for x < 0. pow_pow now folds only when the base is provably nonnegative or neither exponent is a fractional constant. Extracting pow(z,c) out of pow(x*pow(z,c), n) composes exponents the same way, so mul_pow applies the same check per factor. Signed-off-by: petlenz <peterlenz89.pl@gmail.com>
Two folds still composed exponents on an unknown-sign base. pow(sqrt(x), 2) gave x where sqrt(x) is NaN for x < 0, and pow(pow(x,y),z) multiplied symbolic exponents that may be fractional at evaluation time — y = 2, z = 1/2 reproduces the original (x^2)^(1/2) = |x| bug. Both now fold only when the base is provably nonnegative; exponent composition also accepts exponents provably integer, numeric or assumed. The tests that pinned the old behavior now assert the unfolded form for an unknown-sign base and the folded form for an assumed one. Signed-off-by: petlenz <peterlenz89.pl@gmail.com>
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 #336. Closes #463.
(x^a)^b = x^(a·b)holds for every real x only whenaandbare integers; otherwise it needsx ≥ 0. Three folds composed exponents with no such check, so they either changed the value or invented one where the expression is undefined:pow(pow(x,2), 1/2)x, −4|x|)sqrt(pow(x,2))(same expression, other spelling)pow(pow(x,1/2), 2)x, −4pow(pow(x,3), 1/3)x, −4pow(pow(x,1/2)*y, 2)x*pow(y,2), −16pow(sqrt(x), 2)x, −4pow(pow(x,y), 2), y = 1/2 at evalCondition implemented
Fold when either:
is_nonnegative/is_positive; orinteger{}.Why this shape:
pow(pow(y,-2),-2) → pow(y,4)still folds).a·bbeing an integer is NOT sufficient, so the fold cannot be gated on the composite exponent:a = 2, b = 1/2givesa·b = 1and is exactly the reported bug.aodd withbintegral adds nothing — already a subset of both-integer. Its neighboura = 3, b = 1/3is unsound, not an exemption.is_integeris queryable and lifts the gate: withm,nassumed integer,pow(pow(x,m),n)folds and evaluates 4096 on x = −4.pow(sqrt(x), n):sqrt(x)is real only forx ≥ 0, so the radicand must be provably nonnegative (pow(sqrt(x), n) and symbolic-exponent pow-of-pow fold unsoundly for negative bases (existing tests pin the current behavior) #463).Sites gated
detail::pow_pow_dispatch(generic — used by t2s) and scalarsimplifier::pow_pow, template andnegative_typeoverloads.detail::mul_pow_dispatchand scalarsimplifier::mul_pow: extractingpow(z,c)out ofpow(x*pow(z,c), n)composescwithn, so each nested factor gets the same check.pow_base::dispatch(scalar_sqrt).powneeds no gate:tensor_std.hrejects non-integer constant exponents at construction (tensor_pow broken beyond positive-int/rank-2: eval(pow(X,-1)) returns X (no inversion), pow(X,0.5) evaluates to identity, diff(pow(X,-n)) silently 0, pow(rank-4, 0) yields rank-2 identity #350), so composition stays in the integer regime.Existing tests updated
Each old assertion pinned a fold that is wrong for a negative base; each new one states the domain condition instead. All verified numerically through the evaluator.
ScalarExpressionTest.h:169pow(pow(x,_2),-y)→pow(x,-2*y)pow(pow(x,2),-y); addedpdnonneg →pow(pd,-2*y)ymay be 1/2:(x²)^(−1/2)is1/|x|= 0.25 at x = −4, whilex^(−1)= −0.25:170pow(pow(x,_3),-y)→pow(x,-3*y)(x³)^(−1/3)is NaN:171pow(pow(x,_2),-x)→pow(x,-2*x)-xis symbolic, may be fractional:172pow(pow(x,y),-z)→pow(x,-y*z)pow(pow(pd,y),-z)→pow(pd,-y*z):173pow(pow(x,y),-x)→pow(x,-x*y):367pow(pow(x,y),z)→pow(x,y*z)pbnonneg folded, andm,nassumed integer →pow(x,m*n):368pow(pow(x,y),_2)→pow(x,2*y)y= 1/2 gives(x^(1/2))², NaN for x < 0, whilex^1= −4:369pow(pow(x,_2),y)→pow(x,2*y)y= 1/2 is the reported bug exactly:388pow(x*pow(y,z),_2)→pow(x,2)*pow(y,2*z)zwith 2, same condition:805pow(sqrt(x),_2)→xpow(sqrt(x),2); addedpnonneg →psqrt(−4)is NaN, so the left side is undefined wherexis −4:807pow(sqrt(x),_3)→pow(x,3/2)pnonneg →pow(p,3/2)TensorToScalarExpressionTest.h:267pow(pow(trX,x),_2)→pow(tr(X),2*x)pow(pow(tr(X),x),2)xmay be 1/2 andtr(X)may be negative; the t2s path cannot query either assumptionUnchanged and still folding: every both-integer case (
:365-366,:372-374,:161-163,:167, t2s:264-266), since integer exponents compose for any real base.New tests
ScalarEval.PowOfPowKeepsRealDomain,ScalarEval.PowOfMulKeepsRealDomain,ScalarEval.PowOfSqrtKeepsRealDomain,ScalarEval.PowOfPowSymbolicExponentsKeepRealDomain,T2sEval.PowOfPowKeepsRealDomain,AssumptionFixture.PowOfPowFoldsForNonnegativeBase,AssumptionFixture.PowOfPowKeepsFractionalExponentsForUnknownSign— numeric via the evaluators, covering negative base × {even, odd} inner × {integral, fractional} outer, symbolic exponents that are fractional at evaluation time, both nonnegative-base and assumed-integer escapes, and both spellings agreeing.Negative control (production sources restored from main, tests kept): every new test and all four updated test bodies fail.
PowOfPowFoldsForNonnegativeBasepasses both ways by design — it pins folding that must not regress.Full suite 2412/2412 (gcc-14 Debug), clang-tidy clean with
--warnings-as-errors='*'on the changed.cpp, clang-format clean.