From 5f216791305b80c670a4b8e05978c5c10eece70c Mon Sep 17 00:00:00 2001 From: Antoine Levitt Date: Thu, 16 Jul 2026 11:56:31 +0200 Subject: [PATCH] Don't widen the primal when differentiating `x^y` with a real base `2^x` is `Float32` for a `Float32` `x`, but `2^Dual{T,Float32,N}` came back `Dual{T,Float64,N}`: `expv = x^v` honours Base's promotion, while `log(x)` was evaluated from `x` alone, so an integer base gave a Float64 partial and the `Dual` constructor unified value and partials upward. Differentiating should not change the primal's type. Take the log in `expv`'s type instead, which is the promotion `^` already chose for this base/exponent pair. Float64 results are bit-identical; only bases narrower than the exponent's own promotion (e.g. `Integer`) change, and only in type. `NaNMath.pow` shares the definition and is fixed too. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_017an8Gie5b1ULeSjhep3Sjb --- src/dual.jl | 2 +- test/DualTest.jl | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/dual.jl b/src/dual.jl index 7552dd5f..0c5d5ebc 100644 --- a/src/dual.jl +++ b/src/dual.jl @@ -576,7 +576,7 @@ for (f, log) in ((:(Base.:^), :(Base.log)), (:(NaNMath.pow), :(NaNMath.log))) begin v = value(y) expv = ($f)(x, v) - deriv = (iszero(x) && v > 0) ? zero(expv) : expv*($log)(x) + deriv = (iszero(x) && v > 0) ? zero(expv) : expv*($log)(oftype(expv, x)) return Dual{Ty}(expv, deriv * partials(y)) end ) diff --git a/test/DualTest.jl b/test/DualTest.jl index df9f9dcb..03021b9d 100644 --- a/test/DualTest.jl +++ b/test/DualTest.jl @@ -499,6 +499,14 @@ ForwardDiff.:≺(::Type{OuterTestTag}, ::Type{TestTag}) = false @test partials(NaNMath.pow(Dual{TestTag}(-2.0, 1.0), Dual{TestTag}(2.0, 0.0)), 1) == -4.0 + # differentiating must not widen the primal: `2^x` is Float32 for a Float32 `x` + @testset "$f: real base keeps $W exponent" for f in (^, NaNMath.pow), + W in (Float16, Float32, Float64) + w = W(4)/W(3) + @test typeof(value(f(2, Dual{TestTag}(w, one(W))))) === typeof(f(2, w)) + @test typeof(value(f(2.0f0, Dual{TestTag}(w, one(W))))) === typeof(f(2.0f0, w)) + end + ################################### # General Mathematical Operations # ###################################