diff --git a/src/Infinities.jl b/src/Infinities.jl index fa5bf55..2af22a9 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 @@ -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 @@ -63,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) diff --git a/src/algebra.jl b/src/algebra.jl index 7311748..669cc4a 100644 --- a/src/algebra.jl +++ b/src/algebra.jl @@ -1,14 +1,17 @@ @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 +(::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) = -∞ @@ -88,7 +91,18 @@ 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/ +_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) + 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/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 diff --git a/test/runtests.jl b/test/runtests.jl index 29344e4..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(-∞) @@ -301,14 +324,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.0 + @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) 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