From 5c92e18442a725d8431b98da4cd859f4522f16e3 Mon Sep 17 00:00:00 2001 From: Tianyi Pu <912396513@qq.com> Date: Sat, 6 Dec 2025 20:34:38 +0100 Subject: [PATCH 1/5] Use distinct types for RealInfinity rework of #37 --- src/Infinities.jl | 15 ++++++++++----- src/algebra.jl | 2 +- src/interface.jl | 6 +++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/Infinities.jl b/src/Infinities.jl index fa5bf55..5c549d7 100644 --- a/src/Infinities.jl +++ b/src/Infinities.jl @@ -45,13 +45,18 @@ oneunit(::Infinity) = 1 zero(::Infinity) = 0 zero(::Type{Infinity}) = 0 -struct RealInfinity <: Real - signbit::Bool -end +abstract type RealInfinity <: Real end +struct PositiveInfinity <: RealInfinity end +struct NegativeInfinity <: RealInfinity end + +signbit(::PositiveInfinity) = false +signbit(::NegativeInfinity) = true +one(::RealInfinity) = 1.0 -RealInfinity() = RealInfinity(false) -RealInfinity(::Infinity) = RealInfinity() +RealInfinity() = PositiveInfinity() +RealInfinity(::Infinity) = PositiveInfinity() RealInfinity(x::RealInfinity) = x +RealInfinity(x::Bool) = ifelse(x, NegativeInfinity(), PositiveInfinity()) _convert(::Type{Float16}, x::RealInfinity) = sign(x)*Inf16 _convert(::Type{Float32}, x::RealInfinity) = sign(x)*Inf32 diff --git a/src/algebra.jl b/src/algebra.jl index 7311748..1fc642b 100644 --- a/src/algebra.jl +++ b/src/algebra.jl @@ -8,7 +8,7 @@ # sign +(::Infinity) = RealInfinity() -(::Infinity) = RealInfinity(true) --(y::RealInfinity) = RealInfinity(!y.signbit) +-(y::RealInfinity) = RealInfinity(!signbit(y)) -(y::ComplexInfinity{B}) where B<:Integer = sign(y) == 1 ? ComplexInfinity(one(B)) : ComplexInfinity(zero(B)) +(x::InfiniteCardinal) = x -(::InfiniteCardinal) = -∞ diff --git a/src/interface.jl b/src/interface.jl index b9ebc60..9d4168c 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -7,8 +7,8 @@ iszero(::AllInfinities) = false isinf(::AllInfinities) = true isfinite(::AllInfinities) = false -promote_rule(::Type{Infinity}, ::Type{RealInfinity}) = RealInfinity # not detected by CodeCov. Removing this results in failed tests. +promote_rule(::Type{Infinity}, ::Type{<:RealInfinity}) = RealInfinity # not detected by CodeCov. Removing this results in failed tests. promote_rule(::Type{Infinity}, ::Type{ComplexInfinity{T}}) where T = ComplexInfinity{T} -promote_rule(::Type{RealInfinity}, ::Type{ComplexInfinity{T}}) where T = ComplexInfinity{T} -promote_rule(::Type{ComplexInfinity{T}}, ::Type{RealInfinity}) where T<:Integer = ComplexInfinity{T} +promote_rule(::Type{<:RealInfinity}, ::Type{ComplexInfinity{T}}) where T = ComplexInfinity{T} +promote_rule(::Type{ComplexInfinity{T}}, ::Type{<:RealInfinity}) where T<:Integer = ComplexInfinity{T} promote_rule(::Type{ComplexInfinity{T}}, ::Type{ComplexInfinity{S}}) where {T, S} = ComplexInfinity{promote_type(T, S)} \ No newline at end of file From 1c168dc4b58095cf46cedfae91b7699706bf35c2 Mon Sep 17 00:00:00 2001 From: Tianyi Pu <912396513@qq.com> Date: Sun, 7 Dec 2025 12:36:19 +0100 Subject: [PATCH 2/5] fix --- src/Infinities.jl | 4 ++-- src/algebra.jl | 14 +++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Infinities.jl b/src/Infinities.jl index 5c549d7..7262ed1 100644 --- a/src/Infinities.jl +++ b/src/Infinities.jl @@ -1,11 +1,11 @@ module Infinities import Base: angle, isone, iszero, isinf, isfinite, abs, one, oneunit, zero, isless, inv, - +, -, *, ==, <, ≤, >, ≥, fld, cld, div, mod, min, max, sign, signbit, + +, -, *, ^, ==, <, ≤, >, ≥, fld, cld, div, mod, min, max, sign, signbit, string, show, promote_rule, convert, getindex, Bool, Integer -export ∞, ℵ₀, ℵ₁, RealInfinity, ComplexInfinity, InfiniteCardinal, NotANumber +export ∞, ℵ₀, ℵ₁, RealInfinity, ComplexInfinity, InfiniteCardinal, NotANumber, PositiveInfinity, NegativeInfinity # The following is commented out for now to avoid conflicts with Infinity.jl # export Infinity diff --git a/src/algebra.jl b/src/algebra.jl index 1fc642b..9a57a43 100644 --- a/src/algebra.jl +++ b/src/algebra.jl @@ -88,7 +88,19 @@ for OP in (:fld,:cld,:div) end end -# Base.literal_pow +# power +# Although the base implementation can cover these cases, it can change overtime and yield inconsistent results. +# ref: https://github.com/JuliaMath/Infinities.jl/actions/runs/19993302836/ +@inline function ^(::PositiveInfinity, p::Integer) + iszero(p) && return one(p) + return ifelse(p > 0, +∞, +float(zero(p))) +end +@inline function ^(x::NegativeInfinity, p::Integer) + !isinteger(p) && Base.Math.throw_exp_domainerror(x) + iszero(p) && return float(one(p)) + isodd(p) && return ifelse(p > 0, -∞, -float(zero(p))) + return ifelse(p > 0, +∞, +float(zero(p))) +end # inv inv(::Union{Infinity,InfiniteCardinal}) = 0 From af111f91a407a08cb7c2d44a987eafdbc1e38749 Mon Sep 17 00:00:00 2001 From: Tianyi Pu <912396513@qq.com> Date: Sun, 7 Dec 2025 14:07:31 +0100 Subject: [PATCH 3/5] coverage --- test/runtests.jl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/runtests.jl b/test/runtests.jl index 29344e4..a039b4b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -301,14 +301,26 @@ using Aqua @test Base.literal_pow(^, ℵ₀, Val(0)) ≡ ℵ₀^0 ≡ 1 @test Base.literal_pow(^, ℵ₀, Val(1)) ≡ ℵ₀^1 ≡ ℵ₀ @test Base.literal_pow(^, ℵ₀, Val(-1)) ≡ ℵ₀^(-1) ≡ 0 + @test Base.literal_pow(^, ℵ₀, Val(2)) ≡ ℵ₀^2 ≡ ℵ₀ + @test Base.literal_pow(^, ℵ₀, Val(-2)) ≡ ℵ₀^(-2) ≡ 0 @test Base.literal_pow(^, ∞, Val(0)) ≡ ∞^0 ≡ 1 @test Base.literal_pow(^, ∞, Val(1)) ≡ ∞^1 ≡ ∞ @test Base.literal_pow(^, ∞, Val(-1)) ≡ ∞^(-1) ≡ 0 + @test Base.literal_pow(^, ∞, Val(2)) ≡ ∞^2 ≡ ∞ + @test Base.literal_pow(^, ∞, Val(-2)) ≡ ∞^(-2) ≡ 0 + + @test Base.literal_pow(^, +∞, Val(0)) ≡ (+∞)^0 ≡ 1 + @test Base.literal_pow(^, +∞, Val(1)) ≡ (+∞)^1 ≡ +∞ + @test Base.literal_pow(^, +∞, Val(-1)) ≡ (+∞)^(-1) ≡ 0.0 + @test Base.literal_pow(^, +∞, Val(2)) ≡ (+∞)^2 ≡ +∞ + @test Base.literal_pow(^, +∞, Val(-2)) ≡ (+∞)^(-2) ≡ 0.0 @test Base.literal_pow(^, -∞, Val(0)) ≡ (-∞)^0 ≡ 1.0 @test Base.literal_pow(^, -∞, Val(1)) ≡ (-∞)^1 ≡ -∞ @test Base.literal_pow(^, -∞, Val(-1)) ≡ (-∞)^(-1) ≡ (VERSION < v"1.12-" ? 0.0 : -0.0) + @test Base.literal_pow(^, -∞, Val(2)) ≡ (-∞)^2 ≡ +∞ + @test Base.literal_pow(^, -∞, Val(-2)) ≡ (-∞)^(-2) ≡ 0.0 @test Base.literal_pow(^, ComplexInfinity(0.1), Val(0)) ≡ ComplexInfinity(0.1)^0 ≡ 1.0+0.0im @test Base.literal_pow(^, ComplexInfinity(0.1), Val(1)) ≡ (ComplexInfinity(0.1))^1 ≡ ComplexInfinity(0.1) From 2ab95bdefb4e94774f2796d18f3a2dc43c942760 Mon Sep 17 00:00:00 2001 From: Tianyi Pu <912396513@qq.com> Date: Sun, 7 Dec 2025 14:19:34 +0100 Subject: [PATCH 4/5] fix --- src/Infinities.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Infinities.jl b/src/Infinities.jl index 7262ed1..2af22a9 100644 --- a/src/Infinities.jl +++ b/src/Infinities.jl @@ -68,7 +68,6 @@ for Typ in (RealInfinity, Infinity) @eval Bool(x::$Typ) = throw(InexactError(:Bool, Bool, x)) # ambiguity fix end -signbit(y::RealInfinity) = y.signbit sign(y::RealInfinity) = 1-2signbit(y) angle(x::RealInfinity) = π*signbit(x) From dd50b1f513a675b56ed46b4437a1010f028a0b09 Mon Sep 17 00:00:00 2001 From: Tianyi Pu <912396513@qq.com> Date: Sun, 7 Dec 2025 17:42:18 +0100 Subject: [PATCH 5/5] power of RealInfinity is better supported --- src/algebra.jl | 20 +++++++++++--------- src/ambiguities.jl | 2 ++ test/runtests.jl | 25 ++++++++++++++++++++++++- test/test_ambiguity.jl | 23 +++++++++++++++++++++++ 4 files changed, 60 insertions(+), 10 deletions(-) diff --git a/src/algebra.jl b/src/algebra.jl index 9a57a43..669cc4a 100644 --- a/src/algebra.jl +++ b/src/algebra.jl @@ -1,8 +1,11 @@ @inline infpromote(x, y) = Base._promote(x, y) @inline infpromote(x::ExtendedComplex, y::AllInfinities) = (x, ComplexInfinity(y)) @inline infpromote(x::ExtendedComplex, y::ComplexInfinity) = Base._promote(x, y) -@inline infpromote(x::Real, y::InfiniteCardinal) = (x, ∞) +@inline infpromote(x::Real, ::InfiniteCardinal) = (x, ∞) @inline infpromote(x::Integer, y::InfiniteCardinal) = (x, y) +@inline infpromote(x::RealInfinity, y::Union{Integer, Rational}) = (x, float(y)) +@inline infpromote(x::Union{Integer, Rational}, y::RealInfinity) = (float(x), y) +@inline infpromote(x::RealInfinity, ::InfiniteCardinal) = (x, ∞) # sign @@ -91,16 +94,15 @@ end # power # Although the base implementation can cover these cases, it can change overtime and yield inconsistent results. # ref: https://github.com/JuliaMath/Infinities.jl/actions/runs/19993302836/ -@inline function ^(::PositiveInfinity, p::Integer) +_infpow(::PositiveInfinity, p) = ifelse(iszero(p), one(p), ifelse(p > 0, +∞, +zero(p))) +function _infpow(x::NegativeInfinity, p) + !isinteger(p) && throw(Base.Math.throw_exp_domainerror(x)) iszero(p) && return one(p) - return ifelse(p > 0, +∞, +float(zero(p))) -end -@inline function ^(x::NegativeInfinity, p::Integer) - !isinteger(p) && Base.Math.throw_exp_domainerror(x) - iszero(p) && return float(one(p)) - isodd(p) && return ifelse(p > 0, -∞, -float(zero(p))) - return ifelse(p > 0, +∞, +float(zero(p))) + isodd(p) && return ifelse(p > 0, -∞, -zero(p)) + return ifelse(p > 0, +∞, +zero(p)) end +^(x::RealInfinity, p::Real) = _infpow(infpromote(x, p)...) +^(x::RealInfinity, p::Integer) = _infpow(infpromote(x, p)...) # inv inv(::Union{Infinity,InfiniteCardinal}) = 0 diff --git a/src/ambiguities.jl b/src/ambiguities.jl index 2ca1ead..719df67 100644 --- a/src/ambiguities.jl +++ b/src/ambiguities.jl @@ -27,6 +27,8 @@ for Typ in (Complex, Rational, Complex{Bool}, Integer) @eval *(x::$Typ, y::AllInfinities) = _mul(x, y) end +^(x::RealInfinity, y::Rational) = _infpow(infpromote(x, y)...) + for Typ in (Rational, ) @eval mod(::IntegerInfinities, ::$Typ) = NotANumber() @eval mod(x::$Typ, y::IntegerInfinities) = _mod(x, y) diff --git a/test/runtests.jl b/test/runtests.jl index a039b4b..610b384 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -177,6 +177,29 @@ using Aqua @test (-∞)*2 ≡ 2*(-∞) ≡ -2 * ∞ ≡ ∞ * (-2) ≡ (-2) * RealInfinity() ≡ -∞ @test (-∞)*2.3 ≡ 2.3*(-∞) ≡ -2.3 * ∞ ≡ ∞ * (-2.3) ≡ (-2.3) * RealInfinity() ≡ -∞ + @testset "power" begin + # zero + @test (+∞)^0.0 ≡ (-∞)^0.0 ≡ 1.0 + + # positive even/odd/fraction + @test (+∞)^2.0 ≡ (-∞)^2.0 ≡ +∞ + @test (+∞)^1.0 ≡ +∞ + @test (-∞)^1.0 ≡ -∞ + @test (+∞)^0.5 ≡ +∞ + @test_throws DomainError (-∞)^0.5 + + # negative even/odd/fraction + @test (+∞)^(-2.0) ≡ (-∞)^(-2.0) ≡ 0.0 + @test (+∞)^(-1.0) ≡ 0.0 + @test (-∞)^(-1.0) ≡ -0.0 + @test (+∞)^(-0.5) ≡ 0.0 + @test_throws DomainError (-∞)^(-0.5) + + # irrational + @test (+∞)^π ≡ +∞ + @test_throws DomainError (-∞)^π + end + @test isinf(-∞) @test !isfinite(-∞) @@ -310,7 +333,7 @@ using Aqua @test Base.literal_pow(^, ∞, Val(2)) ≡ ∞^2 ≡ ∞ @test Base.literal_pow(^, ∞, Val(-2)) ≡ ∞^(-2) ≡ 0 - @test Base.literal_pow(^, +∞, Val(0)) ≡ (+∞)^0 ≡ 1 + @test Base.literal_pow(^, +∞, Val(0)) ≡ (+∞)^0 ≡ 1.0 @test Base.literal_pow(^, +∞, Val(1)) ≡ (+∞)^1 ≡ +∞ @test Base.literal_pow(^, +∞, Val(-1)) ≡ (+∞)^(-1) ≡ 0.0 @test Base.literal_pow(^, +∞, Val(2)) ≡ (+∞)^2 ≡ +∞ diff --git a/test/test_ambiguity.jl b/test/test_ambiguity.jl index c2ebeb5..03e2119 100644 --- a/test/test_ambiguity.jl +++ b/test/test_ambiguity.jl @@ -17,4 +17,27 @@ @test fld(inf, 1//2) ≡ cld(inf, 1//2) ≡ div(inf, 1//2) ≡ inf @test fld(inf, ∞) ≡ fld(inf, +∞) ≡ fld(inf, ℵ₀) ≡ fld(inf, ComplexInfinity()) ≡ NotANumber() end + + @testset "rational power" begin + # zero + @test (+∞)^(0//1) ≡ (-∞)^(0//1) ≡ 1.0 + + # positive even/odd/fraction + @test (+∞)^(2//1) ≡ (-∞)^(2//1) ≡ +∞ + @test (+∞)^(1//1) ≡ +∞ + @test (-∞)^(1//1) ≡ -∞ + @test (+∞)^(1//2) ≡ +∞ + @test_throws DomainError (-∞)^(1//2) + + # negative even/odd/fraction + @test (+∞)^(-2.0) ≡ (-∞)^(-2.0) ≡ 0.0 + @test (+∞)^(-1//1) ≡ 0.0 + @test (-∞)^(-1//1) ≡ -0.0 + @test (+∞)^(-1//2) ≡ 0.0 + @test_throws DomainError (-∞)^(-1//2) + + # irrational + @test (+∞)^π ≡ +∞ + @test_throws DomainError (-∞)^π + end end \ No newline at end of file